【问题标题】:Combining two expressions (Expression<Func<T, bool>>)组合两个表达式 (Expression<Func<T, bool>>)
【发布时间】:2010-10-02 05:18:39
【问题描述】:

我有两个 Expression&lt;Func&lt;T, bool&gt;&gt; 类型的表达式,我想对这些表达式进行 OR、AND 或 NOT 并得到一个相同类型的新表达式

Expression<Func<T, bool>> expr1;
Expression<Func<T, bool>> expr2;

...

//how to do this (the code below will obviously not work)
Expression<Func<T, bool>> andExpression = expr AND expr2

【问题讨论】:

    标签: c# linq lambda expression


    【解决方案1】:

    嗯,你可以用Expression.AndAlso/OrElse等组合逻辑表达式,但问题是参数;你在 expr1 和 expr2 中使用相同的 ParameterExpression 吗?如果是这样,那就更容易了:

    var body = Expression.AndAlso(expr1.Body, expr2.Body);
    var lambda = Expression.Lambda<Func<T,bool>>(body, expr1.Parameters[0]);
    

    这也适用于否定单个操作:

    static Expression<Func<T, bool>> Not<T>(
        this Expression<Func<T, bool>> expr)
    {
        return Expression.Lambda<Func<T, bool>>(
            Expression.Not(expr.Body), expr.Parameters[0]);
    }
    

    否则,根据 LINQ 提供程序,您也许可以将它们与 Invoke 结合使用:

    // OrElse is very similar...
    static Expression<Func<T, bool>> AndAlso<T>(
        this Expression<Func<T, bool>> left,
        Expression<Func<T, bool>> right)
    {
        var param = Expression.Parameter(typeof(T), "x");
        var body = Expression.AndAlso(
                Expression.Invoke(left, param),
                Expression.Invoke(right, param)
            );
        var lambda = Expression.Lambda<Func<T, bool>>(body, param);
        return lambda;
    }
    

    在某个地方,我有一些代码重写了表达式树替换节点以消除对Invoke 的需要,但它很长(我不记得我把它放在哪里了......)


    选择最简单路线的通用版本:

    static Expression<Func<T, bool>> AndAlso<T>(
        this Expression<Func<T, bool>> expr1,
        Expression<Func<T, bool>> expr2)
    {
        // need to detect whether they use the same
        // parameter instance; if not, they need fixing
        ParameterExpression param = expr1.Parameters[0];
        if (ReferenceEquals(param, expr2.Parameters[0]))
        {
            // simple version
            return Expression.Lambda<Func<T, bool>>(
                Expression.AndAlso(expr1.Body, expr2.Body), param);
        }
        // otherwise, keep expr1 "as is" and invoke expr2
        return Expression.Lambda<Func<T, bool>>(
            Expression.AndAlso(
                expr1.Body,
                Expression.Invoke(expr2, param)), param);
    }
    

    从 .NET 4.0 开始,ExpressionVisitor 类允许您构建 EF 安全的表达式。

        public static Expression<Func<T, bool>> AndAlso<T>(
            this Expression<Func<T, bool>> expr1,
            Expression<Func<T, bool>> expr2)
        {
            var parameter = Expression.Parameter(typeof (T));
    
            var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
            var left = leftVisitor.Visit(expr1.Body);
    
            var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
            var right = rightVisitor.Visit(expr2.Body);
    
            return Expression.Lambda<Func<T, bool>>(
                Expression.AndAlso(left, right), parameter);
        }
    
    
    
        private class ReplaceExpressionVisitor
            : ExpressionVisitor
        {
            private readonly Expression _oldValue;
            private readonly Expression _newValue;
    
            public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
            {
                _oldValue = oldValue;
                _newValue = newValue;
            }
    
            public override Expression Visit(Expression node)
            {
                if (node == _oldValue)
                    return _newValue;
                return base.Visit(node);
            }
        }
    

    【讨论】:

    • +1 通用版本就像一个魅力,我用 And 而不是 andalso,我认为 linq to sql 不支持 andalso?
    • @Maslow - 这是一个可以内联树以保存调用的重写器:stackoverflow.com/questions/1717444/…
    • @Aron 现在查看日期:.NET 框架访问者 (ExpressionVisitor) 当时不存在;我有一个关于 stackoverflow 的相关示例,它来自一个类似的日期,它手动实现了访问者:它是一个 lot 代码。
    • @MarkGravell,我正在使用您的第一个解决方案来组合我的表达式,即使在实体框架中一切正常,那么使用最后一个解决方案有什么好处?
    • @user3071284 因为解析表达式树是困难,所以有时我们需要帮助他们;试试底部的表达式访问者版本
    【解决方案2】:

    您可以使用 Expression.AndAlso / OrElse 组合逻辑表达式,但您必须确保 ParameterExpressions 相同。

    我在使用 EF 和 PredicateBuilder 时遇到了问题,所以我自己制作了一个,而不使用 Invoke,我可以这样使用:

    var filterC = filterA.And(filterb);
    

    我的 PredicateBuilder 的源代码:

    public static class PredicateBuilder {
    
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b) {    
    
            ParameterExpression p = a.Parameters[0];
    
            SubstExpressionVisitor visitor = new SubstExpressionVisitor();
            visitor.subst[b.Parameters[0]] = p;
    
            Expression body = Expression.AndAlso(a.Body, visitor.Visit(b.Body));
            return Expression.Lambda<Func<T, bool>>(body, p);
        }
    
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> a, Expression<Func<T, bool>> b) {    
    
            ParameterExpression p = a.Parameters[0];
    
            SubstExpressionVisitor visitor = new SubstExpressionVisitor();
            visitor.subst[b.Parameters[0]] = p;
    
            Expression body = Expression.OrElse(a.Body, visitor.Visit(b.Body));
            return Expression.Lambda<Func<T, bool>>(body, p);
        }   
    }
    

    以及用于替换 lambda 中的参数的实用程序类:

    internal class SubstExpressionVisitor : System.Linq.Expressions.ExpressionVisitor {
            public Dictionary<Expression, Expression> subst = new Dictionary<Expression, Expression>();
    
            protected override Expression VisitParameter(ParameterExpression node) {
                Expression newValue;
                if (subst.TryGetValue(node, out newValue)) {
                    return newValue;
                }
                return node;
            }
        }
    

    【讨论】:

    • 此解决方案是唯一允许我将 x => x.Property == Value 与 arg => arg.Property2 == Value 结合使用的解决方案。主要道具,有点简洁和令人困惑,但它有效,所以我不会抱怨。荣誉亚当 :-)
    • 这是一个很好的解决方案。
    • Adam,这解决了我在使用 SharePoint 客户端对象模型的 Linq 提供程序时遇到的一个非常烦人的问题 - 感谢您发布它。
    • 这对我有用!我已经搜索了各种解决方案以及谓词构建器,但在此之前没有任何效果。谢谢!
    • 这是一段很棒的代码。我找不到调整代码的地方,复制粘贴就是这样:)
    【解决方案3】:

    如果您的提供程序不支持 Invoke 并且您需要组合两个表达式,您可以使用 ExpressionVisitor 将第二个表达式中的参数替换为第一个表达式中的参数。

    class ParameterUpdateVisitor : ExpressionVisitor
    {
        private ParameterExpression _oldParameter;
        private ParameterExpression _newParameter;
    
        public ParameterUpdateVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
        {
            _oldParameter = oldParameter;
            _newParameter = newParameter;
        }
    
        protected override Expression VisitParameter(ParameterExpression node)
        {
            if (object.ReferenceEquals(node, _oldParameter))
                return _newParameter;
    
            return base.VisitParameter(node);
        }
    }
    
    static Expression<Func<T, bool>> UpdateParameter<T>(
        Expression<Func<T, bool>> expr,
        ParameterExpression newParameter)
    {
        var visitor = new ParameterUpdateVisitor(expr.Parameters[0], newParameter);
        var body = visitor.Visit(expr.Body);
    
        return Expression.Lambda<Func<T, bool>>(body, newParameter);
    }
    
    [TestMethod]
    public void ExpressionText()
    {
        string text = "test";
    
        Expression<Func<Coco, bool>> expr1 = p => p.Item1.Contains(text);
        Expression<Func<Coco, bool>> expr2 = q => q.Item2.Contains(text);
        Expression<Func<Coco, bool>> expr3 = UpdateParameter(expr2, expr1.Parameters[0]);
    
        var expr4 = Expression.Lambda<Func<Recording, bool>>(
            Expression.OrElse(expr1.Body, expr3.Body), expr1.Parameters[0]);
    
        var func = expr4.Compile();
    
        Assert.IsTrue(func(new Coco { Item1 = "caca", Item2 = "test pipi" }));
    }
    

    【讨论】:

    • 这解决了我的特殊问题,其他解决方案导致相同的异常。谢谢。
    • 这是一个很好的解决方案。
    【解决方案4】:

    这里没有什么新东西,只是将this answerthis answer 结合在一起并稍微重构了它,以便我也能理解发生了什么:

    public static class ExpressionExtensions
    {
        public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
        {
            ParameterExpression parameter1 = expr1.Parameters[0];
            var visitor = new ReplaceParameterVisitor(expr2.Parameters[0], parameter1);
            var body2WithParam1 = visitor.Visit(expr2.Body);
            return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, body2WithParam1), parameter1);
        }
    
        private class ReplaceParameterVisitor : ExpressionVisitor
        {
            private ParameterExpression _oldParameter;
            private ParameterExpression _newParameter;
    
            public ReplaceParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
            {
                _oldParameter = oldParameter;
                _newParameter = newParameter;
            }
    
            protected override Expression VisitParameter(ParameterExpression node)
            {
                if (ReferenceEquals(node, _oldParameter))
                    return _newParameter;
    
                return base.VisitParameter(node);
            }
        }
    }
    

    【讨论】:

    • 我很难理解这个概念,而您对其他几个答案的融合帮助我点击了它。谢谢!
    【解决方案5】:

    我需要获得相同的结果,但使用更通用的东西(因为类型未知)。感谢马克的回答,我终于弄清楚了我想要实现的目标:

        public static LambdaExpression CombineOr(Type sourceType, LambdaExpression exp, LambdaExpression newExp) 
        {
            var parameter = Expression.Parameter(sourceType);
    
            var leftVisitor = new ReplaceExpressionVisitor(exp.Parameters[0], parameter);
            var left = leftVisitor.Visit(exp.Body);
    
            var rightVisitor = new ReplaceExpressionVisitor(newExp.Parameters[0], parameter);
            var right = rightVisitor.Visit(newExp.Body);
    
            var delegateType = typeof(Func<,>).MakeGenericType(sourceType, typeof(bool));
            return Expression.Lambda(delegateType, Expression.Or(left, right), parameter);
        }
    

    【讨论】:

      【解决方案6】:

      我建议对 PredicateBuilderExpressionVisitor 解决方案进行进一步改进。我称之为UnifyParametersByName,你可以在我的麻省理工图书馆找到它:LinqExprHelper。它允许组合任意 lambda 表达式。通常会问关于谓词表达式的问题,但这个想法也扩展到投影表达式。

      下面的代码使用了ExprAdres 方法,它使用内联 lambda 创建了一个复杂的参数化表达式。由于LinqExprHelper 迷你库,这个复杂的表达式只被编码一次,然后被重复使用。

      public IQueryable<UbezpExt> UbezpFull
      {
          get
          {
              System.Linq.Expressions.Expression<
                  Func<UBEZPIECZONY, UBEZP_ADRES, UBEZP_ADRES, UbezpExt>> expr =
                  (u, parAdrM, parAdrZ) => new UbezpExt
                  {
                      Ub = u,
                      AdrM = parAdrM,
                      AdrZ = parAdrZ,
                  };
      
              // From here an expression builder ExprAdres is called.
              var expr2 = expr
                  .ReplacePar("parAdrM", ExprAdres("M").Body)
                  .ReplacePar("parAdrZ", ExprAdres("Z").Body);
              return UBEZPIECZONY.Select((Expression<Func<UBEZPIECZONY, UbezpExt>>)expr2);
          }
      }
      

      这是子表达式的构建代码:

      public static Expression<Func<UBEZPIECZONY, UBEZP_ADRES>> ExprAdres(string sTyp)
      {
          return u => u.UBEZP_ADRES.Where(a => a.TYP_ADRESU == sTyp)
              .OrderByDescending(a => a.DATAOD).FirstOrDefault();
      }
      

      我试图实现的是执行参数化查询,而无需复制粘贴,并且能够使用内联 lambda,它们非常漂亮。如果没有所有这些帮助表达式的东西,我将不得不一次性创建整个查询。

      【讨论】:

        【解决方案7】:

        我在这里结合了一些漂亮的答案,以便轻松支持更多表达式运算符

        这是基于@Dejan 的回答,但现在添加 OR 也很容易。我选择不公开 Combine 函数,但您可以这样做更加灵活。

        public static class ExpressionExtensions
        {
            public static Expression<Func<T, bool>> AndAlso<T>(this Expression<Func<T, bool>> leftExpression,
                Expression<Func<T, bool>> rightExpression) =>
                Combine(leftExpression, rightExpression, Expression.AndAlso);
        
            public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> leftExpression,
                Expression<Func<T, bool>> rightExpression) =>
                Combine(leftExpression, rightExpression, Expression.Or);
        
            public static Expression<Func<T, bool>> Combine<T>(Expression<Func<T, bool>> leftExpression, Expression<Func<T, bool>> rightExpression, Func<Expression, Expression, BinaryExpression> combineOperator)
            {
                var leftParameter = leftExpression.Parameters[0];
                var rightParameter = rightExpression.Parameters[0];
        
                var visitor = new ReplaceParameterVisitor(rightParameter, leftParameter);
        
                var leftBody = leftExpression.Body;
                var rightBody = visitor.Visit(rightExpression.Body);
        
                return Expression.Lambda<Func<T, bool>>(combineOperator(leftBody, rightBody), leftParameter);
            }
        
            private class ReplaceParameterVisitor : ExpressionVisitor
            {
                private readonly ParameterExpression _oldParameter;
                private readonly ParameterExpression _newParameter;
        
                public ReplaceParameterVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
                {
                    _oldParameter = oldParameter;
                    _newParameter = newParameter;
                }
        
                protected override Expression VisitParameter(ParameterExpression node)
                {
                    return ReferenceEquals(node, _oldParameter) ? _newParameter : base.VisitParameter(node);
                }
            }
        }
        

        用法没有改变,仍然是这样:

        Expression<Func<Result, bool>> noFilterExpression = item => filters == null;
        
        Expression<Func<Result, bool>> laptopFilterExpression = item => item.x == ...
        Expression<Func<Result, bool>> dateFilterExpression = item => item.y == ...
        
        var combinedFilterExpression = noFilterExpression.Or(laptopFilterExpression.AndAlso(dateFilterExpression));
            
        efQuery.Where(combinedFilterExpression);
        

        (这是一个基于我的实际代码的示例,但请阅读为伪代码)

        【讨论】:

        • 谢谢,我将它集成到我的代码中,但我没有尝试它是否有效:)
        【解决方案8】:

        我认为这很好,不是吗?

        Func<T, bool> expr1 = (x => x.Att1 == "a");
        Func<T, bool> expr2 = (x => x.Att2 == "b");
        Func<T, bool> expr1ANDexpr2 = (x => expr1(x) && expr2(x));
        Func<T, bool> expr1ORexpr2 = (x => expr1(x) || expr2(x));
        Func<T, bool> NOTexpr1 = (x => !expr1(x));
        

        【讨论】:

        • 这不能用于例如Linq to SQL
        猜你喜欢
        • 1970-01-01
        • 2019-11-26
        • 1970-01-01
        • 2021-08-25
        • 2013-03-09
        相关资源
        最近更新 更多