Compare two datatables, if anything is different show MessageBox - vb.net

I have two datatables, one of them is populated when application starts and the other one is populated on button click. How can i check (fastest way) if anything changed in second datatable?
I have tried this but it does not work:
For Each row1 As DataRow In dtt.Rows
For Each row2 As DataRow In dtt1.Rows
Dim array1 = row1.ItemArray
Dim array2 = row2.ItemArray
If array1.SequenceEqual(array2) Then
Else
End If
Next
Next

The problem is that your loops are nested. This means that the inner For Each loops through each row of dtt1 for each single row of dtt. This is not what you want. You want to loop the two tables in parallel. You can do so by using the enumerators that the For Each statements use internally
Dim tablesAreDifferent As Boolean = False
If dtt.Rows.Count = dtt1.Rows.Count Then
Dim enumerator1 = dtt.Rows.GetEnumerator()
Dim enumerator2 = dtt1.Rows.GetEnumerator()
Do While enumerator1.MoveNext() AndAlso enumerator2.MoveNext()
Dim array1 = enumerator1.Current.ItemArray
Dim array2 = enumerator2.Current.ItemArray
If Not array1.SequenceEqual(array2) Then
tablesAreDifferent = True
Exit Do
End If
Loop
Else
tablesAreDifferent = True
End If
If tablesAreDifferent Then
'Display message
Else
'...
End If
The enumerators work like this: They have an internal cursor that is initially placed before the first row. Before accessing a row through the Current property, you must move to it with the MoveNext function. This function returns the Boolean True if it succeeds, i.e. as long as there are rows available.
Since now we have a single loop statement and advance the cursors of enumerator1 and enumerator2 at each loop, we can compare corresponding rows.
Note that the Rows collection implements IEnumerable and thus the enumerators returned by GetEnumerator are not strongly typed. I.e. Current is typed as Object. If instead you write
Dim enumerator1 = dtt.Rows.Cast(Of DataRow).GetEnumerator()
Dim enumerator2 = dtt1.Rows.Cast(Of DataRow).GetEnumerator()
Then you get enumerators of type IEnumerator(Of DataRow) returning strongly typed DataRows.

Related

Searching a custom object

As a follow-up to my previous question, and continuing with the sample code in the answer there, how can I e.g. find out if a ParameterSet exists in my ParameterSetCollection with a Y parameter of "foobar"? I can write code to iterate over the ParameterSet, but it seems there should be a better way.
Dim parameterSets As New ParameterSetCollection
'...
If parameterSetCollection.Any(Function(ps) ps.Y = "foobar") Then
'At least one item in the collection has a Y value of "foobar".
End If
Any is a LINQ method and LINQ excels at compressing loops. That code is functionally equivalent to this:
Dim parameterSets As New ParameterSetCollection
'...
Dim match = False
For Each ps In parameterSetCollection
If ps.Y = "foobar" Then
match = True
Exit For
End If
Next
If match Then
'At least one item in the collection has a Y value of "foobar".
End If

How to get the value of a column using a dataview in vb.net

I have a list of records and for Employee R1005, I need to check if that Employee has been Enabled for login alert (i.e EnableLoginAlert = Yes), then a button will be displayed.
CompanyID EmployeeNo EnableLoginAlert
10046 R1005 Yes
20041 Ajax12 No
47021 Drek Yes
I have tried the below codes:
If dCompanyDetails.Tables(0).Rows.Count > 0 Then
Dim dataView As DataView = dCompanyDetails.Tables(0).DefaultView
dataView.RowFilter = "EmployeeNo = '" & strEmployeeNumber & "'"
Dim svalue As String = dataView.Table.Rows(0).ItemArray(0).ToString()
If svalue = "No" Then
AlertButton.Visible = False
ElseIf svalue = "Yes" Then
{
//Do something else
}
End If
End If
If you are going to use a DataView then use it. This:
Dim svalue As String = dataView.Table.Rows(0).ItemArray(0).ToString()
is simply going back to the DataTable and using it, ignoring the DataView. The DataView contains DataRowView objects so get the one you need and use it. It is similar to a DataRow and you can use it the same way in this case:
Dim enableLoginAlert = CStr(dataView(0)("EnableLoginAlert")) = "Yes"
Now you have an actual Boolean that represents the state you want.
That's not how you should do it though. Generally speaking, you would use a DataView when you want to bind data. In fact, if you bind a DataTable then the data you see in the UI actually comes from the DefaultView. That's why you can filter and sort it. In this case, there are better options.
If you want to find a row by its primary key then the Rows collection of a DataTable has a Find method, e.g.
Dim row = dCompanyDetails.Tables(0).Rows.Find(strEmployeeNumber)
Dim enableLoginAlert = CStr(row("EnableLoginAlert")) = "Yes"
If you're searching by other than the primary key, the DataTable itself has a Select method. Because multiple rows may match, it returns an array, so you need to get the row out of that, e.g.
Dim row = dCompanyDetails.Tables(0).Select($"EmployeeNo = '{strEmployeeNumber}'").First()
Dim enableLoginAlert = CStr(row("EnableLoginAlert")) = "Yes"
If you want to look up a single row it's perhaps easiest to use LINQ:
Dim row = dCompanyDetails.Tables(0).Rows.Cast(Of DataRow).AsQueryable().FirstOrDefault(Function(r) r("EmployeeNo").ToString() = strEmployeeNumber)
If row IsNot Nothing AndAlso row("EnableLoginAlert").ToString() = "Yes" Then
...
..though I'd be the first to claim that using LINQ on base DataTables is very verbose, because of the Cast/AsQueryable. I'd use strongly typed DataTables (in a dataset); if you were to convert your code to using strongly typed tables it would look like:
Dim r = someDataSet.AProperTableName.FirstOrDefault(Function(r) r.EmployeeNo = strEmployeeNumber)
If r?.EnableLoginALert = "Yes" Then
...
...using strongly typed datatables is much less messy..
nb: You need to Imports System.Linq for these to work
That LINQ is the same thing as:
For Each r as DataRow in dCompanyDetails.Tables(0)
If r("EmployeeNo").ToString() = "R1005" AndAlso r("EnableLoginAlert").ToString() = "Yes" Then
...
You also have the option of using DataTable.Select (not a LINQ thing, though LINQ has a Select too)
Dim matchingRows = dCompanyDetails.Tables(0).Select($"[EmployeeNo] = '{strEmployeeNumber}'")
If matchingrows.Count > 0 AndAlso matchingRows(0)("EnableLoginAlert").ToString() = "Yes"

