Linq to Datarow, Select multiple columns as distinct? - vb.net

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

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.

Range variable name cannot match the name of a member of the 'Object' class. when querying a DataGridView using Linq

I am trying to Query a DataGridViewRowsCollection object using LINQ so I don't need a for loop. I want to get the first cells out what is a String and put it in a generic list.
Based on my knowledge and research the query should be the following:
Dim Result As List(Of String) = (From row In gridMappingClasses.Rows.Cast(Of DataGridViewRow)()
Select row.Cells(0).Value.ToString).ToList()
but it fails with the following error: Range variable name cannot match the name of a member of the 'Object' class.
But if I remove the ToString method call and change Result to List(of object) it works fine. Btw I am using Strict on.
Can anyone help?
Try giving it an alias (ToString is already a member of Object, can't have another one):
(From row In gridMappingClasses.Rows.Cast(Of DataGridViewRow)()
Select v = row.Cells(0).Value.ToString).toList
But there is nothing wrong with having a for loop. You should generally use LINQ for simple queries that don't cause any debugging headache. If a LINQ query starts to become problematic, it's time to rewrite it as a loop. In your case it would have resolved the problem.

Lambda expression creates multiple queries

I have a strange LINQ issue of system.function. If my Lambda expression contains the datacontext twice it will generate multiple SQL-queries instead of (see second one) one SQL-query.
First
Dim whereFunction As Func(Of tasks, Boolean) = Function(task) New With {.condition =
(From i In myDataContext.taskInfo Where i.taskId = task.id).Any()
}.condition
Dim tasksLambda = myDataContext.tasks.Where(whereFunction)
Dim taskList = tasksLambda.ToList
'generates for each task one select to taskinfo on SQL Server Profiler -> bad
Second
Dim tasksNoLambda = (From task In myDataContext.tasks Where (From i In myDataContext.taskInfo Where i.taskId = task.id).Any())
taskList = tasksNoLambda .ToList
'generates only one select to tasks with subselect to taskinfo on SQL Server Profiler -> good
Can you explain WHY it makes multiple queries instead of subqueries and/or what's the difference between these two Lambda-queries.
Thanks in advance,
Daniel
myDataContext.tasks refers to a collection provided by the data source
.Where(whereFunction) is an expression that must be evaluated in the client because whereFunction can't be generally converted to an SQL expression (it is constructing new anonymous objects and I believe there's no SQL conversion for that).
myDataContext.taskInfo within the function refers back to the data source again.
Therefore there needs to be a lot of back-and-forth communication in the evaluation of the highest level expression, retrieving data from the data source, processing it in a function on the client, which in turn must retrieve more data from the data source.
Could you try the same thing without the anonymous object? Could you try this?
Dim whereFunction As Func(Of tasks, Boolean) = _
Function(task) (From i In myDataContext.taskInfo Where i.taskId = task.id).Any())
Got it. Second attempt -- try this one:
Dim whereFunction As System.Linq.Expressions.Expression(Of Func(Of tasks, Boolean)) = _
Function(task) (From i In myDataContext.taskInfo Where i.taskId = task.id).Any())
By representing the variable as a LINQ "Expression" I think it improves LINQ's ability to convert it to other forms such as SQL.
Try doing this:
Dim tasksLambda = _
From task In myDataContext.tasks
Group Join i In myDataContext.taskInfo On task.id Equals i.taskId Into Group
Where Group.Any()
Select task
This would be the best you can do to make it one query.
If that doesn't work then you can pull the tasks and taskInfo records into memory and do the filtering there. As long as you don't have too many records this can often be faster anyway.

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.

What does the "New ... With" syntax do in VB Linq?

What (if any) is the difference between the results of the following two versions of this VB Linq query?
' assume we have an XElement containing employee details defined somewhere else
Dim ee = From e In someXML.<Employee> _
Select New With {.Surname = e.<Surname>, .Forename = e.<Forename>}
and
Dim ee = From e In someXML.<Employee> _
Select Surname = .Surname = e.<Surname>, .Forename = e.<Forename>
ie what is the point of the New ... With syntax?
I suspect that this has a simple answer, but I can't find it - any links to suitable tutorials or Microsoft documentation would be appreciated.
The difference is that the 1st explicitly creates an anonymous type. The 2nd is a query expression, and may use an existing type rather than creating an anonymous type. From the documentation linked by Cameron MacFarland:
Query expressions do not always require the creation of anonymous types. When possible, they use an existing type to hold the column data. This occurs when the query returns either whole records from the data source, or only one field from each record.
My understanding is that there is no difference.
New With is aimed to out-of-query usage like
Dim X = New With { .Surname = "A", .Forename = "B" }
Specifically for Linq queries, you can skip New With, but it is still useful for other situations. I am not sure, however, since I do not know VB 9 :)
There is no functional difference between the two pieces of code you listed. Under the hood both pieces code will use an anonymous type to return the data from the query.
The first piece of code merely makes the use of an anonymous type explicit. The reason this syntax is allowed is that it's possible to return any type from a Select clause. But the type must be used explicitly.
Dim x = From it in SomeCollection Select New Student With { .Name = it.Name }
Joel is incorrect in his statement that the second query may use an existing type. Without an explicit type, a select clause which uses an explicit property name will always return an anonymous type.
They're called Anonymous Types.
The main reason for their use is to keep the data from a query in a single object, so the iterators can continue to iterate over a list of objects.
They tend to work as temporary types for storage in the middle of a large or multi-part LINQ query.
There is no difference. The compiler will infer the anonymous type.
You most likely want to return the Value of the elements as in e.<Surname>.Value, which returns a String instead of an XElement.
Your 2nd example could be simplified as
Dim ee = From e In someXML.<Employee> _
Select e.<Surname>.Value, e.<Forename>.Value
because the compiler will also infer the names of the members of the anonymous type.
However, if you have the following class
Class Employee
Private _surname As String
Public Property Surname() As String
Get
Return _surname
End Get
Set(ByVal value As String)
_surname = value
End Set
End Property
Private _forename As String
Public Property Forename() As String
Get
Return _forename
End Get
Set(ByVal value As String)
_forename = value
End Set
End Property
End Class
Then you could change the 1st query to produce an IQueryable(Of Employee) instead of the anonymous type by using New ... With like so:
Dim ee = From e In someXML.<Employee> _
Select New Employee With {.Surname = e.<Surname>.Value, _
.Forename = e.<Forename>.Value}
One difference is that Anonymous types aren't serializable.