Convert For-Each into a LINQ query for adding items in list and check wehter list contains the item already - vb.net

I would like to convert ForEach to LINQ. Currently I'm using these two parts
If TypeOf e.FilterPopup Is RadListFilterPopup Then
Dim ePopup As RadListFilterPopup = DirectCast(e.FilterPopup, RadListFilterPopup)
Dim childList As New List(Of Object)()
For Each row As GridViewRowInfo In Me.grdCNCFilesRad.ChildRows
Dim value = row.Cells(e.Column.Index).Value
If Not childList.Contains(value) Then
childList.Add(value)
End If
Next
Dim newPopup As New RadListFilterPopup(e.Column)
For Each item As System.Collections.ArrayList In ePopup.MenuTreeElement.DistinctListValues.Values
If Not childList.Contains(item(0)) Then
newPopup.MenuTreeElement.DistinctListValues.Remove(item(0).ToString())
End If
Next
e.FilterPopup = newPopup
End If
How can I do the same with a LINQ query?

I don't know what your variable grdCNCFilesRad is type of, but I assume it is no .NET type. But when I read ChildRows then I can be sure that this is some sort of enumeration (somewhere in it's inheritance tree must be the interface IEnumerable).
So you can include System.Linq and apply a AsQueryable() at your ChildRows.
The rest is just a little bit of Linq (Select, Where, ToList()). That's it!
Edit:
The first part should be solved by this:
Dim childList =
Me.grdCNCFilesRad.ChildRows
.AsQueryble()
.Select(Function(row) row.Cells(e.Column.Index).Value)
.Distinct()

There is no need of converting ForEach with Linq if you go for performance issues.
Your existing foreach code looks good.
Note: Don't think Linq is better compared to for-each in performance.

Related

Best way to iterate through Hashtable and conditionally remove entries in VB.NET

In VB.NET, I have a HashTable that I would like to iterate through and conditionally remove entries from. I've written the following code that does the job perfectly, but I'd like to know if there are any creative ways to simplify the code. It just doesn't seem right to have to create a second list to perform this operation.
Here's what I've written:
Dim ModsToRemove As New List(Of String)
For Each ModKey As DictionaryEntry In ModHashTable
If ModKey.Key.ToString.Contains("Criteria") Then
ModsToRemove.Add(ModKey.Key.ToString)
End If
Next
For Each ModKey As String In ModsToRemove
ModHashTable.Remove(ModKey)
Next
Is there another way to perform the same operation that doesn't require the creation of a second list and a second loop? Is it possible to remove entries from something you are iterating through without throwing an error in VB.NET? Is doing so universally a bad idea?
With a little bit of help from Resharper and LINQ, you can simplify your expression in the following ways.
This code block here can be rewritten to use LINQ instead of the embedded IF statement
For Each ModKey As DictionaryEntry In ModHashTable
If ModKey.Key.ToString.Contains("Criteria") Then
ModsToRemove.Add(ModKey.Key.ToString)
End If
Next
Is equivalent to
Dim modsToRemove As List(Of String) = (From modKey As DictionaryEntry In
modHashTable Where modKey.Key.ToString.Contains("Criteria")
Select modKey.Key.ToString).ToList()
Combining this with your actual loop to remove the items from the Hashtable, you should be able to get the equivalent functionality of your example above with the following 3 lines of code:
For Each key As String In (From modkey As DictionaryEntry In modHashTable Where modkey.Key.ToString.Contains("Criteria") Select modkey.Key.ToString).ToList()
modHashTable.Remove(key)
Next

Convert list of objects in object to csv

In vb.net How to convert a list of objects within another List of Objects to csv string.
I tried below didn't work
String.Join(",", Person.Cars.Select(Function(d) d.Colors.Select(Function(o) o.colorid.ToString)))
Output should be colorid in string csv eg: "101,102,103"
If i try for Car ids that works as expected
String.Join(",", Person.Cars.Select(Function(d) d.carid.ToString))
output is "2001,2002,2003"
Here's how data is constructed
Dim objPerson As New Person
With objPerson
.Cars = new Cars (1) {}
.Cars(0) = new Cars
.Cars(1) = new Cars
With Cars(0)
.CarId = "2001"
.Colors = new Colors(1){}
.Colors(0) = new Colors
.Colors(1) = new Colors
With .Colors(0)
.Colorid = "101"
End With
With .Colors(1)
.Colorid = "102"
End With
End With
With Cars(1)
.CarId = "2002"
.Colors = new Colors(1){}
.Colors(0) = new Colors
.Colors(1) = new Colors
With .Colors(0)
.Colorid = "103"
End With
With .Colors(1)
.Colorid = "104"
End With
End With
End With
End With
LINQ can be a very useful tool. It's not that it allows you to do anything that you couldn't do in other ways. It's just that it allows you do some things in a much easier, cleaner, and more readable way. This is not an example of one of those situations. In this case, your use of LINQ is not only likely to be confusing to others, but it's even confusing to you. I'm all for using LINQ in places where it makes the code easier to read, but, since that's it's only real benefit, I see no reason to use it in places where it makes the code harder to read.
Ok, now that I'm off my high-horse, let me explain your problem. You have two LINQ calls nested inside of each other. The inner one returns a list of color ID's for a car. The outer one calls that inner one once for each car in the list. That means that in the end, you don't have a flat list of color ID's for all of the cars. Rather, you have a 2-D list where there is, essentially, one row for each car and one column for each color ID. You need to flatten it into a 1-D list.
You can do it in LINQ with something like this:
String.Join(",", Person.Cars.Select(Function(d) String.Join(",", d.Colors.Select(Function(o) o.colorid.ToString))))
Or by using SelectMany, as recommended by sloth, but I would argue that you are pushing the bounds of readability and should seriously consider trying to bring your cleverness-factor down a notch and just go with a simpler, more readable, For loop. If that makes me sound old-fashioned, so be it. For instance, I would argue that something like this would cause much less head scratching:
Private Function GetAllColorIds(cars As IEnumerable(Of Car)) As IEnumerable(Of Integer)
Dim result As New List(Of Integer)()
For Each i As Car In cars
result.AddRange(i.Colors.Select(Function(x) x.colorid.ToString())
Next
Return result
End Function
' ...
Dim csv As String = String.Join(",", GetAllColorIds(Person.Cars))
Use SelectMany instead of Select to flatten the result that you pass to String.Join:
String.Join(",", Person.Cars.SelectMany(Function(d) d.Colors.Select(Function(o) o.colorid.ToString)))
In response to your comment:
It seems you're looking for something like
String.Join(",", Person.Cars.Select(Function(d) String.Join("", d.Colors.Select(Function(o) o.colorid.ToString))))

CopyToDataTable(Of DataRow) shows syntax error

Im trying to convert the output of a LINQ query to a datatable, I have the following code but it shows a syntax error in the (Of DataRow) part:
Dim X As New Entities
Dim query As IEnumerable(Of DataRow) = From cajero In X.CAJERO.AsEnumerable
Select cajero
Dim bla = query.CopyToDataTable(Of DataRow)()
I'm using this question as a guide:
Filling a DataSet or DataTable from a LINQ query result set
If i use
query.CopyToDataTable()
'instead of the overload
query.CopyToDataTable(Of DataRow)
it throws an invalidCastException.
I'm looking for an easy way to accomplish this task, and this seemed to be the easiest one, without too much code and having to implement any "shredder" methods or such, but if that's the only way, then please point me in the right direction.
This is the error that throws(I localized it to english, its a bit different in spanish):
Cannot convert object of type WhereSelectEnumerableIterator to object of type IEnumerable System.Data.DataRow
I have tried the following:
Declare an extension method that would create the datatable like this:
_
Public Function myToDataTable(Of T)(source As IEnumerable(Of T)) As DataTable
Dim properties As PropertyInfo() = GetType(T).GetProperties()
Dim output As New DataTable()
For Each prop In properties
output.Columns.Add(prop.Name, prop.PropertyType)
Next
For Each item In source
Dim row As DataRow = output.NewRow()
For Each prop In properties
row(prop.Name) = prop.GetValue(item, Nothing)
Next
output.Rows.Add(row)
Next
Return output
End Function
But it always throws an exception while adding the columns to the datatable:
DataSet does not support System.Nullable
I also changed the linq query to this:
Dim query = From cajero In X.CAJERO
Select cajero
Dim bla = query.myToDataTable
Following Jon's suggestion I found this question:
.NET - Convert Generic Collection to DataTable
Which just gave me the last bit of code I needed:
output.Columns.Add(prop.Name, If(Nullable.GetUnderlyingType(prop.PropertyType), prop.PropertyType))
and
row(prop.Name) = If(prop.GetValue(item, Nothing), DBNull.Value)
I believe the problem is that you're trying to convert an IEnumerable<T> of an arbitrary entity type - whereas CopyToDataTable() always requires the input to be a sequence of some kind of DataRow.
Unless your entity type actually derives from DataRow, that's not going to work. You could potentially write a LINQ query which creates a DataRow from each instance, but I believe you'll have to write that code yourself.
I believe in the question you referenced, the OP already had a strongly typed DataSet - at least the answer suggested that's the case.

How to Load a Generic class without loop

ok this is the thing I have right now which is working quite well except its a bit slow:
Public Function GetList() As List(Of SalesOrder)
Try
Dim list As New List(Of SalesOrder)
Dim ds As DataSet
ds = cls.GetSalesOrderList 'CLS is the data access class
For i = 0 To ds.Tables(0).Rows.Count - 1
Dim row As DataRow = ds.Tables(0).Rows(i)
Dim kk As SalesOrder = New SalesOrder()
kk.ID = Val(row.Item("id") & "")
kk.SalesOrderNo = row.Item("salesorderid") & ""
kk.SalesOrderDate = row.Item("OrderDate") & ""
kk.CustomerId = Val(row.Item("customerid") & "")
list.Add(kk)
Next
Return list
Catch ex As Exception
Throw ex
End Try
End Function
Now once I start retrieving more than 10000 records from the table, the loop takes long time to load values into generic class. Is there any way that I can get rid of loop? Can I do something like the following with the generic class?
txtSearch.AutoCompleteCustomSource.AddRange(Array. ConvertAll(Of DataRow, String)(BusinessLogic.ToDataTable.ConvertTo(WorkOr derList).Select(), Function(row As DataRow) row("TradeContactName")))
I would have thought the problem isn't with doing a loop but with volumes of data. Your loop method seems to process each bit of data only once so there isn't any massive efficiency crash (such as looping over the dataset once and then again for each row or that kind of thing). Any method you choose is at the end of the day going to have to loop through all your data.
Their methods might be slightly more efficient than yours but they aren't going to be that much more so I'd think. I'd look at whether you can do some refactoring to reduce your data set (eg limit it to a certain period or similar) or whether you can do whatever searching or aggregating of that list you intend in the database instead of in code. eg if you're just going to sum the values of that list then you can almost certainly do it better by having a stored procedure that will do the summing on the database rather than in the code.
I know this hasn't directly answered your question but this is mainly because I don't know of a more efficient method. I took the question as asking for optimisation in general though rather than how to do this specific one. :)
Converting the loop into some kind of LINQ construct isn't necessarily going to improve performance if you're still enumerating over every row at once. You could return IEnumerable(Of SalesOrder) if you don't need to give the consumer the ability to add/remove from the list (which it looks like might be the case), and then in that case you could create an enumerator to handle this. That way, the dataset is loaded all at once, but the items are only converted into objects when they're being enumerated over, which may be part of your performance hit.
Something like this:
Return ds.Tables(0).Rows.Select(Function(dr As DataRow) Return New SalesOrder ... );
My VB with LINQ is a little rusty, but something to that effect, where the ... is the code to instantiate a new SalesOrder. That will only create a new SalesOrder object as the IEnumerable(Of SalesOrder) is being enumerated over (lazy, if you will).
Hey Paul, You mean something like below code
Dim list As New List(Of SalesOrder)
Dim kk As SalesOrder = New SalesOrder()
Function DrToOrder(dr as datareader)
kk.ID = Val(dr.Item("id") & "")
kk.SalesOrderNo = dr.Item("salesorderid") & ""
list.Add(kk)
End function
Function LoadData()
datareader.Rows.Select(DrToOrder)
End function
Are you talking about something like above code?