Looping through Entity Framework complex type

Trying to loop through the results of a stored procedure using entity framework. I need to compare the list of communities to the value in a textbox so the user does not enter duplicate communities in the database, using a duplicate flag. I can retrieve my list of communities but I'm having difficulty looping through that list.
Dim duplicate = False
Dim list = Accommodations.GetCommunities
For Each n As String In list.CommunityName
If n = txtCommunities.Text Then
duplicate = True
End If
Next n
While debugging I can hover over Accommodations.GetCommunities and see all of the values I need to loop through under the field "CommunityName" but when I step through the loop, the value for n shows up as a single character. Is there a way to turn this result set into a list so that I can loop through each value under "CommunityName"
I've also tried the below code and it sets com equal to the name of the complex type for some reason, but it properly loops through the correct number of items in the list. How can I extract that field to compare it to the textbox?
Dim duplicate = False
Dim com As String = String.Empty
Dim list = Accommodations.GetCommunities
For i As Integer = 0 To list.count - 1
com = list(i).ToString
If com = txtCommunities.Text Then
duplicate = True
End If
Next
Return duplicate
FINAL EDIT:
This is the function I used after. Used the String.Compare() method to compare the strings to ignore the case as well.
Private Function checkDuplicates()
Dim duplicate = False
Dim list = Accommodations.GetCommunities 'Put items in a list
For i As Integer = 0 To list.count - 1 'loop through the list
If String.Compare(list(i).CommunityName, txtCommunities.Text, True) = 0 Then 'Compare the two strings, comparrison is not case sensitive
duplicate = True 'set dup flag to true
Exit For 'exit loop
End If
Next
Return duplicate
End Function
In the first case you are comparing n to each character in CommunityName. You should do something like:
For Each n As String In list
If n.CommunityName = txtCommunities.Text Then
In the second case list(i) is a community in the list. Thus list(i).ToString() shows the name of the variable.
I think it should look something more like
Dim duplicate = False
Dim list = Accommodations.GetCommunities
For Each community As [something] In list
If community.CommunityName = txtCommunities.Text Then
duplicate = True
End If
Next n
or
Dim duplicate = False
Dim list = Accommodations.GetCommunities
For i As Integer = 0 To list.count - 1
If list(i).CommunityName = txtCommunities.Text Then
duplicate = True
End If
Next
Return duplicate
Side note: Try to use good variable names instead of just n. Also, when you find a duplicate, you can exit for loop or just return True right away.
Sample 1
Ther is also option using dictionary, If u have your list as a dictionary u don't need looping after if u wana check add or remove by unique key value
Public Class TestClass
Property Name As String
End Class
Private Function TestFun() As Boolean
'Sample List to convert use Accommodations.GetCommunities
Dim List As New List(Of TestClass)
List.Add(New TestClass With {.Name = "a"})
List.Add(New TestClass With {.Name = "b"})
'If at Begining all elements by key are unique u can convert to dictionary
'
'From This Point u can map your list in to dictionary Use Accommodations.GetCommunities instant List. and type of what use in this collection replece as TestClass
Dim CheckInDictionary As Dictionary(Of String, TestClass) = List.ToDictionary(Function(p) p.Name, Function(p) p)
' After you can us if some key or Id exist
Return CheckInDictionary.ContainsKey("a")
End Function 'Return True
Sample 2
Ther is Also Another Possibility to Check if List have duplicate, but this is abit more complex.
List have method Contains that check if element exist in said list, so before add new element you can check if list have it. To Compare is used Method Equal so if you overide it in your base class you can do special rules for equal.
Like
Public Class TestClass
Property Name As String
Public Overrides Function Equals(obj As Object) As Boolean
'IMPORTEND Input is as object if anny case will be somthing diffrent then this type it can meak exception
If DirectCast(obj, TestClass).Name = Me.Name Then Return True
Return MyBase.Equals(obj)
End Function
End Class
and after when u atempt to add new element u dolike that
Dim NewEle = New TestClass With {.Name = "a"}
If Not List.Contains(NewEle) Then
List.Add(NewEle)
End If

