How NOT filter LINQ results if criteria list is empty? - vb.net

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

Related

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) };

LINQ to Entities .contains is ignoring results with NULL

I am new to the Entity Framework, and am struggling with what I hope is a basic problem. My code is here:
Dim accounts As List(Of STUDENT) =
(From a In SA.STUDENTs
Where (a.MATRIC_NO.Contains(matric) And a.FIRST_NAME.Contains(firstName) And a.MIDDLE_NAMES.Contains(middleName) And a.SURNAME.Contains(lastName) And a.PREFERRED_NAME.Contains(preferredName))
Select a).ToList
The query runs fine, until one of the search fields is NULL in the database. If, for instance, a matric number is entered in the seach interface but middle name is left blank, the query will not return any records if middle name is NULL in the database. If middle name is a blank space in the database then it will return the record.
Can anyone offer any pointers?
Many thanks!
You can add an extra check in your query. For example:
public function filterList(IEnumerable list, string name)
{
var filtered_list = list.Where(x=> x.Name.Contains(name) || string.IsNullorWhitespace(name)).ToList();
return filtered_list;
}
So you can see, if the name variable is empty, all elements will return true, so all elements will be return (no real filter applied).
So basically, you can change all filters from
something.Contains(anotherthing)
to
something.Contains(anotherthing) || string.IsnullOrWhitespace(anotherthing)
(From a In SA.STUDENTs
Where isnull(a.MATRIC_NO.Contains(matric) And a.FIRST_NAME.Contains(firstName) And a.MIDDLE_NAMES.Contains(middleName) And a.SURNAME.Contains(lastName) And a.PREFERRED_NAME.Contains(preferredName))
Select a).ToList
Like this `:select * from tbl where statusid = isnull(#statusid,statusid)
try like this ..
Dim get_rmf_2 = From rmf In t_rmf _
Where Not IsDBNull(rmf!NIVP) AndAlso rmf!NIVP = nivp_rap
this is in VB I think this is works fine
I managed to solve this using a different approach. If there was no value entered for a particular field, leave it out the query. I accomplished this using predicates, as below:
'create the base query
Dim accounts =
(From a In SA.STUDENTs
Select a)
'create predicates for each condition required in the query
If matric <> "" Then
accounts = accounts.Where(Function(m) m.MATRIC_NO.Contains(matric))
End If
If firstName <> "" Then
accounts = accounts.Where(Function(f) f.FIRST_NAME.Contains(firstName))
End If
If middleName <> "" Then
accounts = accounts.Where(Function(mn) mn.MIDDLE_NAMES.Contains(middleName))
End If
If lastName <> "" Then
accounts = accounts.Where(Function(l) l.SURNAME.Contains(lastName))
End If
If preferredName <> "" Then
accounts = accounts.Where(Function(p) p.PREFERRED_NAME.Contains(preferredName))
End If
'execute the query
Dim accountlist = accounts.ToList
'return the results
Return accountlist
If anyone can see anything wrong with this, or any gotchas that I'm unaware of, please let me know! I'm very new to LINQ to Entities, and LINQ in general!

Convert a two-table exists query from SQL to Linq using dynamic fields in the subquery

I'm trying to query old Access database tables and compare them with SQL Server tables.
They often don't have primary keys, or they have extra fields that had some purpose in the nineties, etc., or the new tables have new fields, etc.
I need to find records - based on a set of fields specified at runtime - that are in one table but not another.
So, I do this kind of query all the time in SQL, when I'm comparing data in different tables:
dim fields_i_care_about as string = "field1, field2, field3"
'This kind of thing gets set by a caller, can be any number of fields, depends on the
'table
dim s as string= ""
dim flds = fields_i_care_about.split(",")
for i as integer = 0 to ubound(flds)
if s > "" then s += " AND "
s += " dysfunctional_database_table." & flds(i) & "=current_database_table." & flds(i)
next
s = "SELECT * from dysfunctional_database_table where not exists (SELECT * from current_database_table WHERE " & s & ")"
====
I'm trying to do this using Linq because it seems like some of the datatype problems with two different database types become less of a headache,
but I'm new to Linq and totally stuck.
I got as far as this:
Put old and new tables into datatables as dt1 and dt2
Dim new_records = _
From new_recs In dt2.AsEnumerable
Where Not ( _
From old_recs In dt1.AsEnumerable Where old_recs(field1) = new_recs(field1) AndAlso old_recs(field2) = new_recs(field2)).Any
Select new_recs
But I can't figure out how to put this part in on the fly -
old_recs(field1) = new_recs(field1) AndAlso old_recs(field2) = new_recs(field2)
So far I've tried:
putting the fields I want to compare and making them a string and just putting that string in as a variable ( I thought I was probably cheating, and I guess I was)
dim str = old_recs(field1) = new_recs(field1) AndAlso old_recs(field2) = new_recs(field2)
From new_recs In dt2.AsEnumerable
Where Not ( _
From old_recs In dt1.AsEnumerable Where str).Any
Select new_recs
It tells me it can't convert a Boolean -
Is there any way to do this without Linq expressions? They seem far more complex than what I'm trying to do here, and they take a lot of code, and also I can't seem to find examples of Expressions where we're comparing two fields in a subquery.
Is there a simpler way? I know I could do the usual EXISTS query using JOIN or IN - in this case I don't need the query to be super fast or anything. And I don't need to use a DataTable or DataSet - I can put the data in some other kind of object.
So I found a lot of sample code that used MethodInfo and reflection and things like that, but I couldn't get any of it to work - these Datarows have a Field method but it requires that you add an (of object) argument before the field name argument and that's tricky to do.
So I'm not sure if this solution is the most efficient way, but at least it works. I'd be interested in finding out whether this way of doing it is efficient and why or why not. It seemed like most people used reflection to do this kind of thing, but I couldn't get that working properly and anyway what I'm trying to do is pretty simple while those methods were pretty complex. I suppose I'm doing Linq with a SQL mindset, but anyway it works.
Dim f As Func(Of DataRow, DataRow, String, Boolean) = Function(d1 As DataRow, d2 As DataRow, s As String)
Dim fields = Split(s, ",")
Dim results As Boolean = True
For k As Integer = 0 To UBound(fields)
Dim obj = DataRowExtensions.Field(Of Object)(d1, fields(k))
Dim obj2 = DataRowExtensions.Field(Of Object)(d2, fields(k))
If obj <> obj2 Then results = False : Exit For
Next
Return results
End Function
Dim new_records = _
From new_recs In dt2.AsEnumerable.AsQueryable()
Where Not ( _
From old_recs In dt1.AsEnumerable.AsQueryable Where f(old_recs, new_recs, id_key)).Any
Select new_recs
Try
Return new_records.CopyToDataTable
Catch ex As Exception
Stop
End Try

linq to xml enumerating over descendants

Hi trying to write a simple linq query from a tutorial I read. But i cannot seem to get it to work. I am trying to display both the address in the attached xml document, but can only display the first one. Can someone help me figure out why both aren't being printed. Thank you very much
<?xml version="1.0" encoding="utf-8" ?>
<Emails>
<Email group="FooBar">
<Subject>Test subject</Subject>
<Content>Test Content</Content>
<EmailTo>
<Address>foo#bar.com</Address>
<Address>bar#foo.com</Address>
</EmailTo>
</Email>
</Emails>
Dim steve = (From email In emailList.Descendants("Email") _
Where (email.Attribute("group").Value.Equals("FooBar")) _
Select content = email.Element("EmailTo").Descendants("Address")).ToList()
If Not steve Is Nothing Then
For Each addr In steve
Console.WriteLine(addr.Value)
Next
Console.ReadLine()
End If
Your current query returns a List<IEnumerable<XElement>>. That means you need two nested foreach loops: one to loop over the list, and another to loop over the content of the IEnumerable<XElement>.
Instead, you could update your LINQ query to use the Enumerable.SelectMany method and get to the addresses directly. In query format a SelectMany is represented by using a second from clause to indicate a subquery. This would resemble the following:
Dim query = (From email In emailList.Descendants("Email") _
Where (email.Attribute("group").Value.Equals("FooBar")) _
From addr In email.Element("EmailTo").Descendants("Address") _
Select addr.Value).ToList()
If query.Any() Then
For Each addr In query
Console.WriteLine(addr)
Next
End If
Also, the ToList isn't needed if you only want to iterate over the results and don't intend to use the result as a list for other purposes.
EDIT: to explain how this query works let's break it down in 2 parts:
First:
From email In emailList.Descendants("Email") _
Where (email.Attribute("group").Value.Equals("FooBar")) _
This queries for all <Email> nodes and only matches the ones that have a group attribute value of "FooBar".
Second:
From addr In email.Element("EmailTo").Descendants("Address") _
Select addr.Value
This is a subquery that continues where the first part (above) ended. It essentially is a way to further query the results of the original query. Here we query for all <Address> nodes and, finally, select their Value for the inner text of the nodes. The reason we need to do this is because Descendants("Address") returns a IEnumerable<XElement> containing all "Address" elements. We need to perform an additional query (or foreach) to iterate over those values and extract their values.
Another way to illustrate this is by breaking it down in 2 queries:
Dim query1 = From email In emailList.Descendants("Email") _
Where (email.Attribute("group").Value.Equals("FooBar"))
Select email.Element("EmailTo").Descendants("Address")
Dim query2 = query1.SelectMany(Function(addr) addr.Select(Function(a) a.Value))
Notice the use of SelectMany in query2. The Select in query2 is that additional effort to loop over the IEnumerable that I mentioned earlier. The original query is clearer than query1/query2, but I wrote them just to clarify the point.

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.