Search Through A DataTable LINQ - vb.net

I am trying to return a list of data rows where a filed in a datatable matches some criteria. Here is what I have
'Dim returnedList = myDatatable.Where(Function(x) x.Item("TagergetField").ToString = "TheCriteria").ToList()
My Where clause isnt correct and I am not sure how to return a filtered datatable with only the records that match my criteria. I want to return a datatable object not a List

Just try with
datatable.AsEnumerable().Where(Function(x) x("TagergetField").ToString = "TheCriteria").ToList()
this will return a List(Of DataRow)
You could achieve the same result without resorting to Linq
Dim rows = dataTable.Select("TargetField = 'TheCriteria'").ToList()
Note how the last one is more readable and considerably more performant (Just tested)
EDIT
To get a datatable you could use the DataSetExtension namespace method CopyToDataTable
Dim dataTable1 = rows.CopyToDataTable()

Related

How to Select Rows in a Datatable?

How can I use the Variable Itmnmbr instead of hard-coding its value, 'i-2051'?
Dim fr() As DataRow
Dim Itmnmbr As string = "i-2051"
fr = dt.Select("item = 'i-2051'")
The most direct way is to use an Interpolated String, which are available from Visual Studio 2015, VB.Net 14:
Dim Itmnmbr As string = "i-2051"
fr = dt.Select($"item = '{Itmnmbr}'")
As a suggestion, let's change the names of Variables / Fields so it's easier to read them and also understand what these objects are used for. For example:
Dim dt as New DataTable()
'[...]
Dim itemNunmber As string = "i-2051"
Dim filteredRows As DataRow() = dt.Select(...)
itemNunmber is easier to read than Itmnmbr and filteredRows is more explicit than fr. There are some convetions that most are used to, as dt for DataTable, ds for DataSet etc., in this context. Better be sure that when you read your code after some time you don't get mad with yourself :)
Note that an Interpolated String is the same as a string formatted with String.Format(), so these two are actually the same thing:
Dim filteredRows As DataRow() = dt.Select($"item = '{itemNumber}'")
Dim filteredRows As DataRow() = dt.Select(String.Format("item = '{0}'", itemNumber))
Setting Option Infer On (should be On already), to make use of local type inference, you can write:
Dim filteredRows = dt.Select($"item = '{itemNumber}'")
and let the compiler infer the Type. In Visual Studio, if you move the mouse pointer over the variable, it will tell you what Type that is.
You have other options, if you need more dynamic selections.
The DataTableExtensions (which require a Project Reference to the System.Data.DataSetExtensions assembly - usually already linked along with System.Data), let you use the the AsEnumerable() method.
In LINQ to Objects style:
Here, using the default string Comparer
Dim filteredRows =
dt.AsEnumerable().Where(Function(dr) dr("item").ToString().Equals(itemNumber))
Or in LINQ to SQL style:
Here, using the InvariantCulture for the comparison.
Dim filteredRows =
From row In dt.AsEnumerable()
Where row.Field(Of String)("item").Equals(itemNumber, StringComparison.InvariantCulture)
Select row
See also: StringComparison and Best practices for comparing strings in .NET
These two last methods don't return an array of DataRow objects references, but a EnumerableRowCollection. The advantage is (when you can make use of it) that the collection is returned only when you actually use it (the execution is deferred).
When used correctly, it can improve the performance of your code. Try it out.
Instead of DataTable.Select(), you could also filter your DataTable, using its DefaultView.RowFilter property.
dt.DefaultView.RowFilter = $"item = '{itemNumber}'"
' You can save the filter to restore it later, if needed
Dim previousFilter = dt.DefaultView.RowFilter
When you present the Rows of your DataTable, only the Rows that meet the criteria defined by the Filter are shown (e.g., in a DataGrid of sort).
As mentioned, you're working with References here. The Collection of Rows returned by DataTable.Select() contain references of the Rows in the DataTable.
For example, if you consider the Collection and the filtered DataTable:
Dim filteredRows = dt.Select($"item = '{itemNumber}'")
dt.DefaultView.RowFilter = $"item = '{itemNumber}'"
Assume that filteredRows contains a single Row. Then you apply a Filter.
If you now change the value of filteredRows(0)("item"):
filteredRows(0)("item") = "Some other value"
when you present your DataTable in a UI, no Rows will be shown, since the Filter is active and now none of the Rows meet the filter's criteria: setting filteredRows(0)("item") has changed the value of the Row it refers to.
To remove a Filter, set it to string.Empty:
dt.DefaultView.RowFilter = Sting.Empty
To restore the previously saved filter:
dt.DefaultView.RowFilter = previousFilter

