Kết hợp hai biểu thức (Biểu thức <Func <T, bool >>)


249

Tôi có hai biểu thức loại Expression<Func<T, bool>>và tôi muốn đưa đến OR, VÀ hoặc KHÔNG trong số này và nhận một biểu thức mới cùng loại

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
Bài đăng rất hữu ích tôi nhận được từ Google: LINQ to Entities: Kết hợp Người dự đoán
Thomas CG de Vilhena

Câu trả lời:


331

Chà, bạn có thể sử dụng Expression.AndAlso/ OrElseetc để kết hợp các biểu thức logic, nhưng vấn đề là các tham số; bạn có đang làm việc với ParameterExpressionexpr1 và expr2 không? Nếu vậy, nó dễ dàng hơn:

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

Điều này cũng hoạt động tốt để phủ nhận một hoạt động duy nhất:

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

Mặt khác, tùy thuộc vào nhà cung cấp LINQ, bạn có thể kết hợp chúng với 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;
}

Ở đâu đó, tôi đã có một số mã viết lại một nút biểu thức cây thay thế để loại bỏ nhu cầu Invoke, nhưng nó khá dài (và tôi không thể nhớ mình đã để nó ở đâu ...)


Phiên bản tổng quát chọn tuyến đường đơn giản nhất:

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

Bắt đầu từ .NET 4.0, có ExpressionVisitorlớp cho phép bạn xây dựng các biểu thức an toàn với 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);
        }
    }

Này Marc, tôi đã thử đề xuất đầu tiên của bạn, trong khối mã đầu tiên của bạn ở trên, nhưng khi tôi chuyển qua biểu thức "lambda" <func <T, bool >> dẫn đến một phương thức Where, tôi gặp lỗi khi nói tham số là ra khỏi phạm vi? bất kỳ ý tưởng? chúc mừng
andy

1
+1 phiên bản tổng quát hoạt động như một bùa mê, tôi đã sử dụng Và thay vì andalso, tôi nghĩ linq to sql không hỗ trợ andalso?
Maslow

2
@Maslow - đây là một trình viết lại có thể nội tuyến các cây để lưu Invoke: stackoverflow.com/questions/1717444/ chủ
Marc Gravell

1
@Aron bây giờ hãy xem ngày: khách truy cập .NET framework ( ExpressionVisitor) không tồn tại trước đó; Tôi có một ví dụ liên quan về stackoverflow từ một ngày tương tự nơi nó thực hiện thủ công khách truy cập: đó là rất nhiều mã.
Marc Gravell

1
@MarkGravell, tôi đang sử dụng giải pháp đầu tiên của bạn để kết hợp các biểu thức của mình và mọi thứ đều hoạt động tốt ngay cả trong thực thể, vậy lợi ích của việc sử dụng giải pháp cuối cùng là gì?
ngày 5

61

Bạn có thể sử dụng Expression.AndAlso / OrElse để kết hợp các biểu thức logic, nhưng bạn phải đảm bảo ParameterExpressions giống nhau.

Tôi đã gặp rắc rối với EF và Người dự đoán nên tôi đã tự tạo mà không cần dùng đến Invoke, rằng tôi có thể sử dụng như thế này:

var filterC = filterA.And(filterb);

Mã nguồn cho Dự đoán của tôi:

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

Và lớp tiện ích để thay thế các tham số trong 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;
        }
    }

Giải pháp này là giải pháp duy nhất cho phép tôi có x => x.Property == Giá trị kết hợp với arg => arg.Property2 == Giá trị. Đạo cụ chính, hơi ngắn gọn và khó hiểu nhưng nó hoạt động nên tôi sẽ không phàn nàn. Kudos Adam :-)
VulgarBinary

Đây là một giải pháp tuyệt vời.
Aaron Stainback

Adam, điều này đã giải quyết một vấn đề rất khó chịu mà tôi gặp phải khi sử dụng nhà cung cấp Linq của mô hình Đối tượng khách hàng SharePoint - cảm ơn vì đã đăng nó.
Christopher McAtackney

Điều này làm việc cho tôi! Tôi đã tìm kiếm một loạt các giải pháp cũng như xây dựng vị ngữ và không có gì hoạt động cho đến khi này. Cảm ơn bạn!
tokyo0709

Đây là một đoạn mã tuyệt vời. Tôi không thể tìm thấy một nơi để điều chỉnh mã, sao chép-dán và đó là :)
Tolga Evcimen

19

Nếu nhà cung cấp của bạn không hỗ trợ Gọi và bạn cần kết hợp hai biểu thức, bạn có thể sử dụng ExpressionVisitor để thay thế tham số trong biểu thức thứ hai bằng tham số trong biểu thức đầu tiên.

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
Điều này đã giải quyết vấn đề cụ thể của tôi trong đó giải pháp khác dẫn đến ngoại lệ tương tự. Cảm ơn.
Shaun Wilson

1
Đây là một giải pháp tuyệt vời.
Aaron Stainback

3

Không có gì mới ở đây ngoài việc kết hôn câu trả lời này với câu trả lời này và hơi refactored nó để mà ngay cả tôi hiểu những gì đang xảy ra:

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

Tôi đã gặp khó khăn trong việc nắm bắt khái niệm này, và việc bạn kết hợp một vài câu trả lời khác đã giúp nó nhấp vào cho tôi. Cảm ơn!
Kevin M. Lapio

2

Tôi cần phải đạt được kết quả tương tự, nhưng sử dụng một cái gì đó chung chung hơn (vì loại này không được biết đến). Nhờ câu trả lời của marc cuối cùng tôi đã tìm ra những gì tôi đang cố gắng đạt được:

    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

Tôi đề nghị thêm một cải tiến cho Dự đoánExpressionVisitorgiải pháp. Tôi đã gọi nó UnifyParametersByNamevà bạn có thể tìm thấy nó trong thư viện MIT của tôi: LinqExprHelper . Nó cho phép kết hợp các biểu thức lambda tùy ý. Thông thường các câu hỏi được hỏi về biểu thức vị ngữ, nhưng ý tưởng này cũng mở rộng cho các biểu thức chiếu.

Đoạn mã sau sử dụng một phương thức ExprAdrestạo ra một biểu thức tham số phức tạp, sử dụng lambda nội tuyến. Biểu thức phức tạp này chỉ được mã hóa một lần, và sau đó được sử dụng lại, nhờ vào LinqExprHelperthư viện nhỏ.

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

Và đây là mã xây dựng phụ:

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

Những gì tôi đã cố gắng đạt được là thực hiện các truy vấn tham số mà không cần phải sao chép-dán và với khả năng sử dụng lambdas nội tuyến, rất đẹp. Nếu không có tất cả những thứ biểu hiện trợ giúp này, tôi sẽ buộc phải tạo toàn bộ truy vấn trong một lần.


-7

Tôi nghĩ rằng điều này làm việc tốt, phải không?

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
điều này không thể được sử dụng trong Linq to SQL chẳng hạn
Romain Vergnory
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.