结合两个expression式(Expression <Func <T,bool >>)

我有两个Expression<Func<T, bool>> ,我想把它们作为OR,AND或NOT,并得到一个相同types的新expression式

 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 

那么,你可以使用Expression.AndAlso / OrElse等来组合逻辑expression式,但问题是参数; 你在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; } 

在某处,我有一些代码重写了一个expression式树来代替节点,以去除Invoke的需要,但是这个代码非常冗长(我不记得我把它留在了哪里…)


select最简单路线的通用版本:

 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开始。 ExpressionVistor类允许您构buildEF安全的expression式。

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

您可以使用Expression.AndAlso / OrElse来组合逻辑expression式,但必须确保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来replace参数:

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

Joe Albahari(C#3.0在Nutshell和LINQPad中的作者)编写了一个名为PredicateBuilder的实用程序,它可以用于AND和ORfunction。

http://www.albahari.com/nutshell/predicatebuilder.aspx

虽然它的function是开源的,所以你可以看看它是如何工作的。

如果您的提供程序不支持Invoke并且需要组合两个expression式,则可以使用ExpressionVisitor通过第一个expression式中的参数replace第二个expression式中的参数。

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

我build议对PredicateBuilderExpressionVisitor解决scheme进行一次改进。 我把它称为UnifyParametersByName ,你可以在我的MIT库中find它: LinqExprHelper 。 它允许组合任意的lambdaexpression式。 通常问题是关于谓词expression的问题,但这个想法也扩展到投影expression式。

下面的代码使用了一个方法ExprAdres ,它使用内联lambda来创build一个复杂的参数化expression式。 这个复杂的expression式只被编码一次,然后重用,感谢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); } } 

这是子expression式的build筑代码:

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

我试图实现的是执行参数化查询,而不需要复制粘贴,并能够使用内联lambdas,这是非常漂亮的。 没有所有这些帮助expression的东西,我将被迫一次创build完整的查询。

我需要达到相同的结果,但使用更通用的东西(因为types是未知的)。 感谢marc的回答,我终于想出了我想要达到的目标:

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

我觉得这样很好,不是吗?

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