LINQ VB.NET Return multiple columns

I have a LINQ statement that will return all the fields in a row. How can I modify this statement to return just two fields, "Address 1" and "Address 2"? (row is a DataRow)
Dim fields As String() = row.ItemArray.Select(Function(field) field.ToString()).ToArray()
DataGridViewRows? I'm not sure I understand your question completely, but I think you're looking for anonymous types.
Ex.:
Dim fields = New With {.Column1 = row.Cells.Item("Column1Name"), _
.Column2 = row.Cells.Item("Column2Name")}
Here's one way of doing it:
Dim fields As String() = (From obj In {row.Item("a1"), row.Item("a2")} Select CStr(obj)).ToArray()

datacolumn select by custom object

How can I select from Datatable in VB.net all rows which contains my custom object? - actually its type is INotifyPropertyChanged.
I consider using datatable.select method with filter expression, however no idea how comparision works with select.
Try using LINQ's Where method, something like,
' this will return a list of DataRow object
Dim rows = datatable.AsEnumerable().Where(Function(x) TypeOf x("mycolumn") Is INotifyPropertyChanged).ToList()
OR
' this will return a new DataTable with your selected rows
Dim dt2 = datatable.AsEnumerable().Where(Function(x) TypeOf x("mycolumn") Is INotifyPropertyChanged).CopyToDataTable()

How do I sort a datatable

