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

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.

Related

VB.Net - Can you access the expected data type within a function?

I was wondering if there is any way to access the expected data type within a function similar to an event arg. I am doubtful that this is possible, though it would be an excellent feature.
I frequently work with (old and disorganized)Mysql databases creating interfaces through VB.Net. Often I will have an optional field which contains a NULL value in the database. I am frequently dealing with errors due to NULL and dbnull values in passing data to and from the database.
To complicate things, I often am dealing with unexpected datatypes. I might have an integer zero, a double zero, an empty string, or a string zero.
So I spend a fair amount of code checking that each entry is of the expected type and or converting NULLs to zeros or empty strings depending on the case. I have written a function ncc(null catch convert) to speed up this process.
Public Function ncc(obj As Object, tp As Type) As Object 'Null Catch Convert Function...
My function works great, but I have to manually set the type every time I call the function. It would be so much easier if it were possible to access the expected type of the expression. Here is an example of what I mean.
Dim table as datatable
adapter.fill(table)
dim strinfo as string
dim intinfo as long
strinfo = ncc(table.Rows(0).Item(0),gettype(String)) 'here a string is expected
intinfo = ncc(table.Rows(0).Item(0),gettype(Long)) 'here a long is expected
It would be so much more efficient if it were possible to access the expected type directly from the function.
Something like this would be great:
Public Function ncc(obj As Object, optional tp As Type = nothing) As Object
If tp Is Nothing Then tp = gettype(ncc.expectedtype)
That way I do not have to hard code the type on each line.
strinfo = ncc(table.Rows(0).Item(0))
You can make the ncc function generic to simplify calling it:
Public Function ncc(Of T)(obj As T) As T
If DbNull.Value.Equals(obj) Then Return Nothing
Return Obj
End Function
This kind of function will be able to in some cases infer the type, but if there's any possibility of null you'll still want to include a type name (because DBNull will be the inferred type for those values). The advantage is not needing to call gettype() and so gaining a small degree of type safety:
strinfo = ncc(Of String)(table.Rows(0).Item(0))
But I think this has a small chance to blow up at run time if your argument is not implicitly convertible to the desired type. What you should be doing is adding functions to accept a full row and return a composed type. These functions can exist as static/shared members of the target type:
Shared Function FromDataRow(IDataRow row) As MyObject
And you call it for each row like this:
Dim record As MyObject = MyObject.FromDataRow(table.Rows(i))
But, you problem still exists.
What happens if the column in the database row is null?
then you DO NOT get a data type!
Worse yet? Assume the data column is null, do you want to return null into that variable anyway?
Why not specify a value FOR WHEN its null.
You can use "gettype" on the passed value, but if the data base column is null, then you can't determine the type, and you right back to having to type out the type you want as the 2nd parameter.
You could however, adopt a nz() function (like in VBA/Access).
So, this might be better:
Public Function ncc(obj As Object, Optional nullv As Object = Nothing) As Object
If obj Is Nothing OrElse IsDBNull(obj) Then
Return nullv
End If
Return obj
End Function
So, I don't care if the database column is null, or a number, for such numbers, I want 0.
So
dim MyInt as integer
Dim MyDouble As Double
MyInt = ncc(rstData.Rows(0).Item("ContactID"), 0)
MyDouble = ncc(rstData.Rows(0).Item("ContactID"), 0)
dim strAddress as string = ""
strAddress = ncc(rstData.Rows(0).Item("Address"), "")
Since in NEAR ALL cases, you need to deal with the null from the DB, then above not only works for all data types, but also gets you on the fly conversion.
I mean, you CAN declare variables such as integer to allow null values.
eg:
dim myIntValue as integer?
But, I not sure above would create more problems than it solves.
So,
You can't get exactly what you want, because a function never has knowledge of how it's going to be used. It's not guaranteed that it will be on the right-hand side of an assignment statement.
If you want to have knowledge of both sides, you either need to be assigning to a custom type (so that you can overload the assignment operator) or you need to use a Sub instead of an assignment.
You could do something like this (untested):
Public Sub Assign(Of T)(ByVal field As Object, ByRef destination As T,
Optional ByVal nullDefault As T = Nothing)
If TypeOf field Is DBNull Then
destination = nullDefault
Else
destination = CType(field, T)
End If
End Sub
I haven't tested this, so I'm not completely certain that the compiler would allow the conversion, but I think it would because field is type Object. Note that this would yield a runtime error if field is not convertible to T.
You could even consider putting on a constraint requiring T to be a value type, though I don't think that would be likely to work because you probably need to handling String which is a reference type (even though it basically acts like a value type).
Because the destination is an argument, you wouldn't ever need to specify the generic type argument, it would be inferred.

