'EF LINQ query with Expression: method name expected - How can I pass an Expression into function to be used in EF query?
I have a function that's supposed to return different info from an EF LINQ query based on an Expression and/or lambda that's passed to it.
Here's my code:
public static ObservableCollection<SI> GetDisplayStratsByJurisd(string jurisd, short Year,
Expression<Func<string, bool>> lambStat)
{
var ctx = new MI_Entities(server, database);
var strats = from y in ctx.SIset.AsNoTracking()
where y.Jurisd == jurisd && y.Year_ID == Year && lambStat(y.Status)
select y;
return new ObservableCollection<SI>(strats);
}
The compiler gives me the following error:
Method name expected
If I use this instead:
public static ObservableCollection<SI> GetDisplayStratsByJurisd(string jurisd, short Year,
Expression<Func<string, bool>> lambStat)
{
var ctx = new MI_Entities(server, database);
Func<string, bool> bob = lambStat.Compile();
var strats = from y in ctx.SIset.AsNoTracking()
where y.Jurisd == jurisd && y.Year_ID == Year && bob(y.Status)
select y;
return new ObservableCollection<SI>(strats);
}
Then I get a different error:
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities
So, I'm not sure how to go about passing a lambda to a function so that it can be used in a query. Can this be done? If so, how?
Solution 1:[1]
So you have some Expression<Func<TSource, bool>>
, and you want to combine them into one Expression<Func<TSource, bool>>
using AND functionality, such that you can use it AsQueryable in entity framework.
It would be nice to have this in a LINQ like fashion, so we can put it in between a concatenation of Linq statements.
Let's create some extension functions.
// A function that takes two Expression<Func<TSource, bool>> and returns the AND expression
static Expression<Func<TSource, bool>> AndAlso<TSource> (
this Expression<Func<TSource, bool>> x,
Expression<Func<TSource, bool>> y)
{
// TODO implement
}
Usage:
Expression<Func<Student, bool>> expr1 = student => student.City == "Birmingham";
Expression<Func<Student, bool>> expr2 = student => student.Gender == Gender.Male;
Expression<Func<Student, bool>> exprAND = expr1.AndAlso(expr2);
var brummyMaleStudents = dbContext.Students.Where(exprAnd).Select(...);
Let's implement AndAlso
Normally a Where would be like:
.Where(student => student.City == "Birmingham" && student.Gender == Gender.Male)
The input parameter student
is used as input for the left expression and as input for the right expression. We need to have something that says:
Take one input parameter of type Student, put it in the left expression, and in the right Expression and perform an AND between the two Boolean return values. Return the Boolean result.
For this we create a class derived from System.Linq.Expressions.ExpressionVisitor
.
This class represent the action: "put a student in the expression and calculate it". This calculating is called "visiting the Expression". The input of the expression is an expression, the result of visiting is another expression:
internal class ReplaceExpressionVisitor : ExpressionVisitor
{
private readonly Expression oldValue;
private readonly Expression newValue;
public ReplaceExpressionVisitor(ParameterExpression oldValue,
ParameterExpression newValue)
{
this.oldValue = oldValue;
this.newValue = newValue;
}
public override Expression Visit(Expression node)
{
if (node == this.oldValue)
{ // "my" expression is visited
return this.newValue;
}
else
{ // not my Expression, I don't know how to Visit it, let the base class handle this
return base.Visit(node);
}
}
}
Now that we've create the expression visitor we can implement AndAlso:
static Expression<Func<TSource, bool>> AndAlso<TSource>(
this Expression<Func<TSource, bool>> expr1,
Expression<Func<TSource, bool>> expr2)
{
// Create one expression that represent expr1 && expr2
// the input of expr1 is a TSource,
// the input of expr2 is a TSource,
// so the input of expr1 && expr2 is a TSource:
ParameterExpression inputParameter = Expression.Parameter(typeof(TSource));
// Visit the left part of the AND:
var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], inputParameter)
var left = leftVisitor.Visit(expr1.Body);
// Visit the right part of the AND:
var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], inputParameter);
var right = rightVisitor.Visit(expr2.Body);
// Combine left and right with a binary expression representing left && right:
var andExpression = Expression.AndAlso(left, right);
// return the lambda expression that takes one Tsource as input and returns the AND:
var lambda = Expression.Lambda<Func<TSource, bool>>(andExpression, new[] {parameter});
return lambda;
}
Usage:
Expression<Func<Student, bool>> expr1 = student => student.City == "Birmingham";
Expression<Func<Student, bool>> expr2 = student => student.Gender == Gender.Male;
var brummyMaleStudents = dbContext.Students.Where(expr1.AndAlso(expr2));
Another example:
IQueryable<int> numbers = Enumerable.Range(0, 100).Asqueryable();
// two expressions: one that filters 0, 2, 4, 6, 8, ...
// and one that filters 0, 3, 6, 9, 12, ...
Expression<Func<int, bool>> exprEvenNumbers = x => x % 2 == 0;
Expression<Func<int, bool>> exprTrifolds = x => x % 3 == 0;
// the AND of these two expressions should filter 0, 6, 12, 18, ...
Expression<Func<int, bool>> exprSixfolds = exprEvenNumbers.AndAlso(exprTrifolds);
// Test this
IQueryable<int> sixfolds = numbers.Where(exprSixfolds);
IEnumerable<int> results = sixfolds.Take(10).AsEnumerable();
foreach (int i in results)
{
Console.WriteLine(i);
}
This will print the numbers 0, 6, 12, 18, ...
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 |