Defining objects from data in an excel table - vb.net

So I have data that is supplier by country where the supplier information changes by country.
600 suppliers and will be upwards of 35 countries(15 attributes per country per supplier). The excel data sheet looks similar to this:
SupplierID SupplierName USsupplierCategory USsupplierCategoryCode UKSupplierCategory ...
1 Sup1 Beverages 1 Ropes
3 Sup5 Ladders 46 Small Ladders
If I could figure out a simple way to get this excel data into an array(even copying and pasting works if the formatting goes quickly since I only have to do this once a month at the most) I can then loop through it an build the needed objects off of the array. But I can't find a simple way to build an array with the excel data without going through a lot of formatting for an array assignment.
I'm pretty new to VB.net and still an amateur programmer and I just can't seem to envision a simple solution to this.
Is an array the way to go? Should I instead loop through each row as a string and break the data by tabs and assign the data that way?
If I am too vague I apologize, let me know and I will provide more specific detail and some code if needed.

The high level approach I'd use is as follows:
1) Define your supplier object
2) Create a supplier object for each row of your datatable and add it to a collection, e.g. a List of supplier objects
Once you have done this, you can easily iterate through your collection of supplier objects to do whatever you like.
Detailed approach:
1) Define your supplier object:
Public Class Supplier
Public property SupplierID as integer
Public Property SupplierName as string
Public Property USsupplierCategory as string
Public property USsuppliercategorycode as integer
Public property UKSupplierCategory as string
End Class
2) Read your excel object into a datatable
This gets a bit tricker. As Tim said, it is easier to save your excel files as csv and then read them in. This is because it is easy to read in csv, but it is also complicated to read in excel files. They can be either xls or xlsx format, and deciding on which one can be a problem. We can also get into COM object issues etc.
If you are to stick with reading in excel, I recommend using Linq2Excel, as this library handles the xls / xlsx issue for you. Here is some example code I knocked up based on your example spreadsheet that returns a list of supplier objects:
Imports LinqToExcel
Imports Remotion.Data.Linq
Imports System.Data
Imports System.Linq
Public Class ReadInExcelData
Public Shared Function GetSupplierListFromSupplierExcel() As List(Of Supplier)
Dim excel As ExcelQueryFactory = New ExcelQueryFactory("C:\Users\YourUserName\Desktop\ExampleData.xls")
Dim suppliers = From c In excel.Worksheet
Select c
Dim list As New List(Of Supplier)
For Each supplier In suppliers
list.Add(New Supplier(supplier.Item(0),
supplier.Item(1),
supplier.Item(2),
supplier.Item(3),
supplier.Item(4)))
Next
Return list
End Function
End Class
You can get the linqtoexcel library here: http://code.google.com/p/linqtoexcel/

Related

vb.net class upcasting / downcasting