How do I sort a datatable? I need to return a datatable from a function. I have been struggling with this for hours, and the internet has a few different answers, none of which seem to work for me.
Edit: I want to punch myself. Do a DataView.Sort on your table, then a DataView.ToTable() to put the sorted data into a new dataset...
Example:
Dim view As New DataView(OriginalDataSet) 'Put your original dataset into a dataview
view.Sort = "ColumnName" ' Sort your data view
Dim NewDataSet As DataTable = view.ToTable() ' Put your dataview into a new datatable
End of example
I have a relatively simple example table below, taken from a teaching website. The one twist is that there are duplicate values in the row I am trying to sort on.
Module Module1
Sub Main()
' Get a DataTable instance from helper function.
Dim table As DataTable = GetTable()
End Sub
''' <summary>
''' Helper function that creates new DataTable.
''' </summary>
Function GetTable() As DataTable
' Create new DataTable instance.
Dim table As New DataTable
' Create four typed columns in the DataTable.
table.Columns.Add("Dosage", GetType(Integer))
table.Columns.Add("Drug", GetType(String))
table.Columns.Add("Patient", GetType(String))
table.Columns.Add("Date", GetType(DateTime))
' Add five rows with those columns filled in the DataTable.
table.Rows.Add(25, "Indocin", "David", DateTime.Now)
table.Rows.Add(50, "Enebrel", "Sam", DateTime.Now)
table.Rows.Add(10, "Hydralazine", "Christoff", DateTime.Now)
table.Rows.Add(21, "Combivent", "Janet", DateTime.Now)
table.Rows.Add(100, "Dilantin", "Melanie", DateTime.Now)
table.Rows.Add(21, "Aspirin", "Janet", DateTime.Now)
Return table
End Function
End Module
I have tried selecting into an array, then looping through the array and putting each row into a new datatable, but the select isn't grabbing rows. Example:
drarray = ds.Select("I want to select all here", "MySortColumn")
I have tried various looping strategies, with inner loops, etc and can't seem to figure that out.
I have tried dataTable.DefaultView.Sort = "sortExp" but I can't get that to work.
So what am I missing? I figure with the DefaultView and Select methods I'm just missing something syntactly.
So what's the best way to go, and what am I missing?
You can use something like this:
Return table.Select("","Columns to sort on").CopyToDataTable
Use a DataView to create a view of your data in the DataTable. This allows you to sort, filter, etc. Here's a C# example: Datatable VS dataview
This may help you sortExp can be field on which based the sort should be performed filterExp that should evaluate to true or false. assuming the following fields
Dim filterExp As String = "Patient<> ''"
Dim sortExp As String = "Date "
dt_item.Select(filterExp, sortExp, DataViewRowState.CurrentRows)
The above code shows how to filter and sort the data table dt_item. The filter expression selects Date whose Patient is not NULL. The sort expression causes the results to be sorted by the Date column

Copy LINQ results to data set/table

I’m working on a form that has a few data grid views that are populated from LINQ queries, no problem there it works as it should however that sorting does not work. After doing some reading its because LINQ results do not support sorting.
As I have the LINQ results already is there a way of copying the results into a dataset or datatable then binding the data grid view to that so sorting will work?
Thanks
EDIT:
Thanks to everyone for the answers, sadly I'm off on holiday for 2 weeks so cant try out and upvote the correct one. However when I return it will be on the top of my list
You can use the CopyToDataTable extension method for this.
The standard implementation of this method only works over IEnumerable<T> where T is of type DataRow but there is an example on MSDN of making your own extension method which works on anonymous types.
I've actually not used CopyToDataTable, in the past I've created a similar end result by creating a BindingList which supports sorting and then creating an instance of it with the query as the IList in the constructor, but the CopyToDataTable approach looks a lot cleaner to me.
In the end I used bits from each option and a little tweak to get it to work with nullable fields.
First thing is getting the CopyToDataTable function into VB, this is done here
http://msdn.microsoft.com/en-us/library/bb669096.aspx
When I tried this it would fail if the column was nullable so I searched and found a mod to that code that would work. Here it is in full
Public Function ExtendTable(ByVal table As DataTable, ByVal type As Type) As DataTable
For Each f As FieldInfo In type.GetFields()
If (Not _ordinalMap.ContainsKey(f.Name)) Then
Dim dc As DataColumn
dc = If(table.Columns.Contains(f.Name), table.Columns(f.Name), table.Columns.Add(f.Name, f.FieldType))
_ordinalMap.Add(f.Name, dc.Ordinal)
End If
Next f
For Each p As PropertyInfo In type.GetProperties()
If Not _ordinalMap.ContainsKey(p.Name) Then
Dim colType As Type = p.PropertyType
If (colType.IsGenericType) AndAlso (colType.GetGenericTypeDefinition() Is GetType(Nullable(Of ))) Then
colType = colType.GetGenericArguments()(0)
End If
Dim dc As DataColumn = IIf(table.Columns.Contains(p.Name), table.Columns(p.Name), table.Columns.Add(p.Name, colType))
_ordinalMap.Add(p.Name, dc.Ordinal)
End If
Next
Return table
End Function
Upvotes all round as they all would have worked I just took this option as it is neater
In C# I do it thus:
DataTable dt = new DataTable();
dt.Columns.Add("a", Type.GetType("System.String"));
dt.Columns.Add("b", Type.GetType("System.String"));
dt.Columns.Add("c", Type.GetType("System.String"));
dt.Columns.Add("d", Type.GetType("System.String"));
foreach (var row in [linqQueryName] )
{
DataRow destRow = dt.NewRow();
destRow["a"] = row.linqCol1;
destRow["b"] = row.linqCol2;
destRow["c"] = row.linqCol3;
destRow["d"] = row.linqCol4;
dt.Rows.Add(destRow);
}
See the code:
Here testData is the data from LINQ query on a list of Class having ID and Name as properties
Dim dataTable As New DataTable()
dataTable.Columns.Add("ID", GetType(Integer))
dataTable.Columns.Add("Name", GetType(String))
For Each item As var In testData
Dim dataRow As DataRow = dataTable.NewRow()
dataRow("ID") = item.ID
dataRow("Name") = item.Name
dataTable.Rows.Add(dataRow)
Next