【问题标题】:Combining expressions c#组合表达式c#
【发布时间】:2019-10-06 20:28:36
【问题描述】:

我需要连接两个表达式(使用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'

我还尝试使用 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'.'

我做错了什么?

【问题讨论】:

  • @NineBerry ,当我尝试构建 fullExpression 时会出现问题。我还可以提供哪些详细信息?
  • 这正是它所说的。您引用了一些未知变量而不是在它的签名中定义它。在您的情况下,它是您的 request 变量。第二个表达式应该是:(x2, request) => .....
  • @Pyrejkee 一个可以复制和运行的完整示例,在您的情况下,您应该将示例简化到不起作用的最低限度(删除对 Item 和 request 的引用,它们是您自己的代码)或否则,如果没有它们就无法重现问题,请提供它们的实现
  • 我已经添加了一个答案。如果您需要任何进一步的帮助,请告诉我,我在表达式分析和重写方面经验丰富。

标签: c# expression expression-trees


【解决方案1】:

Ian Newson 是完全正确的,但如果你想要代码,那就去吧 :)

使用这两个类,您可以组合两个谓词。我没有想出它,但对其进行了一些改进/调整,并使它使用 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,因此如果前一个表达式被评估为真,则不会评估第二个表达式(OrElse 类似于||,而不是@ 987654331@)。 AndAndAlso 也是如此(AndAlso 类似于 &amp;&amp;,而不是 &amp;)。
另外需要注意的是,如果由于某种原因必须使用Func,您可以轻松地将Predicate&lt;T&gt; 替换为Func&lt;T, bool&gt; :)

【讨论】:

  • 我刚刚意识到,在这种情况下,我认为您关于 OrElse 的说法不正确。在 .net 中,方法调用的参数都在调用方法之前进行评估。
  • 参数是(因为它们也用在第一个谓词中)。但是如果第一个表达式的计算结果为真,则不会计算第二个表达式。 OrElse||OR|
【解决方案2】:

这是因为您的两个表达式(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();

【讨论】:

  • 我已尝试应用 ExpressionVisitor。请你重新检查我的答案,我已经更新了。
  • 既然你理解了表达的东西,你认为我的解决方案是一种更通用的方法(旨在可重用)?
  • @Joelius 看起来不错,但仅限于 ParameterExpression 问题。最终这类问题在几年前就已经解决了,所以没有理由为它编写自己的解决方案。
  • 有什么替代方案?使用那个nuget?当您可以在项目中弹出这几行时,您为什么要这样做?是否有任何未涵盖的内容(当然,除了它只针对Predicates)?我仍在学习表达式树,因此非常欢迎任何新的见解:)
  • @IanNewson PredicateBuilder 也不再维护,不建议将其用于项目 linqkit github.com/scottksmith95/LINQKit 是一个不错的选择。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-15
  • 1970-01-01
相关资源
最近更新 更多