การรวมสองนิพจน์ (นิพจน์ <Func <T, bool >>)


249

ฉันมีนิพจน์สองประเภทExpression<Func<T, bool>>และฉันต้องการนำไปใช้กับ OR และหรือไม่ใช่สิ่งเหล่านี้และรับนิพจน์ใหม่ประเภทเดียวกัน

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 ไปยังเอนทิตี: รวมภาคแสดง
โทมัส 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 มี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);
        }
    }

สวัสดี Marc ฉันลองข้อเสนอแนะครั้งแรกของคุณในบล็อกโค้ดแรกของคุณด้านบน แต่เมื่อฉันส่งผ่านนิพจน์ "แลมบ์ดา" <func <T, bool >> ส่งผลให้วิธีการที่ไหนฉันได้รับข้อผิดพลาดว่าพารามิเตอร์คือ ออกจากขอบเขต? ความคิดใด ๆ ไชโย
andy

1
+1 รุ่นทั่วไปใช้งานได้อย่างมีเสน่ห์ฉันใช้แทน andalso ฉันคิดว่า linq to sql ไม่รองรับ andalso?
Maslow

2
@Maslow - นี่คือนักเขียนที่สามารถแทรกต้นไม้เพื่อบันทึก Invoke: stackoverflow.com/questions/1717444/…
Marc Gravell

1
@Aron ดูที่วันนี้แล้ว: ผู้เยี่ยมชม. NET Framework ( ExpressionVisitor) ไม่มีอยู่ในตอนนั้น ฉันมีตัวอย่างที่เกี่ยวข้องกับ stackoverflow จากวันที่คล้ายกันซึ่งใช้ผู้เยี่ยมชมด้วยตนเอง: มันเป็นโค้ดจำนวนมาก
Marc Gravell

1
@ MarkGravell ฉันใช้โซลูชันแรกของคุณในการรวมการแสดงออกของฉันและทุกอย่างทำงานได้ดีแม้ในการทำงานแบบเอนทิตีดังนั้นประโยชน์ของการใช้โซลูชันล่าสุดคืออะไร
johnny 5

62

คุณสามารถใช้ Expression.AndAlso / OrElse เพื่อรวมนิพจน์ทางตรรกะ แต่คุณต้องตรวจสอบให้แน่ใจว่า Parameter Expressions เหมือนกัน

ฉันมีปัญหากับ 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 == ค่า อุปกรณ์ประกอบฉากที่สำคัญสั้น ๆ และสับสนเล็กน้อย แต่ก็ใช้งานได้ดังนั้นฉันจะไม่บ่น Kudos Adam :-)
VulgarBinary

นี่เป็นทางออกที่ดี
Aaron Stainback

อดัมสิ่งนี้แก้ปัญหาที่น่ารำคาญอย่างมากที่ฉันใช้ผู้ให้บริการ Linq ของโมเดลไคลเอ็นต์ SharePoint วัตถุ - ขอบคุณสำหรับการโพสต์
Christopher McAtackney

สิ่งนี้ได้ผลสำหรับฉัน! ฉันค้นหาวิธีแก้ปัญหาที่หลากหลายรวมถึงเครื่องมือสร้างคำกริยาและไม่มีอะไรทำงานได้จนถึงตอนนี้ ขอบคุณ!
tokyo0709

นี่เป็นโค้ดที่ยอดเยี่ยม ฉันไม่สามารถหาสถานที่เพื่อปรับเปลี่ยนรหัสคัดลอกและนั่นคือ :)
Tolga Evcimen

19

หากผู้ให้บริการของคุณไม่สนับสนุน 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" }));
}

1
วิธีนี้แก้ไขปัญหาเฉพาะของฉันซึ่งโซลูชันอื่นทำให้เกิดข้อยกเว้นเดียวกัน ขอบคุณ
Shaun Wilson

1
นี่เป็นทางออกที่ดี
Aaron Stainback

3

ไม่มีอะไรใหม่ที่นี่ แต่แต่งงานกับคำตอบนี้ด้วยคำตอบนี้และ refactored เล็กน้อยเพื่อให้ฉันเข้าใจสิ่งที่เกิดขึ้น:

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

ฉันขอแนะนำการปรับปรุงอีกหนึ่งข้อสำหรับPredicateBuilderและExpressionVisitorโซลูชัน ผมเรียกมันว่าUnifyParametersByNameและคุณสามารถค้นหาได้ในห้องสมุด MIT ของฉัน: LinqExprHelper มันช่วยให้สามารถรวมการแสดงออกแลมบ์อาร์บิเทีย โดยปกติแล้วคำถามจะถูกถามเกี่ยวกับการแสดงออกของเพรดิเคต แต่ความคิดนี้รวมถึงการแสดงการแสดงออกเช่นกัน

รหัสต่อไปนี้ใช้วิธีการExprAdresที่สร้างนิพจน์ที่ซับซ้อนโดยใช้แลมบ์อินแบบอินไลน์ นิพจน์ที่ซับซ้อนนี้มีการเข้ารหัสเพียงครั้งเดียวแล้วนำกลับมาใช้ใหม่ด้วยLinqExprHelpermini-library

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

และนี่คือรหัสอาคาร subexpression:

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

สิ่งที่ฉันพยายามที่จะบรรลุคือการดำเนินการสอบถาม parametrized โดยไม่จำเป็นต้องคัดลอกและวางด้วยความสามารถในการใช้ lambdas แบบอินไลน์ซึ่งค่อนข้างน่ารัก ฉันจะถูกบังคับให้สร้างข้อความค้นหาทั้งหมดในครั้งเดียว


-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.