Combinando duas expressões (Expression <Func >)

Eu tenho duas expressões do tipo Expression<Func> e eu quero levar para OR, AND ou NOT destes e obter uma nova expressão do mesmo tipo

 Expression<Func> expr1; Expression<Func> expr2; ... //how to do this (the code below will obviously not work) Expression<Func> andExpression = expr AND expr2 

Bem, você pode usar Expression.AndAlso / OrElse etc para combinar expressões lógicas, mas o problema são os parâmetros; você está trabalhando com o mesmo ParameterExpression no expr1 e expr2? Se sim, é mais fácil:

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

Isso também funciona bem para negar uma única operação:

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

Caso contrário, dependendo do provedor do LINQ, você poderá combiná-los com o Invoke :

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

Em algum lugar, eu tenho algum código que reescreve uma tree de expressão substituindo nós para remover a necessidade de Invoke , mas é bastante demorado (e não me lembro onde deixei …)


Versão generalizada que escolhe a rota mais simples:

 static Expression> AndAlso( this Expression> expr1, Expression> 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>( Expression.AndAlso(expr1.Body, expr2.Body), param); } // otherwise, keep expr1 "as is" and invoke expr2 return Expression.Lambda>( Expression.AndAlso( expr1.Body, Expression.Invoke(expr2, param)), param); } 

A partir de .net 4.0. Existe a class ExpressionVistor que permite criar expressões seguras para EF.

  public static Expression> AndAlso( this Expression> expr1, Expression> 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>( 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); } } 

Você pode usar Expression.AndAlso / OrElse para combinar expressões lógicas, mas você deve certificar-se de que os ParameterExpressions são os mesmos.

Eu estava tendo problemas com o EF e o PredicateBuilder então fiz o meu sem recorrer ao Invoke, que eu poderia usar assim:

 var filterC = filterA.And(filterb); 

Código-fonte para o meu PredicateBuilder:

 public static class PredicateBuilder { public static Expression> And(this Expression> a, Expression> 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>(body, p); } public static Expression> Or(this Expression> a, Expression> 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>(body, p); } } 

E a class utility para replace os parâmetros em um lambda:

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

Joe Albahari (autor de C # 3.0 in a Nutshell e LINQPad) escreveu um utilitário chamado PredicateBuilder que pode ser usado para funções AND e OR juntas.

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

Enquanto ele funciona em funções, ele é de código aberto para que você possa verificá-lo e ver como ele funciona.

Se o seu provedor não suporta Invoke e você precisa combinar duas expressões, você pode usar um ExpressionVisitor para replace o parâmetro na segunda expressão pelo parâmetro na primeira expressão.

 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> UpdateParameter( Expression> expr, ParameterExpression newParameter) { var visitor = new ParameterUpdateVisitor(expr.Parameters[0], newParameter); var body = visitor.Visit(expr.Body); return Expression.Lambda>(body, newParameter); } [TestMethod] public void ExpressionText() { string text = "test"; Expression> expr1 = p => p.Item1.Contains(text); Expression> expr2 = q => q.Item2.Contains(text); Expression> expr3 = UpdateParameter(expr2, expr1.Parameters[0]); var expr4 = Expression.Lambda>( Expression.OrElse(expr1.Body, expr3.Body), expr1.Parameters[0]); var func = expr4.Compile(); Assert.IsTrue(func(new Coco { Item1 = "caca", Item2 = "test pipi" })); } 

Eu sugiro mais uma melhoria para as soluções PredicateBuilder e ExpressionVisitor . Eu chamei de UnifyParametersByName e você pode encontrá-lo na minha biblioteca MIT: LinqExprHelper . Permite combinar expressões lambda arbitrárias. Geralmente, as perguntas são feitas sobre a expressão de predicado, mas essa ideia se estende também às expressões de projeção.

O código a seguir emprega um método ExprAdres que cria uma expressão parametrizada complicada, usando lambda embutido. Esta expressão complicada é codificada apenas uma vez e, em seguida, reutilizada graças à mini-biblioteca LinqExprHelper .

 public IQueryable UbezpFull { get { System.Linq.Expressions.Expression< Func> 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>)expr2); } } 

E este é o código de construção da subexpressão:

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

O que eu tentei alcançar foi realizar consultas parametrizadas sem necessidade de copiar e colar e com a capacidade de usar lambdas embutidos, que são tão bonitos. Sem todas essas expressões de ajuda, eu seria forçado a criar consultas inteiras de uma só vez.

Eu precisava alcançar os mesmos resultados, mas usando algo mais genérico (como o tipo não era conhecido). Graças à resposta de Marc, finalmente descobri o que eu estava tentando alcançar:

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

Eu acho que isso funciona bem, não é?

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