दो भावों को मिलाना (अभिव्यक्ति <फंक <टी, बूल >>)


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 से लेकर एंटिटीज: प्रेडिनेटिंग का संयोजन
थॉमस CG de Vilhena

जवाबों:


331

ठीक है, आप तार्किक अभिव्यक्तियों को संयोजित करने के लिए Expression.AndAlso/ OrElseआदि का उपयोग कर सकते हैं , लेकिन समस्या पैरामीटर है; क्या आप 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);
        }
    }

हे मार्क, मैंने आपका पहला सुझाव दिया, ऊपर दिए आपके पहले कोड ब्लॉक में, लेकिन जब मैं "लैम्ब्डा" एक्सप्रेशन <func <t, bool >> में उत्तीर्ण करता हूं, तो एक विधि में परिणाम मिलता है, मुझे एक त्रुटि मिलती है जिसमें कहा जाता है कि पैरामीटर है दायरे से बाहर? कोई उपाय? चीयर्स
एंडी

1
+1 सामान्यीकृत संस्करण एक आकर्षण की तरह काम करता है, जिसका मैंने उपयोग किया है और इसके बजाय ऑलासो के बारे में, मैंने सोचा कि एसक्यूएल को लिनैक समर्थन और अलसो नहीं करता है?
मैस्लो

2
: - @Maslow यहाँ एक rewriter कि आह्वान को बचाने के लिए पेड़ इनलाइन सकता है stackoverflow.com/questions/1717444/...
मार्क Gravell

1
@ अब तारीख देखें: .NET फ्रेमवर्क आगंतुक ( ExpressionVisitor) तब वापस मौजूद नहीं था ; मेरे पास एक समान तिथि से स्टैकओवरफ़्लो पर संबंधित उदाहरण है जहां यह आगंतुक को मैन्युअल रूप से लागू करता है: यह बहुत कोड है।
मार्क Gravell

1
@MarkGravell, मैं अपने भावों को संयोजित करने के लिए आपके पहले समाधान का उपयोग कर रहा हूं, और सब कुछ Unitframework में भी ठीक काम कर रहा है, तो अंतिम समाधान का उपयोग करने के क्या लाभ होंगे?
जॉनी 5

62

आप तार्किक अभिव्यक्तियों को संयोजित करने के लिए Expression.AndAlso / OrElse का उपयोग कर सकते हैं, लेकिन आपको यह सुनिश्चित करना होगा कि ParameterExpressions समान हैं।

मुझे EF और PredicateBuilder से परेशानी हो रही थी, इसलिए मैंने इनवॉइस का सहारा लिए बिना अपना खुद का बनाया, ताकि मैं इस का उपयोग कर सकूं:

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 == मान के साथ संयोजित करने की अनुमति दी। मेजर प्रॉप्स, थोडा थोडा और भ्रमित करने वाला लेकिन यह काम करता है इसलिए मैं शिकायत नहीं करने जा रहा हूँ। कुडोस एडम :-)
वल्गरबिनरी

यह एक बेहतरीन उपाय है।
हारून स्टेनबैक

एडम, ने SharePoint क्लाइंट ऑब्जेक्ट मॉडल के Linq प्रदाता का उपयोग करते हुए एक बहुत कष्टप्रद समस्या हल की - इसे पोस्ट करने के लिए धन्यवाद।
क्रिस्टोफर मैकअटैकनी

यह मेरे लिए काम किया! मैंने विभिन्न प्रकार के समाधानों के साथ-साथ बिल्डर की खोज की और इसके लिए कुछ भी काम नहीं किया। धन्यवाद!
tokyo0709

यह कोड का एक अद्भुत टुकड़ा है। मुझे कोड, कॉपी-पेस्ट को समायोजित करने का स्थान नहीं मिला और वह यह है :)
टोलगा एवसीमेन

19

यदि आप प्रदाता इनवोक का समर्थन नहीं करते हैं और आपको दो अभिव्यक्ति को संयोजित करने की आवश्यकता है, तो आप पहले अभिव्यक्ति में पैरामीटर द्वारा दूसरी अभिव्यक्ति में पैरामीटर को बदलने के लिए एक एक्सप्रेशनविजर का उपयोग कर सकते हैं।

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
इसने मेरी विशेष समस्या को हल कर दिया जहां दूसरे समाधान के परिणामस्वरूप एक ही अपवाद था। धन्यवाद।
शॉन विल्सन

1
यह एक बेहतरीन उपाय है।
हारून स्टेनबैक

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

मुझे अवधारणा को समझने में कठिनाई हो रही थी, और आपके कुछ और जवाबों के पिघलने से मेरे लिए क्लिक करने में मदद मिली। धन्यवाद!
केविन एम। लापियो

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और आप इसे एमआईटी लाइब्रेरी ऑफ माइन: लिनक्प्रप्रहेलर में पा सकते हैं । यह मध्यस्थ लैम्ब्डा अभिव्यक्ति के संयोजन के लिए अनुमति देता है। आमतौर पर प्रश्न विधेय अभिव्यक्ति के बारे में पूछे जाते हैं, लेकिन यह विचार प्रक्षेपण अभिव्यक्तियों तक भी फैला हुआ है।

निम्न कोड एक विधि को नियोजित करता है 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 के लिए उदाहरण के लिए एसक्यूएल में इस्तेमाल नहीं किया जा सकता है
रोमेन वेगनरी
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.