두 가지 표현식 결합 (expression >)
-
19-08-2019 - |
문제
두 가지 유형 표현이 있습니다 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
해결책
글쎄, 당신은 사용할 수 있습니다 Expression.AndAlso
/ OrElse
논리적 표현을 결합하는 등은 문제가 매개 변수입니다. 당신은 같은 일을하고 있습니까? 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;
}
어딘가에, 나는 Expression-Tree를 교체하여 노드를 다시 작성하여 필요한 코드를 얻었습니다. 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에서 시작합니다. EF 안전한 표현식을 구축 할 수있는 ExpressionVistor 클래스가 있습니다.
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를 사용하여 논리적 표현식을 결합 할 수 있지만 ParameterExpressions가 동일해야합니다.
나는 ef와 the에 어려움을 겪고 있었다 술어 빌더 그래서 나는 다음과 같이 사용할 수 있도록 호출에 의지하지 않고 내 자신을 만들었습니다.
var filterC = filterA.And(filterb);
내 PRECTICEBUILDER의 소스 코드 :
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);
}
}
그리고 람다에서 매개 변수를 대체하는 유틸리티 클래스 :
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의 저자와 LinqPad)는 함께 사용하거나 기능 할 수있는 PrescticateBuilder라는 유틸리티를 썼습니다.
http://www.albahari.com/nutshell/predicatebuilder.aspx
함수에서 작동하는 동안 오픈 소스이므로 확인하고 작동 방식을 확인할 수 있습니다.
제공자가 Invoke를 지원하지 않고 두 표현식을 결합 해야하는 경우, 표현식 범위를 사용하여 첫 번째 표현식의 매개 변수로 두 번째 표현식의 매개 변수를 대체 할 수 있습니다.
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" }));
}
나는 한 번 더 개선을 제안한다 술어 빌더 그리고 ExpressionVisitor
솔루션. 나는 그것을 불렀다 UnifyParametersByName
MIT 라이브러리에서 찾을 수 있습니다. Linqexprhelper. 임의의 람다 표현식을 결합 할 수 있습니다. 일반적으로 질문은 술어 표현에 대한 질문이 있지만이 아이디어는 투영 표현으로 확장됩니다.
다음 코드는 메소드를 사용합니다 ExprAdres
인라인 람다를 사용하여 복잡한 매개 변수화 된 식을 만듭니다. 이 복잡한 표현은 한 번만 코딩 된 다음 재사용됩니다. 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();
}
내가 달성하려고 시도한 것은 복사 할 필요없이 매개 변수화 된 쿼리를 수행하고 인라인 람다를 사용할 수있는 능력이있는 것이 었습니다. 이 모든 도우미 발현이 없으면 한 번에 전체 쿼리를 만들어야합니다.
동일한 결과를 얻을 필요가 있었지만 (유형이 알려지지 않은 것처럼) 더 일반적인 것을 사용했습니다. 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));