合并两个表达式(Expression <Func <T,bool >>)


248

我有两个类型的表达式,Expression<Func<T, bool>>我想对它们进行“或”,“或”或“非”运算,并获得相同类型的新表达式

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

8
我从Google获得的非常有用的文章:LINQ到实体:组合谓词
Thomas CG de Vilhena 2014年

Answers:


330

好了,您可以使用Expression.AndAlso/ OrElseetc组合逻辑表达式,但是问题是参数;您ParameterExpression在expr1和expr2中使用相同的工具吗?如果是这样,则更容易:

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

嗨,马克,我在上面的第一个代码块中尝试了您的第一个建议,但是当我将“ lambda”表达式<func <T,bool >>传递给Where方法时,出现错误,指出参数为超出范围?任何想法?欢呼声
Andy

1
+1通用版本的工作方式就像一个咒语,我使用And而不是andalso,我认为linq to sql不支持andalso?
马斯洛

2
@Maslow -这里是一个可以内联树木保存调用重写:stackoverflow.com/questions/1717444/...
马克Gravell

1
@Aron现在查看日期:.NET框架visitor(ExpressionVisitor当时不存在;我有一个关于stackoverflow的相关示例,它是从一个相似的日期开始的,它手动实现了访问者:这是很多代码。
Marc Gravell

1
@MarkGravell,我正在使用您的第一个解决方案来组合我的表达式,即使在entityframework中,一切都可以正常工作,那么使用最后一个解决方案的好处是什么?
约翰尼

60

您可以使用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结合的解决方案。主要道具,有点简洁和令人困惑,但是可以用,所以我不会抱怨。荣誉亚当:-)
VulgarBinary

这是一个很好的解决方案。
亚伦·斯坦巴克

亚当,这解决了我使用SharePoint客户端对象模型的Linq提供程序时遇到的一个非常烦人的问题-感谢您发布它。
Christopher McAtackney 2014年

这对我有用!我搜索了各种各样的解决方案以及谓词生成器,但直到此为止,都没有奏效。谢谢!
tokyo0709 '16

这是一段很棒的代码。我找不到调整代码,复制粘贴的地方:)
Tolga Evcimen

19

如果提供者不支持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" }));
}

1
这解决了我的特殊问题,其中其他解决方案导致了相同的异常。谢谢。
肖恩·威尔逊

1
这是一个很好的解决方案。
亚伦·斯坦巴克

3

这里没有什么新但结婚这个答案这个答案并稍微重构它,这样即使我明白这是怎么回事:

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

我在理解这个概念时遇到了困难,您将其他几个答案融为一体对我来说很有帮助。谢谢!
Kevin M. Lapio

2

我需要达到相同的结果,但要使用更通用的方法(因为类型未知)。多亏了马克的回答,我终于弄清了我想要达到的目标:

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

1

我建议对PredicateBuilderExpressionVisitor解决方案进行另一项改进。我给它打电话了UnifyParametersByName,您可以在我的MIT库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

我认为这很好,不是吗?

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

1
例如,这不能在Linq to SQL中使用
Romain Vergnory
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.