Объединение выражений c # - PullRequest
       32

Объединение выражений c #

4 голосов
/ 06 октября 2019

Мне нужно объединить два выражения (с оператором or)

Мой код:

var items = new List<Item>
{
    new Item { Color = "Black", Categories = new List<string> { "cat1", "cat2" } },
    new Item { Color = "Red", Categories = new List<string> { "cat3" } },
    new Item { Color = "White", Categories = new List<string> { "cat1" } }
};

var categories = new List<string> { "cat2", "cat3" };

Expression<Func<Item, bool>> func1 = (x1) => x1.Color == "Black";
Expression<Func<Item, bool>> func2 = (x2) => x2.Categories.Any(y => categories.Where(z => z == y).Any());
Expression<Func<Item, bool>> fullExpression = Expression.Lambda<Func<Item, bool>>(
        Expression.Or(func1.Body, func2.Body), func1.Parameters.Single());

var result = items.AsQueryable().Where(fullExpression);
// result should be like this
// items.Where(x => (x.Color == "Black") || x.Categories.Any(y => categories.Where(z => z == y).Any()))

Я получаю ошибку во время выполнения variable 'x2' of type 'Item' referenced from scope '', but it is not defined'

Iтакже пытался построить выражение с ExpressionVisitor.

Вот ExpressionVisitor:

internal class ParameterReplacer : ExpressionVisitor
{
    private readonly ParameterExpression _parameter;

    internal ParameterReplacer(ParameterExpression parameter)
    {
        _parameter = parameter;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return base.VisitParameter(_parameter);
    }
}

Вот как я использую его в коде:

Expression<Func<Item, bool>> func1 = (x1) => x1.Color == "Black";
Expression<Func<Item, bool>> func2 = (x2) => x2.Categories.Any(y => categories.Select(z => z == y).Any());
var paramExpr = Expression.Parameter(typeof(Item));
var exprBody = Expression.Or(func1.Body, func2.Body);
exprBody = (BinaryExpression)new ParameterReplacer(paramExpr).Visit(exprBody);
var finalExpr = Expression.Lambda<Func<Item, bool>>(exprBody, paramExpr);
var result = items.AsQueryable().Where(finalExpr);

В этомдело при создании ParameterReplacer получаю ошибку

System.InvalidOperationException: 'The operands for operator 'Equal' do not match the parameters of method 'op_Equality'.'

что я сделал не так?

Ответы [ 2 ]

4 голосов
/ 07 октября 2019

Ян Ньюсон совершенно прав, но если вам нужен код, то вы идете:)

Используя эти два класса, вы можете объединить два предиката. Я не придумал это, но немного улучшил / скорректировал его и заставил использовать тип Predicate вместо Func вместе с некоторыми более новыми языковыми функциями (оригинал был довольно старым, к сожалению, я не помню, где янашел).

internal class SubstExpressionVisitor : ExpressionVisitor
{
    private readonly Dictionary<Expression, Expression> _subst = new Dictionary<Expression, Expression>();

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (_subst.TryGetValue(node, out Expression newValue))
        {
            return newValue;
        }

        return node;
    }

    public Expression this[Expression original]
    {
        get => _subst[original];
        set => _subst[original] = value;
    }
}
public static class PredicateBuilder
{
    // you don't seem to need this but it's included for completeness sake
    public static Expression<Predicate<T>> And<T>(this Expression<Predicate<T>> a, Expression<Predicate<T>> b)
    {
        if (a == null)
            throw new ArgumentNullException(nameof(a));

        if (b == null)
            throw new ArgumentNullException(nameof(b));

        ParameterExpression p = a.Parameters[0];

        SubstExpressionVisitor visitor = new SubstExpressionVisitor();
        visitor[b.Parameters[0]] = p;

        Expression body = Expression.AndAlso(a.Body, visitor.Visit(b.Body));
        return Expression.Lambda<Predicate<T>>(body, p);
    }

    public static Expression<Predicate<T>> Or<T>(this Expression<Predicate<T>> a, Expression<Predicate<T>> b)
    {
        if (a == null)
            throw new ArgumentNullException(nameof(a));

        if (b == null)
            throw new ArgumentNullException(nameof(b));

        ParameterExpression p = a.Parameters[0];

        SubstExpressionVisitor visitor = new SubstExpressionVisitor();
        visitor[b.Parameters[0]] = p;

        Expression body = Expression.OrElse(a.Body, visitor.Visit(b.Body));
        return Expression.Lambda<Predicate<T>>(body, p);
    }
}

Вы можете использовать это так:

Expression<Predicate<Item>> func1 = (x1) => x1.Color == "Black";
Expression<Predicate<Item>> func2 = (x2) => x2.Categories.Any(y => categories.Select(z => z == y).Any());

Expression<Predicate<Item>> finalExpr = func1.Or(func2);

Возможно, вы хотите иметь в виду, что мой Or использует OrElse для внутреннего использованиятаким образом, второе выражение не будет оцениваться, если предыдущее значение равно true (OrElse похоже на ||, а не |). То же самое относится к And с AndAlso (AndAlso похоже на &&, а не &).
Еще одна вещь, которую следует отметить, это то, что вы можете легко заменить Predicate<T> на Func<T, bool>, если у вас естьиспользовать Func по некоторым причинам:)

4 голосов
/ 06 октября 2019

Это потому, что ваши два выражения (func1 и func2) ссылаются на два разных ParameterExpressions. То, что они одного типа, не означает, что они одинаковы.

Они должны быть точно такими же экземплярами ParameterExpression, чтобы это работало. Для этого вы можете использовать средство переписывания выражений, чтобы изменить одно из выражений: https://docs.microsoft.com/en-us/dotnet/api/system.linq.expressions.expressionvisitor?view=netframework-4.8

Я думаю, что вы должны иметь возможность использовать библиотеку, такую ​​как построитель предикатов, чтобы сделать то же самое более простым способом:

https://www.nuget.org/packages/PredicateBuilder/

РЕДАКТИРОВАТЬ:

Ваш код указан в правильных строках, но внесите следующие изменения:

Для посетителей:

internal class ParameterReplacer : ExpressionVisitor
{
    private readonly ParameterExpression _parameter;

    internal ParameterReplacer(ParameterExpression parameter)
    {
        _parameter = parameter;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return node;
    }
}

Для бита выполнения:

        Expression<Func<Item, bool>> func1 = (x1) => x1.Color == "Black";
        Expression<Func<Item, bool>> func2 = (x2) => x2.Categories.Any(y => categories.Select(z => z == y).Any());
        var paramExpr = func1.Parameters.Single();
        var expr2 = new ParameterReplacer(paramExpr).Visit(func1);
        var exprBody = Expression.Or(func1.Body, ((LambdaExpression)expr2).Body);

        var finalExpr = Expression.Lambda<Func<Item, bool>>(exprBody, paramExpr);

        var result = items.AsQueryable().Where(finalExpr)
            .ToList();
...