Dynamically Modifying Where Condition in LINQ Query - c#

The following Entity Framework query runs without error.
Predicate<Program> filterProgram;
if (programId.HasValue)
filterProgram = (p => p.Id == programId && !p.IsDeleted);
else
filterProgram = (p => !p.IsDeleted);
var analytics = (from a in repository.Query<Analytic>()
where (a.Marker == "Open" || a.Marker == "LastTouch") &&
a.EntityType == "Proposal" &&
a.Site == "C"
join p in repository.Query<Program>()
on a.EntityId equals p.Id
//where filterProgram(p)
group a
by new { a.LoginSessionId, a.EntityId, p.Id, p.Name } into g
let f = g.OrderBy(x => x.TimestampUtc).FirstOrDefault(x => x.Marker == "Open")
where f != null
let t = g.FirstOrDefault(x => x.Marker == "LastTouch" && x.TimestampUtc > f.TimestampUtc)
select new
{
ProgramId = g.Key.Id,
Program = g.Key.Name,
ProposalId = g.Key.EntityId,
FirstOpen = f,
LastTouch = (t ?? f).TimestampUtc
}).ToList();
However, if I uncomment the line where filterProgram(p), I get the run-time error:
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
I was expecting that LINQ would be able to incorporate my predicate into the query and convert it to SQL. Why am I getting this error, and is there a way to dynamically modify a where predicate this way?

The problem is caused because Entity Framework needs to be able to convert your LINQ query into SQL. Your LINQ query is compiled into a data structure called an Expression tree which is then passed to Entity Framework for conversion to SQL.
You have two options:
Replace your call to filterProgram with a more basic C# expression. Depending on the complexity of filterProgram this may not be possible.
Remove you call to filterProgram, and convert this query to an IEnumerable<T>, maybe by calling .ToList(). You can then further filter the results of this query using filterProgram
Example of 2
var query = /* Your Query With filterProgram commented out */
var resultsFromSql = query.ToList();
var fullyFiltered = resultsFromSql.Select(filterProgram);

Change filterProgram's type to Expression<Func<Program, bool>> and then it should be usable in a LINQ Where clause.
One caveat, however: I managed to get it to work in method chain syntax, but not in query syntax.
For example, this works:
dataContext.Programs.Where (filterProgram)
but this does not:
from p in dataContext.Programs
where filterprogram
(The compiler complains that it cannot resolve method Where, which is available on both IEnumerable and IQueryable.)
In your case, it might be acceptable to replace the line
join p in repository.Query<Program>()
with
join p in repository.Query<Program>().Where(filterProgram)

In Linq to Entities it is not possible to call an external method without converting the query to IEnumerable. Thus, since filterProgram is a method, you may not call inside the query.
If possible you may call ToList before calling the FilterProgram and it will work.
Another option maybe inserting the logic of filterProgram into the query.

The problem is that linq2entities tries to translate the expression tree to SQL your expression tree has an invocation of the method Invoke on a delegate type (filterProgram)
however there's nothing stopping you from inlining that predicate
var id= programId.HasValue ? programId.GetValueOrDefault() : -1;
var analytics = (from a in repository.Query<Analytic>()
where (a.Marker == "Open" || a.Marker == "LastTouch") &&
a.EntityType == "Proposal" &&
a.Site == "C"
join p in repository.Query<Program>()
on a.EntityId equals p.Id
where !p.IsDeleted && (!hasValue || p.Id == id)
....
This assumes that -1 is an invalid programId

Related

A query body must end with a select clause or a group clause c#

While this is a frequently asked question I have not found a solution that fits my situation.
I am receiving the above compile error on the following code:
var data = from g in db.MD_import_results
.Where((fProjectID == tProjectID) && (g.md_CheckResults) != null
|| (g.md_CheckResults1) != null || (g.md_CheckResults2) != null
|| (g.md_CheckResults3) != null).Select(
p => new
{
p.AccountID,
p.md_HouseNumber,
p.md_StreetPreDirectional,
p.md_StreetName,
p.md_StreetSuffix,
p.md_StreetPostDirectional,
p.md_Suite,
p.md_City,
p.md_State,
p.md_ZipCode,
p.md_CheckResults,
p.md_CheckResults1,
p.md_CheckResults2,
p.md_CheckResults3,
p.ProjectID
});
Any suggestions are appreciated.
There are two ways to write LINQ queries, query syntax and method syntax.
Query syntax looks like
from g in x
where g.foo == bar
select g.baz;
Method syntax looks like
x.Where(g => g.foo == bar).Select(g => g.baz);
You have combined the two. You started writing from g in x and then continued writing .Where(...). Pick one syntax and stick with it.

Why Reverse() cannot be converted into SQL?

My application is running under ASP.NET 4.0, which uses BLToolkti as ORM tool.
I have some queryable expression:
var q = db.GetTable<T>()
.Where(tb=>tb.TeamId==MyTeamId && tb.Season==MySeasonId)
.OrderByDescending(tb=>tb.Id)
.Take(20)
.Reverse()
Attempt to convert q.ToList() causes the following error:
Sequence 'Table(TeamBudget).Where(tb => ((tb.TeamId ==
value(VfmElita.DataLogicLayer.Teams.Team+TeamBudget+<>c__DisplayClass78).teamId)
AndAlso (tb.Season ==
value(VfmElita.DataLogicLayer.Teams.Team+TeamBudget+<>c__DisplayClass78).season))).OrderByDescending(tb
=> Convert(tb.Id)).Take(20).Reverse()' cannot be converted to SQL.
If I remove ".Reverse()" from the queryable object everything works fine.
What is the reason why queryable object with .Reverse() cannot be converted into SQL? Is that BLToolkit limitation? Is there any solution workaround for that?
Thank you!
It's pretty clear what the other LINQ methods convert to (where, order by, top(20)), but what would Reverse() convert to? I can't think of an SQL statement I've seen that mimics that behavior, and when you're querying the database your LINQ statement must ultimately resolve to valid SQL.
This may not be what you're going for, but one option would be to execute the query first using ToList(), then apply Reverse():
var q = db.GetTable<T>()
.Where(tb => tb.TeamId == MyTeamId && tb.Season == MySeasonId)
.OrderByDescending(tb => tb.Id)
.Take(20)
.ToList()
.Reverse();
Alternatively, you could get the count and skip that many records first, although this could be inaccurate if the number of records change between calls. Plus it's two queries instead of just one.
var totalRecords = db.GetTable<T>()
.Count(tb => tb.TeamId == MyTeamId && tb.Season == MySeasonId);
var q = db.GetTable<T>()
.Where(tb => tb.TeamId == MyTeamId && tb.Season == MySeasonId)
.Order(tb => tb.Id)
.Skip(totalRecords)
.Take(20);

changing linq to sql query

I have a question regarding a Linq to SQL query.
I have following situation:
I have a search with lots of options, like location, availability, name, language etc ...
For this options i have to execute a query to retrieve the results according to options selected, how can i best do it, i cannot write a linq query like for each possibility and combination of options, but i cannot write one for all of them as it will not work, for example:
from p in context.people where p.location==model.location && p.availability==model.availability .... select p
In this case imagine availability is not selected and should not be searched for, but in this case it will be passed as false, or if location is not set and is null so it will only search for empty locations, although i just need all.
So my question is how do people handle this kind of behaviour with queries?
As you long as you do not execute the linq query immediately you can just add where clauses to it. You can do this for example:
var query = from p in context.people;
if(searchOnLocation)
{
query = query.where(p => p.location == model.location);
}
if(otherSearch)
{
query = query.where(p => p.someOtherProperty == someotherValue);
}
var result = query.ToList();
As long you don't call ToList() on your IQueryable, the linq will not be translated into SQL. It's only in the last call, that the linq will be translated and executed against the database
IQueryable<Person> query = context.people;
if(model.location != null)
query = query.Where(x => x.location == model.location);
if(model.availability != null)
query = query.Where(x => x.availability == model.availability);
// etc
Basically, you can compose more and more restrictions as you go.
If you want to implement query without if condition than you can use following syntax:
var query = context.people.
where(p => p.location == (model.location ?? p.location)
&& p.availability == (model.availability ?? p.availability))
.ToList();

