Node.JS
Node.JS

Reputation: 1568

Aggregate Linq Expressions: getting an exception of variable referenced from scope

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)

Upvotes: 1

Views: 358

Answers (1)

abatishchev
abatishchev

Reputation: 100268

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);

Upvotes: 2

Related Questions