Why do I get a different output from analogous Linq queries? - c#

I am trying the following code in LinqPad 5 (specifically 5.26.01)
IEnumerable<string> breeds = new List<string>{
"Fantail",
"Lahore",
"Bokhara Trumpeter",
"Rhine Ringbeater",
"Birmingham Roller",
"Pomeranian Pouter",
"Racing Homer",
"Archangel"};
IEnumerable<string> GetAllBreedsContainingLetter_Fluent(IEnumerable<string> breedlist, string letter)
{
return breedlist
.Where(breedname => breedname.Contains(letter.ToUpperInvariant()) || breedname.Contains(letter.ToLowerInvariant()))
.OrderByDescending(breedname => breedname)
.Select(breedname => breedname);
}
IEnumerable<string> GetAllBreedsContainingLetter_Query(IEnumerable<string> breedlist, string letter)
{
return breedlist = from b in breedlist
where (b.Contains(letter.ToUpperInvariant()) || b.Contains(letter.ToLowerInvariant()))
orderby b descending
select b;
}
var breedsFluent = GetAllBreedsContainingLetter_Fluent(breeds, "R");
breedsFluent.Dump();
var breedsQuery = GetAllBreedsContainingLetter_Query(breeds, "R");
breedsQuery.Dump();
I think the two functions should be analogous but I noticed something odd about the output in Linqpad. The first .Dump() is identified as an IEnumerable<String>; the second .Dump() identifies as a IOrderedEnumerable<String>.
Is this something about the queries I'm running or is it an artifact of Linqpad? I haven't found anything from Googling.

In query syntax the transformation is such that when you have a trivial projection (projecting an item to itself) it only generates a Select call when that trivial projection is the only operation in the query. Since your query contains other operations, the Select is elided entirely, for performance reasons.
A proper translation of that query syntax query into method syntax would skip the Select. The way to replicate the behavior using query syntax would require something like a second query, to do the trivial projection.

Related

Lambda statement Intersect

Trying to work out this lambda query without doing a foreach etc. I currently have a database table with a column which contains a comma separated list of strings. It's basically a filter (it could for example be 'pants,tops,t-shirts,gloves'). In the function that queries the database is basically has a parameter that accepts a similar string.
I don't know if I'm just too tired at the moment and can't work it out but struggling. I know it will be Intersect but can't figure out the syntax.
Currently I have...
public static List<ItemListItem> GetItems(string filter = "")
{
var db = new dbConnection();
var results = (from i in db.Items
select i);
if (!string.IsNullOrEmpty (filter))
results = results.Where(x => x.Filters.Split(',').Intersect(filter.Split(',')) )
}
You need Enumerable.Any at the end of your Intersect like:
x.Filters.Split(',').Intersect(filter.Split(',')).Any()
So your query would be:
results = results.Where(x => x.Filters.Split(',')
.Intersect(filter.Split(','))
.Any());
Enumerable.Where would require an expression returning bool. Intersect would return an IEnumerable<T> and Enumerable.Where would not accept it. Adding Enumerable.Any would means return those rows where intersection resulted in Any row.

Dynamic Linq - Joining a table with 1 to many relationship

