How to return a List(Of String) from a LINQ statement with a Group By in VB.net? - vb.net

I've seen several questions on how to do this in C# but I'm having trouble translating those to VB. Here's the basics of my issue:
Table of data NOT normalized and accessed via Entity Framework
Get all unique string values in a certain field
Convert those values to a List(Of String)
This works but I'm guessing there's a better way to do it without iterating through the list:
Public Function GetGroups() As IEnumerable(Of String)
Dim GroupList As New List(Of String)
Dim CodeList = (From c In Context.Codes
Group c.Group By c.Group Into g = Group)
For Each c In CodeList
GroupList.Add(c.Group)
Next
Return GroupList
End Function
What I seem to be struggling with the most is using Group By in LINQ. I'm guessing this could probably be done in 1 or 2 lines by having LINQ return just the list of strings or by converting the list of anonymous objects to a list of strings.

Well, if you don't need anything in the group, you can just use .Distinct():
Return (
From c In Context.Codes
Order By c.Group
Select c.Group
).Distinct().ToList()
Edit: Added Order By

Related

Problems using linq in order to find differences in datatables

I have the following function:
Public Function Check_Desparity(Byval dtTestStep as DataTable, Byval dtLimits as DataTable) as DataTable
Dim diff = dtTestSteps.AsEnumerable.Union(dtLimits.AsEnumberable).Except(dtTestSteps.Intersect(dtLimits.AsEnumerable))
End Function
I expect, that diff contains the rows with differences. But it doesn`t. I have two differences, but diff contains only one and that one is no difference.
When I try the same thing with List(Of String) instead of DataTable it works perfect.
Public Function Check_Desparity(Byval TestStep as List(Of String), Byval Limits as List(Of String)) as List(Of String)
Dim diff = TestStep.Union(Limits).Except(TestStep.Intersect(Limits))
End Function
Here I get exactly the two differences of both lists back in diff.
Could somebody explain me why?
Thank you
EDIT:
With help of you, I got exactly what I wanted. The function for my answer is the following:
Public Function Check_Desparity(Byval dtTestStep as DataTable, Byval dtLimits as DataTable) as IEnumerable(Of DataRow)
Dim diff = dtLimits.AsEnumerable.Except(dtTestSteps.AsEnumberable, DataRowComparer.Default)
Return diff
End Function
But I forgot to mention an important detail.
This function works only if both of the tables have the same columns.
In my case, the columns are different, but column "dictkey". Column "dictkey" exists in both of my datatables.
How I get it to work, that my function returns only rows, where "dictkey" is different respectivly not existent?
You can't use Except, Intersect or Union in this way because DataRow.Equals is not overridden, hence it will just compare references and all are different. You can use DataRowComparer.Default which compares all columns of the row with all columns of the other row.
Your LINQ query doesn't make sense either, i guess you want something like this:
Public Function Check_Desparity(ByVal dtTestStep As DataTable, ByVal dtLimits As DataTable) As DataTable
Dim stepRows = dtTestStep.AsEnumerable()
Dim limitRows = dtLimits.AsEnumerable()
Dim allInStepButNotInLimit = stepRows.Except(limitRows, DataRowComparer.Default)
Dim allInLimitButNotInStep = limitRows.Except(stepRows, DataRowComparer.Default)
End Function
I think this is because DataTable.AsEnumerable returns IEnumerable of DataRow.
DataRow is a reference type and since LINQ uses Equals() for comparison to find differences, all rows from both tables are considered to be different (they all are different objects).
Your code works for strings because they are compared using their content, like value types.

Using Linq order by an object's property Vb.Net

I have a datatable of pointshape Objects in a map and I want to order this list by Object.baseshape.label.y which is a double value. How can I do this using Linq?
I Have implemented this code so far
Dim query As IEnumerable(Of DataRow) = From result In dataArray.AsEnumerable() Order By result.Field(Of Object)("MapShapes") Descending
but I want something like this
Dim query As IEnumerable(Of DataRow) = From result In dataArray.AsEnumerable() Order By result.Field(Of Object)("MapShapes")..baseshape.label.y Descending
You can use the specific object type in the Field(Of ...) like this...
Dim query = From result In dataArray.AsEnumerable() Order By result.Field(Of MapSuite.BaseMapShape)("MapShapes").label.y Descending

Getting 2100 parameter limit in LINQ

I am getting 2100 parameter limit error in a LINQ to SQL query. Here is the procedure which returns the list of id's. Parameter l has over 5000 id's.
Public Function GetByID(ByVal l As List(Of Int32)) As List(Of OfficeAccess)
Return (From d In db.OfficeAccess
Where l.Contains(d.ID)
Order By d.ID Ascending Select d).ToList()
End Function
I have tried the solution given in the second part of Hitting the 2100 parameter limit (SQL Server) when using Contains() but I'd need another solution without manually building SQL query string and this doesn't work:
Dim ids = String.Join(" ", l.ToArray())
Return(From d In db.OfficeAccess
Where ids.IndexOf(Convert.ToString(d.ID)) != -1
Order By d.ID Ascending Select d).ToList()
Updated
I have used the following code and works fine.
Dim l As New List(Of OfficeAccess)
Dim f As Int32, t As List(Of Int32)
While (ids.Count > 0)
If (ids.Count < 2000) Then f = ids.Count Else f = 2000
t = ids.GetRange(0, f)
l.AddRange((From d In db.OfficeAccess Where t.Contains(d.ID) Select d).ToList())
ids.RemoveRange(0, f)
End While
You may want to partition your original list into smaller chunks. Let's say each one of 2000 elements, this will affect performance (especially if SQL Server version doesn't support OFFSET n and FETCH NEXT m ROWS) but it'll solve your problem.
Function Partition(ByVal l As List(Of Integer)) As List(Of OfficeAccess)
Const size As Integer = 2000
Dim result As List(Of OfficeAccess) = new List(Of OfficeAccess)
Dim index As Integer = 1
Dim partition As List(Of Integer) = (
From item In l
Take size
).ToList()
While partition.Any()
result.AddRange(
From d In db.OfficeAccess
Where partition.Contains(d.ID)
Order By d.ID Ascending
Select d
)
partition = (
From item in l
Skip index * size
Take size
).ToList()
index += 1
End While
Return result
End Function
Please note code is untested then be ready to syntax errors!
Note: in this example I accumulate partial results into result list but you may concatenate each enumeration with Enumerable.Concat() (declaring result as IEnumerable(Of OfficeAccess) and performing ToList() once: Return result.ToList(). It shouldn't make any (visible) difference unless you have a huge number of records (because, at least, you avoid to resize result for each bulk insertion) or unless LINQ is smart enough to optimize (somehow) full resulting query (as concatenation of multiple queries).
I'd avoid, if possible, to manually build SQL string (also to keep code reusable for other data types and easy to update and refactor) but, from linked post, you should try code from one of answers instead of not working code from question...

Sorting a List by the second column in VB

I am using a loop that builds a list of filenames and their creation dates:
Dim datelist As New List(Of KeyValuePair(Of String, Date))
Dim values As New KeyValuePair(Of String, Date)(filename, initialdate)
If Not datelist.Contains(values) Then
datelist.Add(values)
End If
After the list is populated, I need to sort it by date before performing some other functions.
I've been looking at an orderby or sort method, but I can't figure out how to implement them correctly. Can someone give me a hand?
This sorts the original list without creating a new list (like the Linq methods) using List.Sort:
datelist.Sort(Function(kv1, kv2) kv1.Value.CompareTo(kv2.Value))
Dim sorted = (From item In datelist Order By item.Value Select item).ToList
should do the trick
What's wrong with just using some simple LINQ?
Dim orderedList As List(Of KeyValuePair(Of String, Date)) = datelist.OrderBy(Function(o) o.Value).ToList

VB.Net - Efficient way of de-duplicating data

I am dealing with a legacy application which is written in VB.Net 2.0 against a SQL 2000 database.
There is a single table which has ~125,000 rows and 2 pairs of fields with similar data.
i.e. FieldA1, FieldB1, FieldA2, FieldB2
I need to process a combined, distinct list of FieldA, FieldB.
Using SQL I have confirmed that there are ~140,000 distinct rows.
Due to a very restrictive framework in the application I can only retrieve the data as either 2 XML objects, 2 DataTable objects or 2 DataTableReader objects. I am unable to execute custom SQL using the framework.
Due to a very restrictive DB access policy I am unable to add a View or Stored Proc to retrieve as a single list.
What is the most efficient way to combine the 2 XML / DataTable / DataTableReader objects into a single, distinct, IEnumerable object for later processing?
I may have missed something here but could you not combine both DataTables using Merge?
DataTableA.Merge(DataTableB)
You can then use DataTableA.AsEnumerable()
Then see this answer on how to remove duplicates or
You can do this with a DataView as follows: dt.DefaultView.ToTable(True,[Column names])
This is the solution I came up with.
Combine the 2 DataTables using .Merge (thanks to Matt's answer)
Using this as a base I came up with the following code to get distinct rows from the DataTable based on 2 columns:
Private Shared Function GetDistinctRows(sourceTable As DataTable, ParamArray columnNames As String()) As DataTable
Dim dt As New DataTable
Dim sort = String.Empty
For Each columnName As String In columnNames
dt.Columns.Add(columnName, sourceTable.Columns(columnName).DataType)
If sort.Length > 0 Then
sort = sort & ","
End If
sort = sort & columnName
Next
Dim lastValue As DataRow = Nothing
For Each dr As DataRow In sourceTable.Select("", sort)
Dim add As Boolean = False
If IsNothing(lastValue) Then
add = True
Else
For Each columnName As String In columnNames
If Not (lastValue(columnName).Equals(dr(columnName))) Then
add = True
Exit For
End If
Next
End If
If add Then
lastValue = dr
dt.ImportRow(dr)
End If
Next
Return dt
End Function