Problems using linq in order to find differences in datatables - vb.net

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.

Related

Check if ArrayList contains an object with a property that equals a specific value

So I have an application in VB.net that is pulling data from a table and inserting it into an arraylist to be used later. What I want to do is before adding the object to the arraylist, I want to check that arraylist to see if the object exists, but I want to be able to check based off a particular property of that object.
Here is an example of what I am talking about:
Lets say Im pulling info from a table with the following columns:
InvoiceNo|DateCharged|Quantity|TotalCharge
I have a SQL statement that pulls info from a table and then I use a data reader to go through the info. My Code looks somewhat like this:
Dim dbobjM As New clsDbobjManual()
If dbobjM.Exec_SQL_DR("SELECT InvoiceNo, DateCharged, Quantity, TotalCharges From Invoices") = 0 Then
If dbobjM.DataReader.HasRows Then
Dim invoicelist As New ArrayList(5000)
Dim invoiceno As String = String.Empty
Do While dbobjM.DataReader.Read()
invoicelist.Add(New Invoice(dbobjM.DataReader.GetInt32(0), dbobjM.DataReader.Value(1), dbobjM.DataReader.GetInt32(2), dbobjM.DataReader.GetFloat(3)))
Loop
End If
End if
(Exec_SQL_DR is a function in the clsDbobjManual class that check to make sure the SQL is in the proper syntax first and checks that records are returned otherwise it returns an error)
Basically what I want to do is before I add a new object to the arraylist I want to check if an object already exists in the list where the InvoiceNo is a particular value, or the value pulled from the table each time to make sure there is no duplicates. I want one object in the list for each InvoiceNo.
Im looking for something like:
If Not invoicelist.Contains(Object where InvoiceNo = dbobjM.DataReader.GetInt32(0)) Then
invoicelist.Add
End If
But I cant seem to find what I need, any help is greatly appreciated
There is no need to use the outdated ArrayList: a List will serve you better. Please see ArrayList vs List<> in C# if you need reasons - the advantages for a list apply to VB.NET too.
Without seeing your clsDbobjManual or Invoice classes, I ended up writing the minimal code to do what you're after, which is basically the check for invoices.Any(Function(i) i.InvoiceNo = inv.InvoiceNo), which you can do if you have the data in a List(Of Invoice).
Please note that I assumed that the appropriate data types have been used in the database - you should use the Decimal type for money as otherwise you can end up with significant rounding errors, and a date should be stored as DateTime, not as a string.
Imports System.Data.SqlClient
Module Module1
Class Invoice
Property InvoiceNo As Integer
Property DateCharged As DateTime
Property Quantity As Integer
Property TotalCharges As Decimal
Sub New()
' empty constructor
End Sub
Sub New(invoiceNo As Integer, dateCharged As DateTime, quantity As Integer, totalCharges As Decimal)
Me.InvoiceNo = invoiceNo
Me.DateCharged = dateCharged
Me.Quantity = quantity
Me.TotalCharges = totalCharges
End Sub
End Class
Function LoadData() As List(Of Invoice)
Dim invoices As New List(Of Invoice)
Dim connStr As String = "your connection string"
Dim sql = "SELECT InvoiceNo, DateCharged, Quantity, TotalCharges From Invoices"
Using sqlConn As New SqlConnection(connStr)
Using sqlCmd As New SqlCommand(sql, sqlConn)
Dim reader As SqlDataReader = sqlCmd.ExecuteReader()
While reader.Read()
Dim inv As New Invoice(reader.GetInt32(0), reader.GetDateTime(1), reader.GetInt32(2), reader.GetDecimal(3))
If Not (invoices.Any(Function(i) i.InvoiceNo = inv.InvoiceNo)) Then
invoices.Add(inv)
Else
' there is a duplicate invoice number
End If
End While
End Using
End Using
Return invoices
End Function
Sub Main()
Dim uniqueInvoices As List(Of Invoice) = LoadData()
' uniqueInvoices now contains the data
End Sub
End Module
If you had a lot of invoice entries to go through, you would likely be better off writing an SQL query to do that.
If you actually just want to find duplicate invoice numbers, you could use the SQL
SELECT [InvoiceNo]
FROM testTable
GROUP BY [InvoiceNo]
HAVING COUNT([InvoiceNo]) > 1
Finally, please ensure that you are using Option Strict On so that you don't make accidental data type errors - they can drastically slow down your program and lead to erroneous results.
You can use linq to select the objects that matches your condition.
Dim result = (From invoiceitem As Invoice
In invoicelist
Where invoiceitem.InvoiceNo = dbobjM.DataReader.GetInt32(0)
Select invoiceitem).ToList()
If Not result.Count > 0 Then
invoicelist.Add(New Invoice(dbobjM.DataReader.GetInt32(0), dbobjM.DataReader.Value(1), dbobjM.DataReader.GetInt32(2), dbobjM.DataReader.GetFloat(3)))
End If

How to return a List(Of String) from a LINQ statement with a Group By in 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

Search DataTable with values from another table

I'm trying to search one DataTable with values from anotherDataTable using LINQ, but no progress so far... How to do it?
In example below i have table, in which i search, and PlTable, which has only one column; and i need to retrieve every row from table, in which the Name field contains at least one string of Name field in PlTable's rows.
Dim ePlTable As IEnumerable(Of DataRow) = PlTable.AsEnumerable()
Dim found = From row In table.AsEnumerable
Where row(0).Contains(ePlTable)
Select row
Return found.CopyToDataTable.Rows
Surely it does not work, as .Contains wants String as argument
Surely it does not work, as .Contains wants String as argument
That's exatly the problem, so use the strongly typed Field extension
method to cast it to it's correct type and Enumerable.Any to look if at least one string is contained in this Name:
Dim strings = From row In PlTable Select row.Field(Of String)(0)
Dim found = From row In table.AsEnumerable
Where strings.Any(Function(s) row.Field(Of String)("Name").Contains(s))
Select row
Return found.CopyToDataTable()

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

DataTable Select(String) Function Help VB .NET

I made a datatable with 2 columns a transactionTime column and a numberOfTransactions column. I made the table with the pre-defined transaction times and want to add the number of transactions from an XML file. I have gotten through the XML file and want to add the data to the correct row. Here is the function:
Function AddRow(ByVal timeOfTransaction As String, ByVal numberOfTransactions As String, ByRef dataTableOfTransactions As DataTable) As String
Dim row() As DataRow = dataTableOfTransactions.Select("transactionTime = timeOfTransaction")
If row(0) IsNot Nothing Then
row(0)("numberOfTransactions") = numberOfTransactions
End If
Return Nothing
End Function
When I run this it overwrites the first element in the table's numberOfTransactions coloumn. I know it has to do with the "transactionTime = timeOfTransaction" part but I can't seem to get it to read timeOfTransaction as a reference to a string instead of a literal. Any help would be much appreciated. Thanks!
You need to write something like this :
Dim row() As DataRow = dataTableOfTransactions.Select("transactionTime=#" & timeOfTransaction & "#")
But be careful with your date/month or month/date format, it depends of your regional settings.
row(0)("numberOfTransactions") = numberOfTransactions
Right there you are telling the program to overwrite that value with number of transactions.
If you want that value you need to set it to something, not set something to it.
Also, if you want your select to work properly try doing it like this
dataTableOfTransactions.Select("transactionTime = " + timeOfTransaction)