Adding Nodes to Tree via LINQ creates "query operator not supported" during runtime - vb.net

I am trying to get my head around a LINQ issue. The ultimate goal is to load tree view with data acquired from LINQ to SQL (view). The issue is when I try to access the data acquired it keeps throwing a "query operator not supported during runtime". The code involved is no more than:
Dim tasklistdata As New lqDataContext
Dim UserID As Integer
UserID = (From vwUser In tasklistdata.Operators _
Where vwUser.UserName Is Login.txtUsername.Text _
Select vwUser.OperatorID).Single
Dim lqHospitalList = From vwHospitalList In tasklistdata.SPM_Accounts _
Where vwHospitalList.OperatorID = UserID _
Order By vwHospitalList.CustomerName, vwHospitalList.Class _
Select vwHospitalList.CustomerName, vwHospitalList.Class, vwHospitalList.ClassCount, vwHospitalList.Charges
tvHospitalSelect.Nodes.Add(lqHospitalList(0).CustomerName)
if I were to change the code to lqHospitalList.First.CustomerName it works as expected, however changing it to array position is where the issue arises. Please direct me to where my flaw in logic is.

Linq querys are not lists, so they don't have an index. To iterate over all the items you can use a foreach like so:
For Each hospital In lqHospitalList
tvHospitalSelect.Nodes.Add(hospital.CustomerName)
Next
If you want to convert the query to a list:
lqHospitalList.ToList
or array:
lqHospitalList.ToArray

Related

How to Pass Multivalued field in query to VBA function

In my project I have a table1 that has an ID and a text field. table2 has a multi-valued field multifield wherein I can select multiple values from table (via lookup).
Now, I have query1 where I want to pull the value from multifield - not multifield.value and pass it to a VBA function, like this: IDsToNames: table2.multifield. Problem is that VBA gives me this error:
The multi-valued field 'table2.multifield` is not valied in the expression IDsToNames(table2.multifield)
Now, I've tried with IDsToNames([table2].[multifield]) with the same results.
Here is my query's SQL:
SELECT table2.Title, table2.multifield, IDstoNames(table2.multifield) AS FieldNames
FROM table2;
If I remove the IDsToNames function from the SQL, then table2.multifield by itself will return the IDs like: 5, 4, 1 properly. I'm trying to fetch the second column of table1 instead of the first one that includes the IDs. So i figured I'd try passing that field to a function and perform a string split and them look them up with dlookup in a loop. But I can't get the field data into the function.
Is there a way to do this?
Is there a way to pass a multivalued field directly to a VBA function from within an SQL statement? No, regrettably.
However, there are various alternative methods that you can implement to get the list of values stored in the field. It's easy to ramble on about the pros and cons of Access multivalued fields. I'm not going to do that in detail, but it is worth stating that the primary benefit of a multivalue field is the convenience and apparent simplicity of the Access interface being able to automatically list and allow selection of multiple values of a one-to-many relationship. Trying to mimic that behavior in Access can be very awkward and cumbersome (and often impossible). Much of the implementation details for the multivalued fields are "hidden" (i.e. not well documented or are not exposed to the standard SQL or VBA programming interfaces). This includes the ability to pass the mutlivalued field to a VBA function from within an SQL statement. Regardless of how intuitive the intended behavior seems, Access will not simply pass the same concatenated list of values that it displays to another function. But there are still times when one simply wants the list of values, made accessible in a simple list. The information linked to by Gustav is useful and should be well understood for querying multivalued fields, but it still does not address other perfectly reasonable actions required for multiple values. Hopefully the following pointers are useful.
If the values are needed within a standard SQL statement, I suggest passing the primary key value(s) to a VBA function. Then have the VBA function look up the record and retrieve the multivalued-field values using DAO recordsets.
Because this will call the VBA function for every row, this can be (very) slow. It is possible to optimize the function using various techniques, like opening a static recordset object. Further details are beyond the scope of this answer.
Since you're already in code at this point and can structure VBA and queries however you want, the most efficient query will circumvent the multivalued-field itself and use standard SQL joins to get what you need. For instance, if you want to get all of the related user names, then open and enumerate the following recordset to build your list of names:
sSQL = "SELECT table2.key, table2.multifield.value, UserTable.Username " & _
" FROM UserTable INNER JOIN table2 ON UserTable.ID = table2.multifield.Value" & _
" WHERE (table2.key = [KeyParameter])"
Set qry = CurrentDb.CreateQueryDef(, sSQL)
qry.Parameters("KeyParameter") = keyPassedToFunction
Set rs = qry.OpenRecordset
If the SQL query can/will be opened as a DAO recordset in a code context and you still need to retrieve the multivalued-field as a single field, there is a way to enumerate the multivalued-field in VBA.
If the code ends up repeatedly opening and closing multiple recordsets, especially in multiple nested loops, it is likely that you could improve efficiency by restructuring the SQL using appropriate joins and changing the data processing order.
Rant: As you might notice, it is somewhat inconsistent that the underlying recordset object of an SQL statement does indeed return an object which can be enumerated in code, although the Access SQL engine refuses to pass such an object to a VBA function. The SQL engine already deals with boxing and unboxing data into the VBA variant type, so it seems reasonable that when implementing the multivalue fields, they could have had the SQL engine simply box the multivalued recordset object and passed it to a VBA function to be handled similar to the following code... so the original attempt in the question was not unreasonable.
The following code snippet illustrates that the multivalue field is returned as a DAO.Field object containing a DAO.Recordset2 object:
Dim rs as Recordset2
Set rs = CurrentDB.OpenRecordset("SELECT table2.multifield ... FROM ...")
Dim sList As String
sList = ""
If TypeOf rs![multifield] Is Recordset2 Then
Dim rsMVF As Recordset2
Set rsMVF = rs![multifield]
Dim bFirst As Boolean
bFirst = True
rsMVF.MoveFirst
Do Until rsMVF.EOF
If bFirst Then
sList = rsMVF.Fields(0)
bFirst = False
Else
sList = sList & "," & rs.Fields(0)
End If
rsMVF.MoveNext
Loop
'* DO NOT CLOSE the Recordset based on the Multivalue field.
'* Access will close it automatically.
End If
'* sList will contain comma-separated list of values

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

Converting System.Linq.IorderedEnumerable to DataView

I am trying to get the DataView from a linq query expression which is querying a typed dataset. The result lands in a type of System.linq.IOrderedEnumerable. But i'm not able to convert this type to a Dataview although a few examples on the internet say that AsDataView function shoudl work but could you please throw some light on why the method AsDataView is not exposed on the query.
example code:
Dim SortedRates = From rateDetail In ratesDetail _
Select RateName = ("(" & rateDetail.RateType & ") - " & rateDetail.Name), _
RateID = rateDetail.RateID _
Order By RateName Ascending
Dim dv1 As New DataView
dv1 = SortedRates
I cannot do SortedRates.AsDataView and i also cannot directly cast SortedRates to dv1.
Please help.
Thanks.
Khurram.
The AsDataView method only applies to collections of DataRows.
What you're trying to do is impossible because a DataView must wrap a DataTable.
The only way to do this is to create a DataTable from your query and make a DataView for that DataTable.
Why do you need a DataView?

VB Syntax for LINQ query that creates a new{} set?

I am trying to AVERAGE 6 different fields on a DataTable, but without grouping. Just the AVERAGE. I have found a couple of examples in C#, but can't find any examples for how to do this in VB. Can someone please help me convert the syntax?
Here is what I have so far:
Dim query = From dtRow In dtIn.AsEnumerable _
Where dtRow.Field(Of String)("FOLLOWUP") = "Alert" _
Select New With { _
.Brand_Functional_Avg = XXX.Average(Function(f) f("Brand_Functional")), _
.Brand_Personal_Avg = XXX.Average(Function(f) f("Brand_Personal")) _
}
What should I use for XXX? I tried all of the options that I could think of and nothing is compiling.
Trust me, if I could write this in C#, I would, but the project requires VB.
If you're trying to find the average, you shouldn't be using a select to start with - that's going to create a new result for each row, whereas you want just two results for the whole table. I suggest you do something like:
Dim filtered = From dtRow in dtIn.AsEnumerable _
Where dtRow.Field(Of String)("FOLLOWUP") = "Alert"
Dim functionalAvg = filtered.Average(Function(f) f("Brand_Functional"))
Dim personalAvg = filtered.Average(Function(f) f("Brand_Personal"))

Is it possible to change & to & in the results from a LINQ query?

I have the following code:
Dim db As New linqclassesDataContext
Dim categories = (From c In db.faq_cats)
NewFaqDropDownCategory.DataSource = categories
NewFaqDropDownCategory.DataTextField = "category"
NewFaqDropDownCategory.DataValueField = "category_id"
If Not Page.IsPostBack Then
NewFaqDropDownCategory.DataBind()
End If
Unset(categories)
Unset(db)
One of the items under the column "category" has & in the title, and that shows up in the dropdownlist as such. Is there someway to make the list display "&" instead?
One Solution
I figured I could use the .Replace() function to do this, and I accidentally found out how:
For Each c In categories
If c.category.Contains("&") Then
c.category = c.category.Replace("&", "&")
End If
Next
I could expand this in the future to process other values as well.
If there are other HTML encoded characters in there as well you could use HttpUtility.HtmlDecode(c.category). This would prevent your replace and ensure that any characters are properly decoded.
I don't know if this is exact VB linq anonymous object syntax, but I tried.
Dim datasource = From categories In db.faq_cats _
Select New With { .category = HttpUtility.HtmlDecode(categories.category), .category_id = categores.category_id }
Then bind your DDL to that datasource.
You could also just use .Replace if you only need &
Dim datasource = From categories In db.faq_cats _
Select New With { .category = categories.category.Replace("&", "&"), .category_id = categores.category_id }
One of the items under the column "category" has & in the title
If that & is really supposed to be a ‘&’, I suggest your database needs cleaning to make it one. Generally you always want plain text in your database; the HTML-escaping shouldn't happen until the final output-to-page stage.
Storing data in different stages of encoding is confusing and potentially dangerous to work with; get the wrong level of encoding in the other direction and you have cross-site-scripting attacks.
I suggest you do .ToList(), and then have a Select that grabs it doing .Replace()
The reason to do ToList, is to get it as on memory, from then on you get to are no longer constrained to constructs that exists on sql. I don't know the vb.net syntax, but in c# the select would look like:
new {
category = c.category.Replace("&", "&"),
c.category_id
}