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

我有两个类型为Expression<Func<T, bool>>的表达式,我想取它们的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
138211 次浏览

好吧,你可以使用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);
}
}

如果您的提供程序不支持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" }));
}

你可以使用表达式。和also / OrElse组合逻辑表达式,但你必须确保参数表达式是相同的。

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

我觉得这样挺好的,不是吗?

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

我建议对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();
}

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

我需要实现相同的结果,但是使用一些更通用的东西(因为不知道类型)。多亏了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);
}

这里没有什么新内容,只是将这个答案这个答案结合起来,并稍微重构了一下,这样即使是我也能理解发生了什么:

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

我在这里结合了一些美丽的答案,使它成为可能轻松支持更多表达式运算符

这是基于@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);

(这是一个基于我的实际代码的示例,但将其视为伪代码)

using System;
using System.Linq.Expressions;


namespace Extensions
{




public class Example
{
//How to use it
public static void Main()
{
Expression<Func<string, bool>> expression1 = exp => true;
Expression<Func<string, bool>> expression2 = exp => false;


Expression<Func<string, bool>> expression3 = ExpressionExtensions.AndAlso(expression1, expression2);
Expression<Func<string, bool>> expression4 = ExpressionExtensions.OrElse(expression1, expression2);


Expression<Func<string, bool>> expression = ExpressionExtensions.AndAlso(expression3, expression4);
}
}




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


public static Expression<Func<T, bool>> OrElse<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.OrElse(expr1.Body, body2WithParam1), parameter1);
}


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)
{
if (ReferenceEquals(node, _oldParameter))
return _newParameter;


return base.VisitParameter(node);
}
}
}
}