LINQ to Entities Does not Support Invoke

I have the following query in LINQ to Entities:
var query = from p in db.Products
where p.Price > 10M
select p;
At this point the query has not executed, and I want to write a query that will return true/false based on some conditions:
return query.Any(p => p.IsInStock &&
(p.Category == "Beverage" ||
p.Category == "Other"));
This works fine; however, I would like to get some reuse out of my code. I have many methods that need to filter based on if the category is a Beverage or Other, so I tried creating a delegate:
Func<Product, bool> eligibleForDiscount = (product) => product.Category == "Beverage" || product.Category == "Other";
I wanted to substitute the inline check with the delegate:
return query.Any(p => p.IsInStock && eligibleForDiscount(p));
This gives me an error saying that LINQ to Entities doesn't support Invoke. Why can't I substitute the inline code for a delegate like this, and is there any way I can accomplish my reuse some other way?
Recall that under the hood Linq-to-{DATABASE} is just transforming the IQueryable you've produced into Sql.
You can't inline code like that because an Invoke (the method you're actually calling when you call a Func or Action) has no consistent way to transform it into a sql statement (you could be doing anything in there).
That said you can reuse parts by splitting it up:
var query = from p in db.Products
where p.Price > 10M
select p;
query = query.Where(p => p.IsInStock);
query = query.Where(p => p.Category == "Beverage" || p.Category == "Other");
return query.Any();
Both those can be put into methods that take an IQueryable<Product> and return the same (but filtered). Then you can reuse to your heart's content!
The problem is that IQueryable needs to generate a SQL expression to pass to RDBMS, and it cannot do it when all it has is an opaque predicate.
The obvious but inefficient way is to rewrite your query as follows:
return query.Where(p => p.IsInStock).AsEnumerable().Any(eligibleForDiscount);
A less trivial way would be as follows:
bool GotEligible(Expression<Func<Product,bool>> pred) {
return query.Where(p => p.IsInStock).Any(pred);
}
Note how instead of a predicate this method takes a predicate expression. Now it is transparent to the EF, and can be converted to a SQL query without a problem.
As long as you stick with IQueryable, you can keep reusable querys in functions.
public IQueryable<Product> EligibleForDiscount(IQueryable<Product> products)
{
return products.Where(p => product.Category == "Beverage" ||
product.Category == "Other");
}
Now call it like any other function:
IQueryable<Product> query = (from p in db.Products
where p.Price > 10M
select p);
query = EligibleForDiscount(query);

Query syntax works but lambda syntax doesn't for linq select statement

This code doesn't work:
return this.Context.StockTakeFacts.Select(stf => ((stf.StockTakeId == stocktakeid) && (stf.FactKindId == ((int)kind)))).ToList<IStockTakeFact>();
This statement does:
var f = from stf in this.Context.StockTakeFacts
where (stf.StockTakeId == stocktakeid) && (stf.FactKindId == ((int)kind))
select stf;
return f.ToList<IStockTakeFact>();
What is the difference?? The first complains that IQueryable does not have a toList method so I gather I've written the first statement incorrectly.
You need to use a Where call in order to filter elements (not Select)
return this.Context.StockTakeFacts
.Where(stf => ((stf.StockTakeId == stocktakeid) && (stf.FactKindId == ((int)kind))))
.ToList<IStockTakeFact>();
When using explicit LINQ API queries the select item is implicit. It can be made explicit with a call to Select but it's not necessary (unless you map the values in some way)
You have to use Where() in order to be able filtering by the predicate:
return this.Context.StockTakeFacts
.Where(stf => stf.StockTakeId == stocktakeid
&& stf.FactKindId == (int)kind)
.ToList<IStockTakeFact>();

Categories

Resources