How can I dynamically select my Table at runtime with Dynamic LINQ

I'm developing an application to allow engineers to conduct simple single table/view queries against our databases by selecting Database, Table, Fields.
I get how to use the Dynamic LINQ Library Sample to provide for dynamically selecting the Select, Where and Order by Clauses at runtime but I'm at an impass on how to allot for table choice.
Is there a way to provide for dynamically selecting the "from" table at run time, and if how could you provide some concrete example or point me in the direction of someone who has?
Thank You ever so much.
EDIT
So Both of the answers seem to be saying the same general Idea. I'm going to try to convert the C# into VB and get it to work.
The first answer converts to
NotInheritable Class DataContextExtensions
Private Sub New()
End Sub
<System.Runtime.CompilerServices.Extension> _
Public Shared Function GetTableByName(context As DataContext, tableName As String) As ITable
If context Is Nothing Then
Throw New ArgumentNullException("context")
End If
If tableName Is Nothing Then
Throw New ArgumentNullException("tableName")
End If
Return DirectCast(context.[GetType]().GetProperty(tableName).GetValue(context, Nothing), ITable)
End Function
End Class
but it's tossing me an Error stating that Extension methods can be defined only in modules.
But when I wrap it in module tags it still gives the same error.
So I got it to compile by wrapping it in Module Tags and stripping the class tags. Also I can pull the last line out of it and shove that directly into my base method which allows my to execute it but, it seems to be coming back empty. When I try to enumerate the results there are none. Not sure if this is my codes problem or the new codes issue, I'll test more.
Here's my conversion of the second Example, Now I'm off to try to see if I can get them to work. I'll be back with questions or results after some testing.
'get the table from a type (which corresponds to a table in your context)
Dim dataContextNamespace = "My.DataContext.Namespace"
Dim type = Type.[GetType](dataContextNamespace + tableName)
Dim table = dc.GetTable(type
'add where clauses from a list of them
For Each whereClause As String In whereClauses
table = table.Where(whereClause)
Next
'generate the select clause from a list of columns
Dim query = table.[Select]([String].Format("new({0})"), [String].Join(",", selectColumns))
Thanks for the help. BBL
See Get table-data from table-name in LINQ DataContext.
This is probably actually better done by using direct SQL statements. LINQ will just get in your way.
VB Conversion:
NotInheritable Class DataContextExtensions
Private Sub New()
End Sub
<System.Runtime.CompilerServices.Extension> _
Public Shared Function GetTableByName(context As DataContext, tableName As String) As ITable
If context Is Nothing Then
Throw New ArgumentNullException("context")
End If
If tableName Is Nothing Then
Throw New ArgumentNullException("tableName")
End If
Return DirectCast(context.[GetType]().GetProperty(tableName).GetValue(context, Nothing), ITable)
End Function
End Class
Usage:
Dim myDataContext as New MyCustomDataContext
myDataContext.GetTableByName("ORDERS").Where("...")
You can use GetTable() to get the corresponding ITable of your data. Then coupled with using DLINQ, making it relatively easy.
This example uses the AdventureWorks database. My project has the context defined in the DatabaseTest assembly in the DatabaseTest.AdventureWorks namespace.
'' need my database and DLINQ extensions up top
Imports DatabaseTest.AdventureWorks
Imports System.Linq.Dynamic
'' sample inputs
Dim dc = New AdventureWorksDataContext()
Dim tableName = "Contact"
Dim whereClauses() = {"FirstName = ""John"" OR LastName = ""Smith"""}
Dim selectColumns() = {"FirstName", "LastName"}
'' get the table from a type (which corresponds to a table in your database)
Dim typeName = "DatabaseTest.AdventureWorks." & tableName & ", DatabaseTest"
Dim entityType = Type.GetType(typeName)
Dim table = dc.GetTable(entityType)
Dim query As IQueryable = table
'' add where clauses from a list of them
For Each whereClause As String In whereClauses
query = query.Where(whereClause)
Next
'' generate the select clause from a list of columns
query = query.Select(String.Format("new({0})", String.Join(",", selectColumns)))
In retrospect, using reflection might have been the easier way to get the table since you have the name already. But then the names might not have a 1-to-1 correspondence so you'll have to compensate for it.
Dim table As ITable = dc.GetType().GetProperty(tableName & "s").GetValue(dc, Nothing)

UPDATE statement in Oracle

We are building a client program where parameters for storage in a web server with Oracle backend are set in the .Net client program and uploaded as a dataset via webservice.
In the webservice code, data is read from the dataset and added to UPDATE statements on the web server (Oracle backend).
Because the server will run on the customer's LAN behind a firewall and because of the dynamic nature of the parameters involved, no sprocs are being used - SQL strings are built in the logic.
Here is an example string:
UPDATE WorkOrders
SET TravelTimeHours = :TravelTimeHours,
TravelTimeMinutes = :TravelTimeMinutes,
WorkTimeHours = :WorkTimeHours,
WorkTimeMinutes = :WorkTimeMinutes,
CompletedPersonID = :CompletedPersonID,
CompletedPersonName = :CompletedPersonName,
CompleteDate = :CompleteDate
WHERE WorkOrderNumber = :WorkOrderNumber
When debugging code in VS 2010 and stepping into the server code, we receive the following error:
ORA-01036: illegal variable name/number
when executing the SQL command on destination oracle machine, we were prompted to enter the bind
variables for the above statement, and as long as we used the correct date format, the UPDATE statement
worked correctly.
QUESTIONS:
1) is it possible that oracle threw the ORA-01036 error when the month format was wrong?
2) why don't we have to convert the date format from the ASP.net website running on the Oracle machine?
does Oracle have a default conversion routine that excludes the bind variable entry screen?
3) if the date format was not the problem, what precisely does ORA-1036 mean and how do I discover
WHICH variable had an illegal name/number?
This is a snippet of a function that takes the type of the dataset (WOName) and returns the appropriate SQL string.
Many Cases exist but have been removed for readability.
Private Function GetMainSQLString(ByVal WOName As String) As String
Dim Result As String = ""
Select Case WOName
Case "Monthly Site Inspection"
Dim sb As New StringBuilder
sb.Append("UPDATE WorkOrders SET ")
sb.Append("CompletedPersonID = :CompletedPersonID, CompletedPersonName = :CompletedPersonName, CompleteDate = :CompleteDate, ")
sb.Append("SupervisorID = :SupervisorID, SupervisorName = :SupervisorName ")
sb.Append("WHERE WorkOrderNumber = :WorkOrderNumber")
Result = sb.ToString
End Select
Return Result
End Function
This is a snippet of a function that takes the Oracle command object byRef and adds the required parameters to it,
depending upon which of the possible 15 types of dataset(WOName) is received from the client program.
Many Cases exist but have been removed for readability.
The updated Cmd object is then returned to the main program logic, where ExecuteNonQuery() is called.
The test values of params below are as follows:
dr.Item("CompletedPersonID") 21
dr.Item("CompletedPersonName") Pers Name
dr.Item("CompleteDate") #8/16/2010#
dr.Item("SupervisorID") 24
dr.Item("SupervisorName") Sup Name
dr.Item("WorkOrderNumber") 100816101830
Private Function addMainCmdParams(ByVal WOName As String, ByRef cmd As OracleCommand, ByVal dr As DataRow) As OracleCommand
Select Case WOName
Case "Monthly Site Inspection"
cmd.Parameters.Add(":CompletedPersonID", Oracle.DataAccess.Client.OracleDbType.Int32).Value = dr.Item("CompletedPersonID")
cmd.Parameters.Add(":CompletedPersonName", Oracle.DataAccess.Client.OracleDbType.Varchar2).Value = dr.Item("CompletedPersonName")
cmd.Parameters.Add(":CompleteDate", Oracle.DataAccess.Client.OracleDbType.Date).Value = dr.Item("CompleteDate")
cmd.Parameters.Add(":SupervisorID", Oracle.DataAccess.Client.OracleDbType.Int32).Value = dr.Item("SupervisorID")
cmd.Parameters.Add(":SupervisorName", Oracle.DataAccess.Client.OracleDbType.Varchar2).Value = dr.Item("SupervisorName")
cmd.Parameters.Add(":WorkOrderNumber", Oracle.DataAccess.Client.OracleDbType.Varchar2).Value = dr.Item("WorkOrderNumber")
End Select
Return cmd
End Function
While running this today, this precise code WAS successful; but another similar case was not. I still distrust any implicit typecasting performed by Oracle (if any) - and I'm especially suspicious of how Oracle handles any of these parameters that are passed with a dbNull.value - and I know it's going to happen. so if that's the problem I'll have to work around it. There are too many optional parameters and columns that don't always get values passed in for this system to break on nulls.
One Oracle "gotcha" that can cause this error is the fact that, by default, Oracle maps parameters to parameter symbols in the query by sequence, not by name. If the number/type of parameters does not match, you get an error like this one.
The solution is to tell Oracle to bind by name:
cmd.BindByName = true
Without diving into the details of your code, this may or may not be the answer to your specific problem, but this setting should be the default, and should be part of any command setup that uses parameters. It's rather amazing to watch this one statement fix some obscure problems.
EDIT: This assumes that you're using Oracle's data access provider. In .NET, you should be using this, not Microsoft's Oracle provider.
The error has nothing to do with date formats, it means that a variable in the statement was not bound.
Could be as simple as a spelling mistake (would be nice if Oracle included the variable name in the error message).
Can you update your question with the surrounding code that creates, binds, and executes the statement?
This is a snippet of a function that takes the type of the dataset (WOName) and returns the appropriate SQL string.
Many Cases exist but have been removed for readability.
Private Function GetMainSQLString(ByVal WOName As String) As String
Dim Result As String = ""
Select Case WOName
Case "Monthly Site Inspection"
Dim sb As New StringBuilder
sb.Append("UPDATE WorkOrders SET ")
sb.Append("CompletedPersonID = :CompletedPersonID, CompletedPersonName = :CompletedPersonName, CompleteDate = :CompleteDate, ")
sb.Append("SupervisorID = :SupervisorID, SupervisorName = :SupervisorName ")
sb.Append("WHERE WorkOrderNumber = :WorkOrderNumber")
Result = sb.ToString
End Select
Return Result
End Function
This is a snippet of a function that takes the Oracle command object byRef and adds the required parameters to it,
depending upon which of the possible 15 types of dataset(WOName) is received from the client program.
Many Cases exist but have been removed for readability.
The updated Cmd object is then returned to the main program logic, where ExecuteNonQuery() is called.
The test values of params below are as follows:
dr.Item("CompletedPersonID") 21
dr.Item("CompletedPersonName") Pers Name
dr.Item("CompleteDate") #8/16/2010#
dr.Item("SupervisorID") 24
dr.Item("SupervisorName") Sup Name
dr.Item("WorkOrderNumber") 100816101830
Private Function addMainCmdParams(ByVal WOName As String, ByRef cmd As OracleCommand, ByVal dr As DataRow) As OracleCommand
Select Case WOName
Case "Monthly Site Inspection"
cmd.Parameters.Add(":CompletedPersonID", Oracle.DataAccess.Client.OracleDbType.Int32).Value = dr.Item("CompletedPersonID")
cmd.Parameters.Add(":CompletedPersonName", Oracle.DataAccess.Client.OracleDbType.Varchar2).Value = dr.Item("CompletedPersonName")
cmd.Parameters.Add(":CompleteDate", Oracle.DataAccess.Client.OracleDbType.Date).Value = dr.Item("CompleteDate")
cmd.Parameters.Add(":SupervisorID", Oracle.DataAccess.Client.OracleDbType.Int32).Value = dr.Item("SupervisorID")
cmd.Parameters.Add(":SupervisorName", Oracle.DataAccess.Client.OracleDbType.Varchar2).Value = dr.Item("SupervisorName")
cmd.Parameters.Add(":WorkOrderNumber", Oracle.DataAccess.Client.OracleDbType.Varchar2).Value = dr.Item("WorkOrderNumber")
End Select
Return cmd
End Function
While running this today, this precise code WAS successful; but another similar case was not. I still distrust any implicit typecasting performed by Oracle (if any) - and I'm especially suspicious of how Oracle handles any of these parameters that are passed with a dbNull.value - and I know it's going to happen. so if that's the problem I'll have to work around it. There are too many optional parameters and columns that don't always get values passed in for this system to break on nulls.

