두 표현식 결합 (Expression <Func <T, bool >>)


249

나는 두 가지 유형의 표현 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

8
내가 Google에서 얻은 매우 유용한 게시물 : LINQ to Entities : 결합 술어
Thomas CG de Vilhena

답변:


331

Expression.AndAlso/ OrElseetc를 사용하여 논리식을 결합 할 수 있지만 문제는 매개 변수입니다. ParameterExpressionexpr1과 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부터는 ExpressionVisitorEF 안전 표현식을 작성할 수 있는 클래스가 있습니다.

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

Marc, 위의 첫 번째 코드 블록에서 첫 번째 제안을 시도했지만 "lambda"식 <func <T, bool >> 결과를 ​​전달할 때 Where 메서드가 발생하면 매개 변수가 범위를 벗어 났습니까? 어떤 생각? 건배
앤디

1
+1 일반화 된 버전은 매력처럼 작동합니다. 그리고 andand 대신에, linq 대신에 linq는 andand도 지원하지 않는다고 생각 했습니까?
Maslow

2
@Maslow-여기에 저장하기 위해 트리를 인라인 할 수있는 Rewriter가 있습니다 : stackoverflow.com/questions/1717444/…
Marc Gravell

1
@Aron은 이제 날짜를 살펴 봅니다. .NET Framework 방문자 ( ExpressionVisitor) 당시에 는 존재하지 않았습니다 . 방문자를 수동으로 구현하는 비슷한 날짜의 stackoverflow에 대한 관련 예제 가 있습니다. 많은 코드입니다.
Marc Gravell

1
@MarkGravell, 나는 첫 번째 솔루션을 사용하여 내 표현을 결합하고 있으며 엔티티 프레임 워크에서도 모든 것이 잘 작동하므로 마지막 솔루션을 사용하면 어떤 이점이 있습니까?
johnny 5

62

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

그리고 람다에서 매개 변수를 대체하는 유틸리티 클래스 :

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 == arg => arg.Property2 == Value와 결합 된 값을 가질 수있는 유일한 솔루션이었습니다. 주요 소품, 약간 간결하고 혼란 스럽지만 작동하므로 불만을 제기하지 않습니다. Kudos Adam :-)
VulgarBinary

이것은 훌륭한 솔루션입니다.
Aaron Stainback

Adam, 이것은 SharePoint Client Object 모델의 Linq 공급자를 사용하면서 발생했던 매우 성가신 문제를 해결했습니다. 게시 해 주셔서 감사합니다.
Christopher McAtackney

이것은 나를 위해 일했다! 나는 술어 빌더뿐만 아니라 다양한 솔루션을 검색했지만 이것까지 아무것도 작동하지 않았습니다. 감사합니다!
tokyo0709

이것은 훌륭한 코드입니다. 내가 복사 - 붙여 넣기 코드를 조정하는 장소를 찾을 수 없습니다 그것은 : 그것 뿐이다
톨가 Evcimen

19

공급자가 호출을 지원하지 않고 두 식을 결합해야하는 경우 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
이것은 다른 솔루션으로 동일한 예외가 발생하는 특정 문제를 해결했습니다. 감사.
Shaun Wilson

1
이것은 훌륭한 솔루션입니다.
Aaron Stainback

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

나는 동일한 결과를 달성해야했지만 더 일반적인 것을 사용했습니다 (유형이 알려지지 않았으므로). 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);
    }

1

PredicateBuilderExpressionVisitor솔루션에 한 가지 더 개선을 제안 합니다. 나는 그것을 호출 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();
}

내가 달성하려고했던 것은 복사하여 붙여 넣을 필요없이 인라인 람다를 사용할 수있는 매개 변수가없는 쿼리를 수행하는 것입니다. 이러한 도우미 표현이 없으면 전체 쿼리를 한 번에 만들어야합니다.


-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에서 SQL로 사용할 수 없습니다.
Romain Vergnory
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.