Splitting an expression in C # with Expression.AndAlso () throws an exception

In my project written in C #, I found a HUGE predicate that is used in this linq method:

public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);

      

this predicate works great, but it's so hard that I struggled a lot before figuring it out. I would like to make it readable. So I wrote some expressions.

But I have a runtime exception like this: The parameter "scary" was not bound in the specified LINQ to Entities query expression "Exception

I wanted to try the answer, but I still don't understand why the (c) parameter is the problem:

// in a method
Func<string, Expression<Func<TEntity, bool>>> expr1 = (query) => return (c) => ... ;
Func<string, Expression<Func<TEntity, bool>>> expr2 = (query) => return  (c) => ... ;

var expr = Expression.AndAlso(expr1("a string").Body, expr2("same string").Body);

return Expression.Lambda<Func<TEntity, bool>>(expr , expr1("a string").Parameters[0]);

      

My question is to understand why this exception occurs when I finally returned to the huge predicate.

+1


source to share


1 answer


Because when you see one parameter c

, in truth, there are two different parameters c

(call them c1

and c2

). So when you combine two expressions:

c1 => c1.Something && c2.SomethingElse;

      

And the CLR gets mad because it can't find it c2

.

Worse, when you've written your code, you have three c

!

c3 => c1.Something && c2.SomethingElse

      

This is because you are rebuilding expr1("a string")

(in Expression.AndAlso(expr1("a string").Body

and in expr1("a string").Parameters[0]

) twice !

You should have kept it!



var temp1 = expr1("a string");
var temp2 = expr2("same string");

var expr = Expression.AndAlso(temp1.Body, temp2.Body);

// now fix the expr so that it uses the parameters of temp1

return Expression.Lambda<Func<TEntity, bool>>(expr, temp1.Parameters);

      

To give a clear example:

var temp1a = expr1("a string");
var temp1b = expr1("a string");
var temp2 = expr2("same string");

Console.WriteLine(temp1a.Parameters[0] == temp1b.Parameters[0]); // False
Console.WriteLine(temp1a.Parameters[0] == temp2.Parameters[0]); // False

      

Now ... my version of parameter replacement:

public class SimpleParameterReplacer : ExpressionVisitor
{
    public readonly ReadOnlyCollection<ParameterExpression> From;
    public readonly ReadOnlyCollection<ParameterExpression> To;

    public SimpleParameterReplacer(ParameterExpression from, ParameterExpression to)
        : this(new[] { from }, new[] { to })
    {
    }

    public SimpleParameterReplacer(IList<ParameterExpression> from, IList<ParameterExpression> to)
    {
        if (from == null || from.Any(x => x == null))
        {
            throw new ArgumentNullException("from");
        }

        if (to == null || to.Any(x => x == null))
        {
            throw new ArgumentNullException("to");
        }

        if (from.Count != to.Count)
        {
            throw new ArgumentException("to");
        }

        // Note that we should really clone from and to... But we will
        // ignore this!
        From = new ReadOnlyCollection<ParameterExpression>(from);
        To = new ReadOnlyCollection<ParameterExpression>(to);
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        int ix = From.IndexOf(node);

        if (ix != -1)
        {
            node = To[ix];
        }

        return base.VisitParameter(node);
    }
}

      

You can use to change one parameter or an array of parameters ... You can use it like:

var temp1 = expr1("a string");
var temp2 = expr2("same string");

var expr = Expression.AndAlso(temp1.Body, temp2.Body);
expr = new SimpleParameterReplacer(temp2.Parameters, temp1.Parameters).Visit(expr);

return Expression.Lambda<Func<TEntity, bool>>(expr, temp1.Parameters);

      

+2


source







All Articles