AndAlso between several Expression<Func<T, bool>> : referenced from scope
Asked Answered
E

1

8

I have 3 predicates, I'd like make an AndAlso between. I found several sample on the board, but can't solve my problem.

These predicates are : Expression<Func<T, bool>>

I have this code :

Expression<Func<T, bool>> predicate1 = ......;
Expression<Func<T, bool>> predicate2 = ......;
Expression<Func<T, bool>> predicate3 = ......;

I create an extension method to make an "AndAlso" :

public static Expression<Func<T, bool>> AndAlso<T>(
    this Expression<Func<T, bool>> expr, 
    Expression<Func<T, bool>> exprAdd)
{
    var param = Expression.Parameter(typeof(T), "p");
    var predicateBody = Expression.AndAlso(expr.Body, exprAdd.Body);
    return Expression.Lambda<Func<T, bool>>(predicateBody, param);

    //Tried this too
    //var body = Expression.AndAlso(expr.Body, exprAdd.Body);
    //return Expression.Lambda<Func<T, bool>>(body, expr.Parameters[0]);
}

I use like this :

var finalPredicate = predicate1
    .AndAlso<MyClass>(predicate2)
    .AndAlso<MyClass>(predicate3);

The predicate look this : enter image description here

When I use in a query :

var res =  myListAsQueryable().Where(finalPredicate).ToList<MyClass>();

I get this error : variable 'p' of type 'BuilderPredicate.MyClass' referenced from scope '', but it is not defined

Could you tell me what's wrong ?

Thanks a lot,

Esparto answered 20/12, 2012 at 7:51 Comment(0)
G
17

The problem is creating a new parameter - you can do that, but if you simply assign it to the final lambda, there's no connection between your parameter and the original parameters in the provided expressions. Try changing param names of the expressions and then check the finalPredicate. You will see something like:

{p => (((x.Age == 42) AndAlso (y.Salary == 50)) AndAlso z.FirstName.StartsWith("foo"))}

The problem should be obvious now.

Marc Gravell suggest in this answer a general Expression.AndAlso, which is exactly what you need:

public static Expression<Func<T, bool>> AndAlso<T>(
    this Expression<Func<T, bool>> expr1,
    Expression<Func<T, bool>> expr2)
{
    // need to detect whether they use the same
    // parameter instance; if not, they need fixing
    ParameterExpression param = expr1.Parameters[0];
    if (ReferenceEquals(param, expr2.Parameters[0]))
    {
        // simple version
        return Expression.Lambda<Func<T, bool>>(
            Expression.AndAlso(expr1.Body, expr2.Body), param);
    }
    // otherwise, keep expr1 "as is" and invoke expr2
    return Expression.Lambda<Func<T, bool>>(
        Expression.AndAlso(
            expr1.Body,
            Expression.Invoke(expr2, param)), param);
}

(code by Marc, not me)

Gensler answered 20/12, 2012 at 8:42 Comment(7)
I saw Marc's code, but didn't work don't remember why. But I try agrain nowEsparto
Try it and let me know if it doesn't work - it worked for me (you only have to make sure it's accessible to your code, i.e. public or internal; I'll change that in my answer)Gensler
Yes that's work. Don't know what I did in my previous test. But to be honest, it's not very clear in my mind how all this work :)Esparto
Well you combine two expressions, each with its own independent parameter. If you only create a new parameter, the newly formed expression does not know how to pass it to its child expressions (because there may be more parameters, each expression can have different number of them etc.). Marc's solution makes sure that the parameters are either the same (for example when you combine an expression with itself) and if they aren't, it passes the parameter from expr1 to the expr2, so that when you invoke your new expression, the exact same parameter is fed to both child expressions.Gensler
just one more question. I created a method, this receive a list of predicate, I combine all these predicates with AndAlso extension methode. How can I manage if finalPredicate is null. I have to create 2 queries one where the finalPredicate is null and one when finalPredicate is not null ? Thanks,Esparto
In what case does the finalPredicate become null? Does it make sense for you for example return a default predicate (that for example always returns false) instead of null in such case? If yes, you will only have one case (outside the method); if not, you will have to make the check every time you call your method and act based on its result (e.g. with making 2 different queries, one for each case, as you mentioned). It hugely depends on your use cases and needs.Gensler
I suspect that the case where the parameter in each expression are reference equals is really a corner case and could be omitted in most applications.Whalen

© 2022 - 2024 — McMap. All rights reserved.