Where clause in LINQ query is not recognized in vb.net? - vb.net

The entity field is not recognized in the following Where clause. Is the VB wrong?
Dim projects = context.projects
.OrderBy(Function(x) x.name)
.Select(Function(x) {x.id, x.name})
.Where(Function(x) x.id <> sourceid)
If I take the Where off, it works fine. Also, if I flip the Where and the OrderBy, Where is fine, but now OrderBy fails.

Try this:
Dim projects = context.projects
.OrderBy(Function(x) x.name)
.Select(Function(x) New With {x.id, x.name})
.Where(Function(x) x.id <> sourceid)
The New With keyword should create an IEnumerable of anonymous type. You can then work with the id property in the Where clause without having to change the order of your operations.
There is nothing stopping you from doing the operations OrderBy, Select, Where in that order. The above code will certainly compile and run. However, logically you need to do the Where before Select, since the former is a filtering operation, while the latter is a projection.

Can you please try with the below code snippet.
Dim projects = context.projects.Where(Function(x) x.id <> sourceid).OrderBy(Function(x) x.name).Select(Function(x) {x.id, x.name})

This {x.id, x.name} is most likely an array of object (assuming id is integer and name is string, VB would infer Object). It is not an instance of a class with properties of id and name. #shree.pat18 explained how it can be adjusted to return what you want, but I would suggest using query syntax for clarity, and also putting your where clause before Select (should be slightly faster, because it does not create anonymous objects from the values you don't need included in results):
Dim projects = From p In context.projects
OrderBy p.name
Where p.Id <> sourceid
Select Id = p.Id, Name = p.Name

Related

Trying to get a dynamic order by clause in Linq to Entities

I am trying to get a dynamic order by clause working but can't figure out the syntax. Thought I'd cracked it with inline if statements but these aren't supported in EF.
The user will be able to select a field to sort on (initially ascending) probably by clicking on the dgv header.
This is my code: -
Dim q = (From customer In db.tblcustomers.Where(Function(x) CBool(x.f_rn >= 0 And x.cu_brn = "00" _
And (txtFiltAccno.Text.Trim = "" Or x.cu_accno.ToUpper.Contains(txtFiltAccno.Text.ToUpper.Trim)) _
And (txtFiltCustName.Text.Trim = "" Or x.cu_name.ToUpper.Contains(txtFiltCustName.Text.ToUpper.Trim)) _
And (txtFiltPhone.Text.Trim = "" Or x.cu_telno.ToUpper.Contains(txtFiltPhone.Text.ToUpper.Trim)) _
And (txtFiltFax.Text.Trim = "" Or x.cu_faxno.ToUpper.Contains(txtFiltFax.Text.ToUpper.Trim)) _
And (txtFiltEmail.Text.Trim = "" Or x.cu_email.ToUpper.Contains(txtFiltEmail.Text.ToUpper.Trim))))
select customer.f_rn, customer.cu_accno, customer.cu_name, customer.cu_add1, customer.cu_add2, customer.cu_add3, customer.cu_add4, customer.cu_add5,
customer.cu_telno, customer.cu_faxno, customer.cu_email).OrderBy(Function(u)
IIf(a = "cu_name", u.cu_name,
IIf(a = "cu_add1", u.cu_add1,
IIf(a = "cu_add2", u.cu_add2,
IIf(a = "cu_add3", u.cu_add3,
IIf(a = "cu_add4", u.cu_add4,
IIf(a = "cu_add5", u.cu_add5,
IIf(a = "cu_telno", u.cu_telno,
IIf(a = "cu_faxno", u.cu_faxno,
IIf(a = "cu_email", u.cu_email, u.cu_accno)))))))))).Skip((pagenum - 1) * 25).Take(25)
Ooof. That might well turn out some nasty, and likely slow performing SQL. Take a read of https://use-the-index-luke.com/sql/where-clause/obfuscation/smart-logic (not the blog I wanted to link to - but I can't find the one I'm thinking of, that was a lot more old school looking, like it was once a usenet post)
Queries in LINQ are cumulative, and they only run when something happens to enumerate the result, like calling ToArray on it, or ForEaching it. This means you can build your query dynamically in several steps. (I've dumbed things down a bit for clarity/to illustrate a point here; i haven't used your actual column names):
Dim q as IEnumerable(Of Customer) = db.Customers
If nameTextBox.Text <> "" Then q = q.Where(Function(x) x.Name = nameTextBox.Text)
If ageTextBox.Text <> "" Then q = q.Where(Function(x) x.Age = ageTextBox.Text)
If jobTextBox.Text <> "" Then q = q.Where(Function(x) x.Age = jobTextBox.Text)
If sortByCombo.SelectedValue = "Name" Then q = q.OrderBy(Function(x) x.Name)
Else If ....
If you only put something in the Name box, the q has one Where called on it. If you put something in all 3 boxes, the Where are cumulative and will act as one with terms ANDed in the SQL. The db query is not run at any point in the above, even if you add the OrderBy. If you want more info on this look up "LINQ deferred execution". At the moment you try to read the result, the query will be run.
ps; I'm not sure I'd have the DB do the ordering, necessitating a re-query every time the order is changed - perhaps just load the results into a control that knows how to sort (most grid controls, windows and web, know how to cache and sort their data to avoid a round trip to somewhere that provides the data), or use a client side container that can handle the sorting, like e.g. a DataTable, SortedList etc

How NOT filter LINQ results if criteria list is empty?

When filter criteria is passed to my LINQ query, no problem:
Dim statesSelected As String = {‘GA’,’FL’}
Dim results As IEnumerable(Of Person) = _
From p As Person in dc.Persons _
Where statesSelected.Contains(p.StateCode)
HOWEVER, if no filter criteria are selected, then I want ALL states returned (instead of none which is what the above would do). How can I do this, please? I realize I could have an IF statement around the query, but in reality I’ll have many filters (and will want to handle them all within the one query).
Thanks for any help.
I am not sure if this would translate to SQL, but you can try this approach:
Dim results As IEnumerable(Of Person) = _
From p As Person in dc.Persons _
Where statesSelected Is Nothing OrElse statesSelected.Contains(p.StateCode)
In this case if your variable statesSelected is nothing then only the first part of query would be executed, otherwise first part would be true and only second condition would matter
Try this out:
Dim results As IEnumerable(Of Person) = _
From p As Person In Persons
Where If(statesSelected.Length < 1, p.StateCode <> "", statesSelected.Contains(p.StateCode))
What it's doing is checking to make sure statesSelected has elements. If not, it simply brings back all elements. If there values in statesSelected, it brings back the ones that contain that state.
The magic is happening in the ternary If() : https://msdn.microsoft.com/en-us/library/bb513985.aspx?f=255&MSPPError=-2147217396

LinQ: join on nullable property with value null

I have a list of objects, with a property customerId which is a nullable System.Guid property.
I also have a list of id's of type System.Guid, I also added a Guid.Empty value to this list.
I try to do a join on both, but the objects with empty guids aren't returned.
Dim dos = (From d In documents Join c In allowedCustomers On c Equals If(d.CustomerGuid = Nothing, System.Guid.Empty, d.CustomerGuid) Select d).Skip(10 * (pageNr - 1)).Take(10).ToList
What is wrong? Is there another way to do this in a better way?
You are using d.CustomerGuid = Nothing but you have to use d.CustomerGuid Is Nothing.
Try this approach which uses the VB.NET's null-coalescing operator.
Dim query = From doc In documents
Join custID In allowedCustomers
On If(doc.CustomerGuid, Guid.Empty) Equals custID
Skip 10 * (pageNr - 1)
Take 10
Select doc
Dim docList = query.ToList()
Note that you can increase readability with multiple lines, also, VB.NET's query syntax is powerful than C#, so you can use Skip and Take in the query.

Linq join typeddatatable with List and return typeddatatable

I hava a strongly typed datatable and a list(of String).
I want to build a linq query to return a datatable of the same type where the fields of a certain column of the table are in the list. I thought of doing a Join, although in normal sql I would have added
SELECT FROM Table WHERE Table.ID IN(...);
This is what I tried in linq.
Dim Families As List(Of String)
Dim Articles As SomeStronglyTypedDataTable
Dim MatchingArticles = From a In Articles.AsEnumerable _
Join f In Families.AsEnumerable On a.FamilyCode Equals f.ToString _
Select New With {}
I'm not sure either if I need to convert the query result back to a datatable nor if that's even possible.
Thanks!
Try the simpler query:
Dim MatchingArticles = From a In Articles.AsEnumerable _
Where Families.Contains(a.FamilyCode)_
Select a
Dim MyMatchingArticlesTable = CopyToDataTable(Of SomeStronglyTypedDataTable) (MatchingArticles)
Yes, you can do this. Instead of Select New ..., select the matching DataRows, Select a, and then use CopyToDataTable(Of T) on the matching rows.
Dim table As DataTable = query.CopyToDataTable()
Dim typedtable As New TypedDataset.TypedDataTable
typedtable.Merge(table)
I was raking my brain trying to get something similar to work, and your code enlightened me.
All I needed was to add the .AsEnumerable() on both sides.
I'm working with C#. Anyway, I think all you need to do is select your table like
Dim MatchingArticles = From a In Articles.AsEnumerable _
Join f In Families.AsEnumerable On a.FamilyCode Equals f.ToString _
Select a;
Well, this is a very old post, but hey, it might help someone else...
If you think this would resolve your question, please mark it as correct, so others will know. You may also want to mark Devart's answer as correct. I tried it and it works.

Perform an aggregation on a DataTable with Linq in vb.net

Given a datatable, I wish to output an IEnumerable type (dictionary(of T) would be perfect, otherwise a datatable would be acceptable) that provides an aggregation of the data within the datatable.
In SQL I would write the query as such:
select groupByColumn, sum(someNumber) from myTable group by groupByColumn
In VB.NET the closest I have got to this (achieved using information here) is:
' dt is a datatable containing two columns, referred to by index in p below
Dim q = From p In dt Group p By p(0) Into Sum(p(1)) Select p(0), SumOfNumber = Sum
However I receive error: "Range variable name can be inferred only from a simple or qualified name with no arguments." on the p(0) element.
Therefore my question is as follows:
How can I resolve this error?
How do I process the result (q) as an IEnumerable type?
Life isn't made any easier because I'm using Linq in unfamiliar vb.net. Many examples are in C#, but I haven't really come across anything suitable even in C#.
Resolved: I had to alias the columns returned.
Dim q = From p In dt
Group p By transactionTypeName = p(0) _
Into totalForType = Sum(Convert.ToDouble(p(1))) _
Select transactionTypeName, totalForType
Also note that I had to do a conversion on the value that I was summing because being a dynamically returned datatable didn't have the column type specified.
Hint found here because vb.net oh so helpfully gives us a different error message to C#'s "Invalid anonymous type member declarator. Anonymous type members must be declared with a member assignment, simple name or member access."
For completeness, results are processed as below:
For Each item In q ' no need for defining item as a type (in C# we'd use the var keyword)
If item.totalForType = magicNumber Then
'do something
Else
'do something else
End If
Next