I'm having an issue with casting classes in vb.net, I'm using Visual Studio 2008 with the Compact Framework 3.5 as I'm working on legacy Windows Mobile project.
I have a DLL which acts as the Data Layer for accessing Database objects in SqlCe and I cant change any of the code in there, however I want to add extra functionality to the exposed classes for the Business logic so I created my own classes and Inherited the classes from the Data Layer
Public Partial Class Customers
Public Function ListAll() As IEnumerable(Of Customers)
'Do Work
End Function
End Class
Public Class MyCustomers
Inherits Customers
Public Function FindCustomer(ID As Integer)
End Function
End Class
so in my code I would write something like
For Each c As MyCustomer In Customers.ListAll
'I want to be able to use my c.FindCustomer(), but I get an InvalidCast Exception above.
Next
I get that this is an issue with upcasting / downcasting (I don't remember which way is which), but how can I solve it ?
I can't change the return type of Customers.ListAll() but I need to be able to add methods and properties to implement the business logic.
Inside the For Each loop:
For a one-shot:
DirectCast(c, MyCustomer).FindCustomer(1) 'for id 1
To use more than one time:
Dim customer as MyCustomer = DirectCast(c, MyCustomer)
customer.FindCustomer(1)
You can also do:
With DirectCast(c, MyCustomer)
.FindCustomer(1)
.AnotherMethod()
'etc
End With
Have fun!
Here's an alternative. I'm not sure of the exact architecture of your project, so I'll assume it's like this:
Customers -has a list of Customer
MyCustomers -child of Customers with a list of MyCustomer and more functionalities
Customer -base entry
MyCustomer -base entry with more functionalities
The problem being that you cannot cast an object into it's child (this sort of operation can only work in the other direction), this is basically an impossible problem. Yet you can bypass it with some cloning. This tells me that the base data is the same for Customer and MyCustomer, you only added more methods. Which is great, because it also means that you can manually transform a Customer into a MyCustomer. You just need it to happen automatically.
In the MyCustomers and MyCustomer class, you can add theses:
'In MyCustomers
Public Shared Function GetMyCustomersFromCustomers(customers As Customers) As MyCustomers
Dim data As New MyCustomers()
'copy each modal variable
'create a list of MyCustomer from existing Customer list
For Each c As Customer In customers.listOfCustomers
data.listOfMyCustomers.Add(MyCustomer.GetMyCustomerFromCustomer(c))
Next
Return data
End Function
'In MyCustomer
Public Shared Function GetMyCustomerFromCustomer(customer As Customer) As MyCustomer
Dim data As New MyCustomer
'copy all the data
Return data
End Function
Then if you want to work with your own objects you can extrapolate them from the ones of the dll:
'let's say you currently have a 'customers' as Customers object
Dim myStuff as MyCustomers = MyCustomers.GetMyCustomersFromCustomers(customers)
If you often need the MyCustomers list and don't care about the rest of the class, you can create a Shared function which gives you only the extrapolated list of MyCustomer, no problem.
This works only as long as you can extrapolate MyCustomer from Customers and MyCustomer from Customer, of course.
Hope it helps.

Create table with variable number of columns

I am hoping to get a few ideas for something to push me in the right direction. I have a custom class that stores data from a table based on criteria. The raw data (consisting of over 100 columns and varying between 10-1000 rows) is on a worksheet. My code does the following:
1 - Creates an object from the custom class
2 - Adds a value to the properties of the object
3 - Adds the object to a collection
4 - Returns the collection to the controller which sends it to the view to build the table
The following will build a collection of column ranges from the raw data, at least:
Private mcolColumnAddresses As Collection
Private Sub Class_Initialize()
Set mcolColumnAddresses = New Collection
Dim vHeader As Variant
For Each vHeader In mwksReport.Range(mwksReport.Cells(1, 1), mwksReport.Cells(1, mlLastColumn))
mcolColumnAddresses.Add vHeader.Offset(1, 0).Resize(mlLastRow - 1), vHeader.value
Next vHeader
End Sub
The end users want the ability to choose the columns they want for building the new table. But a typical class for a table would use a row as an object with the column headers as the properties. How would I build a table using class properties when the columns are not known until run-time? I hope that makes sense.
Note: I am not asking for code but for suggestions. Has anybody else had this requirement? If so, how did you approach it? An example is welcome, too.
But a typical class for a table would use a row as an object with the column headers as the properties
If your table has really more than 100 columns, or if the column names are only known a runtime, you should probably approach this different. One object per row is fine, but your class could provide a method for accessing all column values by their name. In VBA syntax:
Function GetValue(byval columnName as string) as Variant
'...
As you see, you have to sacrifice some type safety here, but that is typically a small price to pay for getting this solved in a sensible manner.
Internally, your objects can store the values in some Dictionary (in VBA available through the MS Scripting Runtime), indexed by the column names. This leads to
Function GetValue(byval columnName as string) as Variant
if valueDict.ContainsKey(columnName) then
GetValue = valueDict(columnName)
else
'... add some error handling here
end if
End Function
For populating the dictionary, any database has possibilities to determine column names for a table, just google for " get column names programmatically" to find some example code.

vb.net use linq to sort collection of structures by two fields

I have a large collection (Me.OmniColl) of the same structure. Two properties of the structure are .partNumber and .rev. I want to sort first by the part number and then by the rev.
I have tried the following:
Dim q = From c In Me.OmniColl Order By c.partNumber Select c
Dim r = From c In q Order By c.rev Select c
Neither q nor r return any results, even though Me.OmniColl contains thousands of entries. I am already using linq to filter the collection successfully, but all of my sorting attempts have failed. If anyone has a solution, please show your code in vb.net. Thanks in advance for your help.
[Edit]
The structure:
<Serializable()> Structure Part
Public Workbook As String
Public Worksheet As String
Public Product As String
Public partNumber As String
Public itemNo As String
Public rev As String
Public partDescription As String
Public unitOfMeasure As String
Public partType As String
Public purchasingCat As String
Public Quantity As Double
Public TotalPerProduct As Double
Public hierarchy As String
End Structure
[Edit]
I want to find a solution without changing from a Collection to some other type and without changing from a Structure to a Public Class. I have a data caching system in place where I serialize the collection of structures and save this in a textfile before closing the program. I then de-serialize this textfile on program opening and edit the collection as needed based on whether changes to the files the collection is generated from have been made. Sorry for making so many edits.
I would create a Generic.List(Of Part) in my opinion. In my example below col is a Generic.List(Of Part) defined at the top of my class, replace it with your's. Mine looked like this:
Dim col As New Generic.List(Of Part)
To get your List ordered...
Dim newCol As Generic.List(Of Part) = (From c In col Order By c.partNumber, c.rev).ToList()
This will order your item's as needed. Then you asked about removing specific items.
You can use the ForEach function which perform's specific action on each element in the list
newCol.ForEach(Sub(x)
If x.Workbook = condition Then
newCol.Remove(x)
End If
End Sub)
Note: This has been tried and tested
Edit:
If you want to get your Collection into a List...
Dim newColl As List(Of Part) = YOURCOLLECTION.Cast(Of Part).ToList

Advice on most conventional method of parsing data from an input file

I'm a somewhat novice writer of Excel Macros for people in my company. I can tackle the issue at hand in a few different ways but would like some advice on the most efficient and conventional manner to do so.
The macro needs to retrieve data from an input file and compare this data to other data from a second input file. These files are consistent in their structure but obviously different in the data they contain. Each file may contain information for up to 96 samples. Each sample has ~20 categories of information and each category may have 10 pieces of data. So, each sample could have up to 200 pieces of data tied to it.
It seems to me that the best way to store this information is to either create a Class to define an Object and then have a Collection of those Objects like:
Dim Samples as Collection
Dim Smp as CSample
Set Samples = New Collection
For x = 1 to NumberOfSamplesInFile
Set Smp = New CSample
'Set the properties of Smp for each piece of data
Samples.Add Smp
Next x
I assume that I can have certain properties of Smp be arrays? I.e. can a property of the Class be defined as:
Private pSampleID as String
Private pAreaUnderCurve(1 to 10) as double
Private pRetentionTime(1 to 10) as double
Such that
Smp.SampleID = "XYZ"
but
Smp.AreaUnderCurve(1) = 1234
Smp.AreaUnderCurve(2) = 2345
Smp.AreaUnderCurve(3) = 123.78
and the same for retention time (different values obviously)?
The other way I imagined doing this is with the Type declaration:
Type Sample
SampleID as String
AreaUnderCurve(1 to 10) as double
RetentionTime(1 to 10) as double
End Type
My questions is which way is most conventional/recommended?
you could also import the two files into excel in 2 worksheets, then run a comparison on them either by looping through or putting in formulae to match the records and highlight unmatched data...
Is the format of the data files suitable for being imported into Excel?

DNN Dal+ - retrieve individual info class collection items (vb.NET)

I can't seem to find any answers that work. Here's the setup:
Info class:
Public Class ProductStageInfo
Private _ProductNumber As String
Private _ProductReference As String
Public Sub New()
End Sub
Public Property ProductNumber() As String
Get
Return _ProductNumber
End Get
Set(ByVal Value As String)
_ProductNumber = Value
End Set
End Property
End Class
and so on; I have four class declarations in the info class, the one above has fifteen different items - product number, product reference, product name, and so forth. The other's are catalogue classifications, which 'stage' of production the product is in, quality assurance questions; etc.
Then in the Controller class for DNN, I have those various info classes filled via queries to the DB DNN was deployed on; example:
Public Shared Function LoadStages(ByVal ProductNumber As String) As List(Of ProductStageInfo)
Return CBO.FillCollection(Of ProductStageInfo)(CType(DataProvider.Instance().ExecuteReader("Product_LoadStages", ProductNumber), IDataReader))
End Function
and everything works so far, I can fill a datalist using <%# DataBinder.Eval(Container.DataItem, "ProductNumber" %> and in code behind:
Dim ProductStageList As List(Of ProductStageInfo)
ProductStageList = ProductController.LoadStages(ProductNumber)
ProductStageDataList.DataSource = ProductStageList
ProductStageDataList.DataBind()
so far, so good...
but now I need to allow individuals to 'create' stages, and one of the business reqs' is that people shouldn't be able to create, for example, a delivery stage before a packaging stage.
So, how do I go about 'finding' a product number, product reference, stage number, within a collection? I thought I could fill the collection with all the stages of a certain product number, and then do an if/then stage = 0 found, stage > 5 found, etc.
If ProductStageList.Contains(strProductNumber) then
end if
gives error value of type string cannot be converted to namespace.ProductStageInfo; same thing for ProductStageList.Find...
maybe I just don't understand the whole collection/index/thing. All the examples I've found are regarding single dimension collections - 'how to find name within this collection', and the responses use strings to search through them, but somehow the Info class is being treated differently, and I'm not sure how to translate this...
any hints, tips, advice, tutorials.... appreciate it :)
thanks!
Pretty sure I just found the answer by reviewing another module; basically I need to create an empty object instead of a list object of the same class and use the two to iterate through using for/each, etc.
Dim objStages As ProductStagesInfo
Dim intStages, StageSelected As Integer
Dim intStageOption As Integer = -1
Dim blnValid As Boolean = True
Dim ProductChosen As String = lblStagesCNHeader.Text
Dim ProductStageList As List(Of ProductStagesInfo) = ProductController.LoadStages(ProductChosenNumber)
For intStages = 0 To StageList.Count - 1
objStages = StageList(intStages)
intStageOption += 1
Select objStages.StageSetNumber
Case "0"
Next
objStages._ provides me the ability to get the data I needed to do the business logic
<.<
seems so simple once you see it, wish I could just store it all in my brain
blah!