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

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

Related

How to Save/Reload data in vb.net after .exe close?

I am new to vb.net, and this is my first project where I'm fairly certain there is an obvious answer that I just can't find.
Problem: I have a list of a structure I have defined with many properties. I want to be able to edit and load that list with the values I have saved to it before hand after closing the program and loading it backup. What is the best way to do this?
This isn't a simple string or bool, otherwise I would use the user settings that is commonly suggested, in the project's properties. I've seen others that save it into an xml and take it back up, but I'm not inclined to do so since this is going to be distributed to others in mass. Since it's a complex structure, what's the commonly held preferred method?
Example
Here's a structure:
Structure animal
Dim coloring as string
Dim vaccinesUpToDate as Boolean
Dim species as string
Dim age as integer
End structure
And there's a List(Of animal) that the user will add say 1 cat, 2 dogs, etc. I want it so that once the programs is closed after the user has added these, that structure will be saved to still have that 1 cat and 2 dogs with those settings so I can display them again. What's the best way to save the data in my program?
Thanks!
Consider serialization. For this, a class is more in order than an old fashioned Struct:
<Serializable>
Class Animal
Public Property Name As String
Public Property Coloring As String
Public Property VaccinesUpToDate As Boolean
Public Property Species As String
Public Property DateOfBirth As DateTime
Public ReadOnly Property Age As Integer
Get
If DateOfBirth <> DateTime.MinValue Then
Return (DateTime.Now.Year - DateOfBirth.Year)
Else
Return 0 ' unknown
End If
End Get
End Property
' many serializers require a simple CTor
Public Sub New()
End Sub
Public Overrides Function ToString() As String
Return String.Format("{0} ({1}, {2})", Name, Species, Age)
End Function
End Class
The ToString() override can be important. It is what will display if you add Animal objects to a ListBox e.g.: "Stripe (Gremlin, 27)"
Friend animalList As New List(of Animal) ' a place to store animals
' create an animal
a = New Animal
a.Coloring = "Orange"
a.Species = "Feline" ' should be an Enum maybe
a.Name = "Ziggy"
a.BirthDate = #2/11/2010#
animalList.Add(a)
' animalList(0) is now the Ziggy record. add as many as you like.
In more complex apps, you might write an Animals collection class. In that case, the List might be internal and the collection could save/load the list.
Friend Sub SaveData(fileName as String)
Using fs As New System.IO.FileStream(fileName,
IO.FileMode.OpenOrCreate)
Dim bf As New BinaryFormatter
bf.Serialize(fs, animalList)
End Using
End Sub
Friend Function LoadData(fileName as String) As List(Of Animal)
Dim a As List(of Animal)
Using fs As New FileStream(fileName, FileMode.Open, FileAccess.Read)
Dim bf As New BinaryFormatter
a = CType(bf.Deserialize(fs), List(Of Animal))
End Using
Return a
End Function
XMLSerialization, ProtoBuf and even json are much the same syntax. For a small amount of data, a serialized list is an easy alternative to a database (and have many, many other uses, like a better Settings approach).
Calculated Fields as Properties
Notice that I added a BirthDate property and changed Age to calculate the result. You should not save anything which can be easily calculated: in order to update the Age (or VaccinesUpToDate) you'd have to 'visit' each record, perform a calculation then save the result - which might be wrong in 24 hours.
The reason for exposing Age as a Property (rather than a function) is for data binding. It is very common to use a List<T> as the DataSource:
animalsDGV.DataSource = myAnimals
The result will be a row for each animal with each Property as a column. Fields as in the original Structure won't show up. Nor would an Age() function display, wrapping the result as a readonly property displays it. In a PropertyGrid, it will show disabled because it is RO.
Class versus Structure
So if a Structure using Properties will work, why use a Class instead? From Choosing Between Class and Struct on MSDN, avoid using a Structure unless the type meets all of the following:
It logically represents a single value, similar to primitive types (int, double, etc.)
It has an instance size under 16 bytes
It is immutable
It will not have to be boxed frequently
Animal fails the first 3 points (while it is a local item it is not a value for #1). It may also fail the last depending on how it is used.

vb.NET Select distinct... how to use it?

Coming from a C# background I am a bit miffed by my inability to get this simple linq query working:
Dim data As List(Of Dictionary(Of String, Object))
Dim dbm As AccessDBManager = GlobalObjectManager.DBManagers("SecondaryAccessDBManager")
data = dbm.Select("*", "T町丁目位置_各務原")
Dim towns As IEnumerable(Of String())
towns = data.Select(Function(d) New String() {d("町名_Trim").ToString(), d("ふりがな").ToString()})
towns = towns.Where(Function(s) s(0).StartsWith(searchTerms) Or s(1).StartsWith(searchTerms)).Distinct()
Call UpdateTownsListView(towns.ToList())
I pasted together the relevant bits, so hopefully there is no error here...
data is loaded from an access database and is a list with the data from each row stored as a dictionary.
In this case element from data has a field containing the name of a Japanese town and its reading and some other stuff like the row ID etc.
I have a form with a textbox. When the user types something in, I would like to retrieve from data the town names corresponding to the search terms without duplicates.
Right now the results contain loads of duplicates> How can I get this sorted to only get distinct results?
I read from some other posts that a key might be needed, but how can I declare this with extension methods?
Distinct uses the default equality comparer to compare values.
Your collection contains arrays of strings, so Distinct won't work the way you expected since two different arrays never equals each other (since ReferenceEquals would be used in the end).
A solution is to use the Distinct overload which takes an IEqualityComparer.
Class TwoStringArrayEqualityComparer
Implements IEqualityComparer(Of String())
Public Function Equals(s1 As String(), s2 As String()) As Boolean Implements IEqualityComparer(Of String()).Equals
' Note that checking for Nothing is missing
Return s1(0).Equals(s2(0)) AndAlso s1(1).Equals(s2(1))
End Function
Public Function GetHashCode(s As String()) As Integer Implements IEqualityComparer(Of String()).GetHashCode
Return (s(0) + s(1)).GetHashCode() ' probably not perfect :-)
End Function
End Class
...
towns = towns.Where(...).Distinct(new TwoStringArrayEqualityComparer())

How can I dynamically select my Table at runtime with Dynamic LINQ

I'm developing an application to allow engineers to conduct simple single table/view queries against our databases by selecting Database, Table, Fields.
I get how to use the Dynamic LINQ Library Sample to provide for dynamically selecting the Select, Where and Order by Clauses at runtime but I'm at an impass on how to allot for table choice.
Is there a way to provide for dynamically selecting the "from" table at run time, and if how could you provide some concrete example or point me in the direction of someone who has?
Thank You ever so much.
EDIT
So Both of the answers seem to be saying the same general Idea. I'm going to try to convert the C# into VB and get it to work.
The first answer converts to
NotInheritable Class DataContextExtensions
Private Sub New()
End Sub
<System.Runtime.CompilerServices.Extension> _
Public Shared Function GetTableByName(context As DataContext, tableName As String) As ITable
If context Is Nothing Then
Throw New ArgumentNullException("context")
End If
If tableName Is Nothing Then
Throw New ArgumentNullException("tableName")
End If
Return DirectCast(context.[GetType]().GetProperty(tableName).GetValue(context, Nothing), ITable)
End Function
End Class
but it's tossing me an Error stating that Extension methods can be defined only in modules.
But when I wrap it in module tags it still gives the same error.
So I got it to compile by wrapping it in Module Tags and stripping the class tags. Also I can pull the last line out of it and shove that directly into my base method which allows my to execute it but, it seems to be coming back empty. When I try to enumerate the results there are none. Not sure if this is my codes problem or the new codes issue, I'll test more.
Here's my conversion of the second Example, Now I'm off to try to see if I can get them to work. I'll be back with questions or results after some testing.
'get the table from a type (which corresponds to a table in your context)
Dim dataContextNamespace = "My.DataContext.Namespace"
Dim type = Type.[GetType](dataContextNamespace + tableName)
Dim table = dc.GetTable(type
'add where clauses from a list of them
For Each whereClause As String In whereClauses
table = table.Where(whereClause)
Next
'generate the select clause from a list of columns
Dim query = table.[Select]([String].Format("new({0})"), [String].Join(",", selectColumns))
Thanks for the help. BBL
See Get table-data from table-name in LINQ DataContext.
This is probably actually better done by using direct SQL statements. LINQ will just get in your way.
VB Conversion:
NotInheritable Class DataContextExtensions
Private Sub New()
End Sub
<System.Runtime.CompilerServices.Extension> _
Public Shared Function GetTableByName(context As DataContext, tableName As String) As ITable
If context Is Nothing Then
Throw New ArgumentNullException("context")
End If
If tableName Is Nothing Then
Throw New ArgumentNullException("tableName")
End If
Return DirectCast(context.[GetType]().GetProperty(tableName).GetValue(context, Nothing), ITable)
End Function
End Class
Usage:
Dim myDataContext as New MyCustomDataContext
myDataContext.GetTableByName("ORDERS").Where("...")
You can use GetTable() to get the corresponding ITable of your data. Then coupled with using DLINQ, making it relatively easy.
This example uses the AdventureWorks database. My project has the context defined in the DatabaseTest assembly in the DatabaseTest.AdventureWorks namespace.
'' need my database and DLINQ extensions up top
Imports DatabaseTest.AdventureWorks
Imports System.Linq.Dynamic
'' sample inputs
Dim dc = New AdventureWorksDataContext()
Dim tableName = "Contact"
Dim whereClauses() = {"FirstName = ""John"" OR LastName = ""Smith"""}
Dim selectColumns() = {"FirstName", "LastName"}
'' get the table from a type (which corresponds to a table in your database)
Dim typeName = "DatabaseTest.AdventureWorks." & tableName & ", DatabaseTest"
Dim entityType = Type.GetType(typeName)
Dim table = dc.GetTable(entityType)
Dim query As IQueryable = table
'' add where clauses from a list of them
For Each whereClause As String In whereClauses
query = query.Where(whereClause)
Next
'' generate the select clause from a list of columns
query = query.Select(String.Format("new({0})", String.Join(",", selectColumns)))
In retrospect, using reflection might have been the easier way to get the table since you have the name already. But then the names might not have a 1-to-1 correspondence so you'll have to compensate for it.
Dim table As ITable = dc.GetType().GetProperty(tableName & "s").GetValue(dc, Nothing)

List with different object types?

Can I have a List containing one string and two numbers? Or I can only have one type of element?
If that's the kind of functionality you want, then I would look at the non-generic System.Collections.ArrayList class.
Update
For those of you who aren't going to read the huge comment chain...it looks like Adam Robinson is on to something using List<object> over ArrayList. Both will work but on large collections it seems like List<object> is measurably faster than ArrayList.
You can. A list of Objects can do that. But, you lose type safety with that and also design time intelliSense.
What do you want to do? You could also use a class with 3 members.
No, containers like List(Of T) store exactly one type T of elements. You can, though, make this one type consist of one string and two numbers.
Structure Foo
Public Desc As String
Public x As Integer, y As Integer
End Structure
Dim List = New List(Of Foo)
Yes, you can.
dim myVehicles as new list(of object)
dim myCar as new car
dim myBike as new bike
dim mySecondCar as new car
myVehicles.add(myCar)
myVehicles.add(myBike)
myVehicles.add(mySecondCar)

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!