I'm using Dynamic Linq as the backend to an in-app reporting tool and I've hit a problem that I can't get round where I have to access a table that has a 1:M relationship .
My simplified data structure is this:
If I were querying this in standard Linq I'd write the query as:
from a in context.Table_A
select new
{
a.RefNo,
val = from b in a.Table_B
where (b.A_ID == a.ID)
where (b.code == "A0001"
select(b.Value).FirstOrDefault()
}
This works without any problem. However, when I try the query using Dynamic Linq I can't get the join to work.
From the code below you can see what I'm getting at but obviously I can't use the "a." and the "a.Table_B" references in the query. What do I have to do to be able to access Table_B in this context?
string select = "new (Ref_No,
val = from b in a.Table_B
where (b.A_ID == a.ID)
where (b.code == \"A0001\"
select(b.Value).FirstOrDefault()";
var results = context.Table_A.Select(select);
Edit 1:
To answer #Hogan's comment - Why don't I use join: The reports system is dynamic and the select statement may or may not be joining on to Table_B (or indeed joining on to Table_B multiple times) so the join has to be optional. My other issue with this is that unlike the Select method where I can pass in a string as a parameter (allowing me to make it dynamic quite easily) the Join() method can't be called in that way. The closest thing I've found is a dynamic Linq join extention method, something I may have to consider using but I've a feeling that this will be cumbersome with the dynamic select().
Edit 2:
Based on Hogan's suggestions I've got this far:
delegate string searchTableA(Table_A a);
public void Search()
{
....
searchTableA sel = (a) =>
{
return (from b in context.Table_B
where (b.A_ID == a.ID)
select (b.Value)).FirstOrDefault();
};
var res = context.Table_A.Select(sel);
}
This gives the error: 'System.Data.Entity.DbSet<TestDynamicLinqJoins.Table_A>' does not contain a definition for 'Select' and the best extension method overload 'System.Linq.Dynamic.DynamicQueryable.Select(System.Linq.IQueryable, string, params object[])' has some invalid arguments
Hard to give exact code because I don't know the types of your elements, but something like this would work fine using delegates.
delegate string searchTableA(elementType a);
searchTableA sel = (a) =>
{
return from b in a.Table_B
where (b.A_ID == a.ID)
where (b.code == "A0001")
select(b.Value).FirstOrDefault();
};
var results = context.Table_A.Select(sel);

How to use the return value of a function in a linq where clause?

I'm trying to create a general search query against an EF entity type (person). In general, the search takes a string, splits it by commas, and then searches for people whose various attributes contain all of the key words.
I have a function called getProperties(Person p) that takes an entity (overridden by entity type), and returns a string of the various relevant properties joined together with a delimiter... such as:
John~Doe~Team A~Full Time
If the user searches for "Team A, Full" person corresponding to the above flattened entity should be returned... however, if the enter "John, Smith" it shouldn't.
I think the following looks right, but it just doesn't work...
public IEnumerable<Person> SearchPeople(string searchString)
{
if (searchString == null || string.IsNullOrEmpty(searchString.Trim()))
return base._objectSet.ToList();
string[] SearchWords = searchString.Split(',').Select(s => s.Trim()).ToArray();
return (from person
in base._objectSet
let t = (getProperties(person))
where SearchWords.All(word => t.Contains(word))
select person).ToList();
}
and the getProperties function is:
public static string getProperties(Person p)
{
string[] values = { p.Surname, p.GivenName, p.Team, p.Status };
return values.Aggregate((x, y) => String.IsNullOrEmpty(y) ? x : string.Concat(x, "~", y));
}
Does anyone see where I'm going wrong?
Edit
No exceptions are raised, but when I step through the code, when I get to the linq, it steps into the dispose method of the unitofwork that is hosting the query. Very odd.
If I change it so that it searches against a hard-coded string, it works as expected:
var test = (from person
in base._objectSet
where SearchWords.All(word => "John~Doe~Team A~Full Time".Contains(word))
select person).ToList();
well, it works in that it matches the queries I expect it to, but as it's static, it returns every person record (pretty much like having where(true) =P)
Edit the Second
Even odder is that if I store the results into a var, then return the var with a breakpoint on the return, execution never hits the breakpoint... this linq is like a black hole... I can step into it, but it never returns me back to my SearchPeople method... it just disposes the context and forgets about it.
Edit the Third
If I don't call ToString() right away and look at the linq expression in debugger, it says "Linq to Entities does not recognize the method getProperties(Person)" Looks like it was silently choking on my method... any way to use my method without linq choking on it?
You are returning a List? try the return type to be a List or change the .ToList() to be AsEnumerable()
public List<Person> SearchPeople(string searchString)
{
if (searchString == null || string.IsNullOrEmpty(searchString.Trim()))
return base._objectSet.ToList();
string[] SearchWords = searchString.Split(',').Select(s => s.Trim()).ToArray();
return (from person
in base._objectSet
let t = (getProperties(person))
where SearchWords.All(word => t.Contains(word))
select person).ToList();
}
Well, after finding out that linq 2 entites doesn't like methods (as is doesn't know how to translate it to sql), i rewrote my linq is a very tedious but functioning manner:
var people = from p
in base._objectSet
where SearchWords.All(p.GivenName.Contains(word) || p.Surname.Contains(word) || p.(???).Contains(word) || etc.)
select p;
Annoying, but there you go.

Use a function within LINQ

I am using LINQ to create a list. But I want to use a function at the end to generate the object iself, something LINQ complains about
LINQ to Entities does not recognize the method 'WashroomStatusItem GetWashroomStatusForItem(WashroomStatus)' method, and this method cannot be translated into a store expression.
What am I doing wrong?
var query = (from c in context.WashroomStatus
where c.WashroomId == GroupItem.WashroomID
select GetWashroomStatusForItem(c));
private WashroomStatusItem GetWashroomStatusForItem(WashroomStatus item)
{
WashroomStatusItem temp = new WashroomMonitorWCF.WashroomStatusItem();
//do stuff with it
return temp;
}
The problem is that the SQL conversion can't convert your method into SQL. You should use AsEnumerable() to "switch" from the out-of-process provider to LINQ to Objects. For example:
var query = context.WashroomStatus
.Where(c => c.WashroomId == GroupItem.WashroomID)
.AsEnumerable()
.Select(c => GetWashroomStatusForItem(c));
Note that if GetWashroomStatusForItem only uses some properties, you may want to project to those separately first, to reduce the amount of information fetched from the server:
var query = context.WashroomStatus
.Where(c => c.WashroomId == GroupItem.WashroomID)
.Select(c => new { c.Location, c.Date };
.AsEnumerable()
.Select(p => GetWashroomStatusForItem(p.Location, p.Date));
Jon Skeet's answer is correct, but I'd add that depending on the nature of GetWashroomStatusForItem(), it should probably either be broken down into LINQ statements and added into the query itself, or it should be executed after the query has returned.
So, lets say GetWashroomStatusForItem() looks something like this: note that this is extremely oversimplified.
public static WashroomStatus GetWashroomStatusForItem(Item c)
{
return c.WashroomStatus;
}
it should just be added to the LINQ query like this:
var query = (from c in context.WashroomStatus
where c.WashroomId == GroupItem.WashroomID
select c.WashroomStatus);
But if it relies heavily on stuff not in the db, I'd just end the Linq statement before you get the WashroomStatus, and then call GetWashroomStatusForItem() on the results. It's not gonna a performance difference since Linq uses lazy evaluation, and you generally want to keep db operations separate from "programmatic" ones.

Linq - doesn't start with any prefix in range of prefixes

I need to create a Linq query that would have the following logic:
IEnumerable<string> prefixes = GetListOfPrefixesFromSomewhere();
IQueryable<Record> myQuery = GetAllRecordsFromRepository();
foreach (string prefix in prefixes)
{
myQuery = myQuery.Where(x => !x.Field.StartsWith(prefix));
}
This would obviously result in a large IQueryable which can then be executed.
Is there a nice elegant way to express this is a single Linq statement?
You can at least try this:
// Only call ToList if you need to, of course... but I think EF/LINQ To SQL
// will need it as a list (or array)
List<string> prefixes = GetListOfPrefixesFromSomewhere().ToList();
IQueryable<Record> query = GetAllRecordsFromRepository()
.Where(x => !prefixes.Any(prefix => x.Field.StartsWith(prefix)));
Quite what the SQL will look like, I don't know - but I think it's logically what you want, which is usually a good start.

Categories

Resources