1

I have a list of expressions of type: Expression<Func<Person, bool>> and I want to aggregate them and then compile the aggregated result into a single Func<Person, bool>. I was able to create the aggregated expression but the part to compile the result aggregated expression throws an exception. Any help would be appreciated. Thank you.

Expression<Func<Person, bool>> expr1 = x => x.Age > 10;
Expression<Func<Person, bool>> expr2 = x => x.LastName == "some firstname";
Expression<Func<Person, bool>> expr3 = x => x.FirstName == "some lastname";
Expression<Func<Person, bool>> expr4 = x => x.Initial == 'a';
Expression<Func<Person, bool>> expr5 = x => x.DateOfBirth == DateTime.Now;
Expression<Func<Person, bool>> expr6 = x => x.Height > 10;

var exprList = new List<Expression<Func<Person, bool>>>()
{
    expr1, expr2, expr3, expr4, expr5
};


var list = exprList
        .Select(x => x.Body)
        .Aggregate(Expression.AndAlso);

// this works, apparently?!
var aggregatedExpression = Expression.Lambda<Func<Person, bool>>(list, Expression.Parameter(typeof(Person), "x"));

// fails here! it cannot compile
var result = aggregatedExpression.Compile();

This is the exception:

Unhandled Exception: System.InvalidOperationException: variable 'x' of type 'TestAggregateExpression.Person' referenced from scope '', but it is not defined

at System.Linq.Expressions.Compiler.VariableBinder.Reference(ParameterExpression node, VariableStorageKind storage)

Community
  • 1
  • 1
Node.JS
  • 1,042
  • 6
  • 44
  • 114

1 Answers1

2

I believe you need to visit all expressions in the list and to replace the parameter. Use this helper:

internal sealed class ParameterReplacer : ExpressionVisitor
{
    private readonly ParameterExpression _param;

    private ParameterReplacer(ParameterExpression param)
    {
        _param = param;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return node.Type == _param.Type ?
            base.VisitParameter(_param) : // replace
            node; // ignore
    }

    public static T Replace<T>(ParameterExpression param, T exp) where T : Expression
    {
        return (T)new ParameterReplacer(param).Visit(exp);
    }
}

Usage:

var param = Expression.Parameter(typeof(Person), "x"); // I'd use 'p' by the way
exp = ParameterReplacer.Replace(param, exp);

In your case:

var list = exprList.Select(x => x.Body)
                   .Select(exp => ParameterReplacer.Replace(param, exp))
                   .Aggregate(Expression.AndAlso);
abatishchev
  • 98,240
  • 88
  • 296
  • 433
  • I updated my answer with a usage example. Please try and let me know. If it works then you're facing the same issue and I did before. And I'll try to explain. – abatishchev Feb 12 '18 at 23:58
  • @Node.JS: if I remember correctly, this exception is caused by the fact that spite parameter looks the same but the context of this parameter is different. So you need to unify it across all expressions where you're reusing it. – abatishchev Feb 13 '18 at 00:02
  • See https://stackoverflow.com/questions/30556911/variable-of-type-referenced-from-scope-but-it-is-not-defined – abatishchev Feb 13 '18 at 00:03
  • Still no luck. https://gist.github.com/amir734jj/cfd59ee36f6b84d33138cca4722df4b5 – Node.JS Feb 13 '18 at 00:03
  • Pass same `param` to `Expression.Lambda` – abatishchev Feb 13 '18 at 00:13