How to Load a Generic class without loop - vb.net

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?

Related

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

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.

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))))

List contains duplicate Persons

Please see the code below:
Public Function ExecuteDynamicQuery(ByVal strSQL As String, ByVal list As List(Of clsType), ByVal tyType As clsType) As List(Of clsType) Implements IGenie.ExecuteDynamicQuery
Dim objParameterValues As New clsParameterValues
Dim iConnectionBLL As iConnectionBLL
Dim objCon As DbConnection
Dim objDR As DbDataReader
Dim paramValues() As DbParameter
objParameterValues = New clsParameterValues
iConnectionBLL = New clsConnectionBLL()
objCon = iConnectionBLL.getDatabaseTypeByDescription("Genie2")
Using objCon
paramValues = objParameterValues.getParameterValues
objDR = clsDatabaseHelper.ExecuteReader(objCon, CommandType.Text, strSQL, paramValues)
Do While objDR.Read
Dim tyType2 As clsType = tyType
tyType.PopulateDataReader(objDR)
list.Add(tyType2)
Loop
objDR.Close()
Return list
End Using
End Function
An SQL statement is passed to the function along with clsType (the base type). A list of types is returned e.g. a list of Persons. For example, in this case strSQL = "SELECT * FROM Persons". A list of 500 persons is returned but they are all the same person (the last person added to the list). I realise this is because the list is referncing the same object for each entry. How do I change this?
This is a situation where making the method generic would be useful. For instance:
Public Function MyGenericMethod(Of T As New)() As List(Of T)
Dim results As New List(Of T)()
For i As Integer = 0 To 9
Dim item As New T()
' Populate item ...
results.Add(item)
Next
Return results
End Function
For what it's worth, though, I see people trying do this kind of thing often, and it never sits well with me. I'm always the first one in line to suggest that common code should be encapsulated and not duplicated all over the place, but, I've never been convinced that creating some sort of data access layer that encapsulates the calls to ADO, but doesn't also encapsulate the SQL, is a good idea.
Consider for a moment that ADO, is in-and-of-itself an encapsulation of that part of the data-access layer. Sure, it can take a few more lines of code than you might like to execute a simple SQL command, but that extra complexity is there for a reason. It's necessary in order to support all of the features of the data source. If you try to simplify it, inevitably, you will one day need to use some other feature of the data source, but it won't be supported by your simplified interface. In my opinion, each data access method should use all of the necessary ADO objects directly rather than trying to some how create some common methods to do that. Yes, that does mean that many of your data access methods will be very similar in structure, but I think you'll be happier in the long run.
I've reduced your original code. The following sample is functionally equivalent to what you posted. Without knowing more about your types, it will hard to give you anything more than this, but maybe the reduction will make the code clear enough for you to spot a solution:
Public Function ExecuteDynamicQuery(ByVal sql As String, ByVal list As List(Of clsType), ByVal type As clsType) As List(Of clsType) Implements IGenie.ExecuteDynamicQuery
Dim paramValues() As DbParameter = New clsParameterValues().getParameterValues()
Using conn As DbConnection = iConnectionBLL.getDatabaseTypeByDescription("Genie2"), _
rdr As DbDataReader = clsDatabaseHelper.ExecuteReader(conn, CommandType.Text, sql, paramValues)
While rdr.Read()
type.PopulateDataReader(rdr)
list.Add(type)
End While
Return list
End Using
End Function
There are a few additional bits of advice I can give you:
You must have some way to accept parameter information for your query that is separate from the query itself. The ExecuteReader() method that you call supports this, but you only ever pass it an empty array. Fix this, or you will get hacked.
A implementation that uses Generics (as posted in another answer) would be much simpler and cleaner. The Genie interface you're relying doesn't seem to be adding much value. You'll likely do better starting over with a system that understands generics.
The problem of re-using the same object over and over can be fixed by creating a new object inside the loop. As written, the only way to do that is with a New clsType (and it seems you may have Option Strict Off, such that this could blow up on you at run time), through some messy reflection code, a switch to using generics as suggested in #2, or a by accepting a Func(Of clsType) delegate that can build the new object for you.

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...

What Class for Serializable Multidimensional Arrays?

EDIT: See Below
I have a web service which uses a class of functions in order to return data used in various business processes (via InfoPath).
One of the functions takes a given SQLCommand object and executes it into a SQLDataReader. Now depending on the SQL command text used this may return one or many rows of one or many columns. So what is the best class for this function to return bearing in mind it needs to be serialized by the web service.
My existing code is:
Dim array As New ArrayList
Try
conCMS.Open()
Dim rdr As SqlDataReader = cmdCurrent.ExecuteReader
While rdr.Read
If rdr.VisibleFieldCount > 1 Then
Dim complexType(rdr.VisibleFieldCount - 1) As String
For rowIndex As Integer = 0 To rdr.VisibleFieldCount - 1
complexType(rowIndex) = rdr(rowIndex)
Next
array.Add(complexType)
Else
array.Add(rdr(0))
End If
End While
conCMS.Close()
Return array
Catch ex As Exception
array.Add("ERROR " & ex.Message)
End Try
Return Nothing
Now I know this is not efficient code, but this a work in progress.
As you can probably see this is generating a string array to represent a row with more than one column, however this cannot be serialized by the web service.
So 2 things really;
Some guidance on an effective type to use (without writing a serializable class of my own)
Some advice on improving the code going forward.
Thanks in advance
EDIT:
I have managed to get serialization to work by simply creating a nested arrayList as follows (doh!):
If rdr.VisibleFieldCount > 1 Then
Dim complexType As New ArrayList
For rowIndex As Integer = 0 To rdr.VisibleFieldCount - 1
complexType.Add(rdr(rowIndex))
Next
array.Add(complexType)
Else
array.Add(rdr(0))
End If
However please let me know how this could be improved.
If your code knows the schema of the returned data before the call, then you should return data in the same shape. Have a struct or class with properties of the appropriate type for each column of the returned data. For each row, create an instance of such a struct, and fill in the properties from the returned columns. Then add each instance to a strongly-typed list of that struct, a List(Of T). Then return the list.
OBTW, ArrayList was created before we had generics in .NET. Today, it's better to use strongly-typed collections and not ArrayList, which is basically List(Of anything).