Linq to Datarow, Select multiple columns as distinct?

basically i'm trying to reproduce the following mssql query as LINQ
SELECT DISTINCT [TABLENAME], [COLUMNNAME] FROM [DATATABLE]
the closest i've got is
Dim query = (From row As DataRow In ds.Tables("DATATABLE").Rows _
Select row("COLUMNNAME") ,row("TABLENAME").Distinct
when i do the above i get the error
Range variable name can be inferred
only from a simple or qualified name
with no arguments.
i was sort of expecting it to return a collection that i could then iterate through and perform actions for each entry.
maybe a datarow collection?
As a complete LINQ newb, i'm not sure what i'm missing.
i've tried variations on
Select new with { row("COLUMNNAME") ,row("TABLENAME")}
and get:
Anonymous type member name can be
inferred only from a simple or
qualified name with no arguments.
to get around this i've tried
Dim query = From r In ds.Tables("DATATABLE").AsEnumerable _
Select New String(1) {r("TABLENAME"), r("COLUMNNAME")} Distinct
however it doesn't seem to be doing the distinct thing properly.
Also, does anyone know of any good books/resources to get fluent?
You start using LINQ on your datatable objects, you run the query against dt.AsEnumberable, which returns an IEnumerable collection of DataRow objects.
Dim query = From row As DataRow In ds.Tables("DATATABLE").AsEnumerable _
Select row("COLUMNNAME") ,row("TABLENAME")
You might want to say row("COLUMNNAME").ToString(), etc. Query will end up being an IEnumerable of an anonymous type with 2 string properties; is that what you're after? You might need to specify the names of the properties; I don't think the compiler will infer them.
Dim query = From row As DataRow In ds.Tables("DATATABLE").AsEnumerable _
Select .ColumnName = row("COLUMNNAME"), .TableName = row("TABLENAME")
This assumes that in your original sql query, for which you used ADO to get this dataset, you made sure your results were distinct.
Common cause of confusion:
One key is that Linq-to-SQL and (the Linq-to-object activity commonly called) LINQ-to-Dataset are two very different things. In both you'll see LINQ being used, so it often causes confusion.
LINQ-to-Dataset
is:
1 getting your datatable the same old way you always have, with data adapters and connections etc., ending up with the traditional datatable object. And then instead of iterating through the rows as you did before, you're:
2 running linq queries against dt.AsEnumerable, which is an IEnumerable of datarow objects.
Linq-to-dataset is choosing to (A) NOT use Linq-to-SQL but instead use traditional ADO.NET, but then (B) once you have your datatable, using LINQ(-to-object) to retrieve/arrange/filter the data in your datatables, rather than how we've been doing it for 6 years. I do this a lot. I love my regular ado sql (with the tools I've developed), but LINQ is great
LINQ-to-SQL
is a different beast, with vastly different things happening under the hood. In LINQ-To-SQL, you:
1 define a schema that matches your db, using the tools in in Visual Studio, which gives you simple entity objects matching your schema.
2 You write linq queries using the db Context, and get these entities returned as results.
Under the hood, at runtime .NET translates these LINQ queries to SQL and sends them to the DB, and then translates the data return to your entity objects that you defined in your schema.
Other resources:
Well, that's quite a truncated summary. To further understand these two very separate things, check out:
LINQ-to-SQL
LINQ-to-Dataset
A fantastic book on LINQ is LINQ in Action, my Fabrice Marguerie, Steve Eichert and Jim Wooley (Manning). Go get it! Just what you're after. Very good. LINQ is not a flash in the pan, and worth getting a book about. In .NET there's way to much to learn, but time spent mastering LINQ is time well spent.
I think i've figured it out.
Thanks for your help.
Maybe there's an easier way though?
What i've done is
Dim comp As StringArrayComparer = New StringArrayComparer
Dim query = (From r In ds.Tables("DATATABLE").AsEnumerable _
Select New String(1) {r("TABLENAME"), r("COLUMNNAME")}).Distinct(comp)
this returns a new string array (2 elements) running a custom comparer
Public Class StringArrayComparer
Implements IEqualityComparer(Of String())
Public Shadows Function Equals(ByVal x() As String, ByVal y() As String) As Boolean Implements System.Collections.Generic.IEqualityComparer(Of String()).Equals
Dim retVal As Boolean = True
For i As Integer = 0 To x.Length - 1
If x(i) = y(i) And retVal Then
retVal = True
Else
retVal = False
End If
Next
Return retVal
End Function
Public Shadows Function GetHashCode(ByVal obj() As String) As Integer Implements System.Collections.Generic.IEqualityComparer(Of String()).GetHashCode
End Function
End Class
Check out the linq to sql samples:
http://msdn.microsoft.com/en-us/vbasic/bb688085.aspx
Pretty useful to learn SQL. And if you want to practice then use LinqPad
HTH
I had the same question and from various bits I'm learning about LINQ and IEnumerables, the following worked for me:
Dim query = (From row As DataRow In ds.Tables("DATATABLE").Rows _
Select row!COLUMNNAME, row!TABLENAME).Distinct
Strangely using the old VB bang (!) syntax got rid of the "Range variable name..." error BUT the key difference is using the .Distinct method on the query result (IEnumerable) object rather than trying to use the Distinct keyword within the query.
This LINQ query then returns an IEnumerable collection of anonymous type with properties matching the selected columns from the DataRow, so the following code is then accessible:
For Each result In query
Msgbox(result.TABLENAME & "." & result.COLUMNNAME)
Next
Hoping this helps somebody else stumbling across this question...