vba function which returns more than one value and so can be called in sql

I'm new to VBA and i need help.
I want to create vba function which takes table name as input, and distinct specific field from that table. I created function, and it works when i run it in vba immediate window (when i use debug.print command to display results). But when i call this function in sql, instead whole field values, it returns just last one. I'm not good at vba syntax so i need help to understand. Does function can return more than one value? If can, how, and if not what else to use? Here's my code:
Public Function TableInfo(tabela As String)
Dim db As Database
Dim rec As Recordset
Dim polje1 As Field, polje2 As Field
Dim sifMat As Field, pogon As Field, tipVred As Field
Set db = CurrentDb()
Set rec = db.OpenRecordset(tabela)
Set sifMat = rec.Fields("Field1")
Set pogon = rec.Fields("Field2")
Set tipVred = rec.Fields("Field3")
For Each polje1 In rec.Fields
For Each polje2 In rec.Fields
TableInfo = pogon.Value
rec.MoveNext
Next
Next
End Function
Any help is appreciated.
The problem is with this line probably:
TableInfo = pogon.Value
It runs inside the loop and returns the last value of the loop.
Instead of returning one value TableInfo, you may try to return something similar to a Collection or an Array.
Inside the loop, append values in the Collection and after the loop, return the Collection back from the function.
Edit:
I have re-written the code shared by you:
Public Function TableInfo(tabela As String) as String()
Dim db As Database
Dim rec As Recordset
Dim polje1 As Field, polje2 As Field
Dim sifMat As Field, pogon As Field, tipVred As Field
Dim returnValue() As String
Dim i as Integer
Set db = CurrentDb()
Set rec = db.OpenRecordset(tabela)
Set sifMat = rec.Fields("Field1")
Set pogon = rec.Fields("Field2")
Set tipVred = rec.Fields("Field3")
' I am not going to modify this but I think we can do away with two For Each loops.
' Just iterate over rec like
' For Each r In rec -> please use proper naming conventions and best practices
' and access each field as r("Field1") and r("Field2")
For Each polje1 In rec.Fields
For Each polje2 In rec.Fields
returnValue(i) = pogon.Value
i = i + 1
rec.MoveNext
Next
Next
TableInfo = returnValue
End Function
Please note: I have not tested this code but I assume this should work for you. Also, I have assumed that you want to return String() array. Please change the data type if you want to return some other type.
When you call the array (as posted in theghostofc's answer), you will need to do something like this:
Dim TableInfo() As String
For i = LBound(TableInfo) To UBound(TableInfo)
YourValue = TableInfo(i)
... Process some code that uses YourValue
Next i
If you're not looping through your array, you're not going to get each individual value out of it.

DataRow.SetColumnError(Int32, String) ignores Int32 value and always uses zero

Okay, this is doing my head in. I'm calling SetColumnError() on a DataRow object that has 20 columns, but no matter which ColumnIndex I use it sets the error text on column 0. The MSDN documentation makes it plainly clear that the error text is supposed to be set on the column that the ColumnIndex provides.
I'm trying to set error text on columns 1 and 2 (I normally use constants when doing this, I have just used the integers in this example code for simplicity). Why does the error text appear on column 0 and what should I be doing to get the text to show on columns 1 and 2? I'm not receiving IndexOutOfRangeException.
Here is the code I'm having trouble with.
Public Sub ValidateRows()
For Each dgvRow As DataGridViewRow In Me.DataGridView1.Rows
If dgvRow.DataBoundItem IsNot Nothing Then
Dim rowView As DataRowView = dgvRow.DataBoundItem
Dim rowData As MyDataSet.DocumentRow = rowView.Row
rowData.ClearErrors()
If rowData.Revision = rowData.Revision_old Then
rowData.SetColumnError(1, "You must change the revision")
End If
If rowData.InternalRevision = rowData.InternalRevision_old Then
rowData.SetColumnError(2, "You must change the internal revision")
End If
End If
Next
End Sub
I'm not sure, but I think the number of the column must be of the type "DataColumn".