VB.NET: Assign value to variable inside an IF condition?

is there any possibility to assign a value to a variable inside an IF condition in VB.NET?
Something like that:
Dim customer As Customer = Nothing
If IsNothing(customer = GetCustomer(id)) Then
Return False
End If
Thank you
Sorry, no. On the other hand, it would get really confusing, since VB.NET uses the same operator for both assignment and equality.
If a = b Then 'wait, is this an assignment or comparison?!
Instead, just set the variable and compare:
Dim customer As Customer = Nothing
customer = GetCustomer(id)
If IsNothing(customer) Then
Return False
End If
No, I'm fairly sure this is not possible - but be thankful!
This is a "feature" of C-based languages that I would never encourage the use of, because it is probably misused or misinterpreted more often than not.
I think the best approach is to try and express "one thought per line", and resist coding that combines two operations in the same line. Combining an assignment and a comparison in this way usually doesn't achieve much other than making the code more difficult to understand.
VB doesn't do that very well, especially since assignment and comparison both use the = operator. It'd get really confusing. And since VB to some degree is designed to be newbie-friendly (The B in "BASIC" actually stands for "Beginner's"), expressions with multiple side effects are not something the language generally likes to do.
If it's essential that you be able to do it all in one line, then you could make the GetCustomer method assign to a ByRef parameter, and return a boolean saying whether the assigned value was null. But really, it's better to just assign and then compare to null.
There is no builtin support for doing this, but you could use this workaround.
Public Function Assign(Of T)(ByRef destination As T, ByVal value As T) As T
destination = value
Return destination
End Function
And then it could be used like this.
Dim customer As Customer = Nothing
If IsNothing(Assign(customer, GetCustomer(id))) Then
Return False
End If
Thinking out loud because you didn't show Customer or GetCustomer...
Dim myCust As New Customer
If myCust.GetCustomer(someID) Then
End If
Class Customer
Private _customerID As Integer
Const noCustomer As Integer = -1
Public Sub New()
Me._customerID = noCustomer 'no customer
End Sub
Public Function GetCustomer(ByVal id As Integer) As Boolean
'perform required action to get customer
'if successful then set Me._customerID to ID else set it to no customer value
Return Me.HaveCustomer
End Function
Public Function HaveCustomer() As Boolean
If Me._customerID = noCustomer Then Return False Else Return True
End Function
End Class
Yes, it is possible to assign a value to a variable inside an IF condition in VB.NET.
There is even builtin support for doing this, albeit to a limited extend:
If Interlocked.Exchange(customer, GetCustomer(id)) Is Nothing Then
Return False
End If
where the methods within the Interlocked class are intended to be used within a multi-threading environment. By design, the return value of Exchange() is the old value, rather than the new one. Thus, to obtain the rather cryptic equivalent result:
If (customer = Interlocked.Exchange(customer, GetCustomer(id)) And customer Is Nothing Then
return false
End If
All of the people stating that this is an issue as VB uses the "same operator for both assignment and equality"..
I would argue that this is not a good reason as they could simply make slightly different syntax for it... for example:
Dim[Name = Expression]
If a = Dim[qwe = 1 + 2] Then
'qwe = 3
End If
'...
a = 3
If Dim[qwe = a = 1 + 2] Then
'qwe = True
End If

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