简体   繁体   中英

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?

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 framwork.

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], parameter);
     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));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM