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,

TheBoubou
  • 19,487
  • 54
  • 148
  • 236

1 Answers1

16

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)

Community
  • 1
  • 1
Honza Brestan
  • 10,637
  • 2
  • 32
  • 43
  • 1
    I saw Marc's code, but didn't work don't remember why. But I try agrain now – TheBoubou Dec 20 '12 at 08:52
  • 1
    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) – Honza Brestan Dec 20 '12 at 08:54
  • 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 :) – TheBoubou Dec 20 '12 at 09:00
  • 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. – Honza Brestan Dec 20 '12 at 09:18
  • 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, – TheBoubou Dec 20 '12 at 10:01
  • 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. – Honza Brestan Dec 20 '12 at 10:11
  • 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. – pasx Nov 06 '21 at 19:20