LinQ: join on nullable property with value null - vb.net

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.

Related

Dealing with LINQ to Objects when object not found

I am using Linq statement as per below to find student name by its ID. It works fine. However there are cases where there is no student with given ID. In those cases an error is thrown "Object reference not set to an instance of an object."
How to efficiently deal with this problem?
Dim Name As String = Students.FirstOrDefault(Function(Student) Student.ID = "NO00007").Name
If you are satisfied with Name being null if there is no matching student, you can use the null conditional operator for member access:
Dim Name As String = Students.FirstOrDefault(Function(Student) Student.ID = "NO00007")?.Name
As usually answer is "it depend" - it depend on how you will use result you will get
If you want get some "default"/empty string instead of name when collection doesn't contain item
Dim result = Students.Where(Function(student) student.ID = "NO00007").
Select(Function(student) student.Name).
DefaultIfEmpty(String.Empty).
First()
Almost same approach if you want to get some "empty" object instead of null
Dim resultStudent = Students.Where(Function(student) student.ID = "NO00007").
DefaultIfEmpty(New Student With { .Name = "empty" }).
First()
From performance point of view approach above are same as FirstOrDefault - but provide little bid better readability(subjective of course)

Where clause in LINQ query is not recognized in 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

Distinct ComboBox Items

I am trying to convert sql to entity and I need to select distinct items. I thought this would work but its returning all the rows instead of the distinct items.
Dim OrderNos = (From r In Orders.R3Delivery Where r.mainOrderNumber <> "" Select r).Distinct().ToList()
For Each thisentry In OrderNos
cbOrderNumbers.DisplayMember = thisentry.mainOrderNumber
cbOrderNumbers.ValueMember = thisentry.mainOrderNumber
Next
Also is their any good free sql to linq tools out their linquer good but its like 60 quid
The problem is that the Distinct() is comparing the entire object being returned, not just the order number.
If you only need the order numbers, changing this line should get you there:
Dim OrderNos = (From r
In Orders.R3Delivery
Where r.mainOrderNumber <> ""
Select r.mainOrderNumber).Distinct().ToList()
If you need the whole object, then it gets more complicated.

How to get element from list A and list B using LINQ

How to get element from list A and list B using LINQ EVEN if list B is empty (will still return element of list A but elements of list B will be empty)
The idea is to be able to recreate a single anonymous object based on elements of list A and B.
From elemListA In data.ListA_
From elemListB In elemListA.ListB _
Select New With { _
.ElementA = elemListA.ElementA, _
.ElementB = elemListA.ElementB, _
.ElementC = elemListB.ElementA, _
.elementD = elemListB.ElementB, _
}).ToList()
The problem is that it will crash if ListB is empty.. and another problem is if i put a where it will not include the elements of ListA because they are filtered out by the where clause and i want to have them.
I would do a join but the problem is there no relation between the two object.. except an element from ListA have a ListB.
It is surprisingly tough to get an outer join effect when no join is possible. Basically, I can see two approaches:
Replace elemListA.ListB by an array with one empty (Nothing) element when ListB is null.
Dim array(0) as Nullable(of ElementB)
...
From elemListB In If(elemListA.ListB, array)
Use Union: first query the ListA objects that have a ListB and union with the object that haven't. In both queries you must create exactly the same anonymous types, so in the second part you must put .ElementB = emptyB where emptyB was declared by Dim emptyB As ElementB = Nothing.
Sounds like what you're describing is a left outer join. Microsoft wrote a tutorial on doing this with LINQ.
Specifically addressing your question, you need to check for a null value. In their example, Microsoft uses a ternary operator to return an empty string if the value is null.
var query = from person in people
join pet in pets on person equals pet.Owner into gj
from subpet in gj.DefaultIfEmpty()
select new { person.FirstName, PetName = (subpet == null ? String.Empty : subpet.Name) };

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