मैं नियमों को परिभाषित करने के लिए NRules का उपयोग कर रहा हूं जो सभी एक सामान्य आधार वर्ग से प्राप्त होते हैं, जो स्वयं Rule से प्राप्त होते हैं।

जब मैं एक नए तथ्य को सम्मिलित करने के लिए एक डीएसएल एक्सटेंशन का उपयोग करता हूं जो एक मेल खाने वाली वस्तु को लपेटता है, ऐसा लगता है कि मिलान की गई वस्तु को विस्तार विधि में पारित किया गया है null

यहां एक आत्मनिर्भर उदाहरण दिया गया है जो समस्या का प्रदर्शन करना चाहिए। मैं दो नियमों को परिभाषित करने के लिए xUnit परीक्षण ढांचे का उपयोग कर रहा हूं, प्रत्येक समान परीक्षण के साथ। पहला पास हो जाता है, दूसरा फेल हो जाता है।

using NRules;
using NRules.Fluent;
using NRules.Fluent.Dsl;
using Xunit;
using System.Linq;
using System.Reflection;

namespace IntegrationTests.Engine
{
    // A simple domain model
    public interface IFruit { }

    public class Apple : IFruit { }

    public class Basket
    {
        public Basket(IFruit apple)
        {
            MyApple = apple;
        }

        public IFruit MyApple { get; private set; }
    }


    // A base class for the rules
    public abstract class RuleBase : Rule
    {
        public override void Define()
        {
            // Empty
        }
    }

    // The first rule, which does not use the extension:
    public class TestRule : RuleBase
    {
        public override void Define()
        {
            base.Define();

            Apple a = null;
            When()
                .Match(() => a);

            Then()
                .Do(ctx => ctx.Insert(new Basket(a)));
        }
    }

    // The second rule, which uses an extension to add a new fact
    public class TestRuleWithExtension : RuleBase
    {
        public override void Define()
        {
            base.Define();

            Apple apple = null;
            When()
                .Match(() => apple);

            Then()
                .AddToBasket(apple);
        }
    }

    // The DSL extension
    public static class DslExtensions
    {
        public static IRightHandSideExpression AddToBasket(this IRightHandSideExpression rhs, IFruit fruit)
        {
            return rhs.Do(ctx => ctx.Insert(new Basket(fruit)));
        }
    }

    // The tests
    public class ExtensionTest
    {
        // This one tests the first rule and passes
        [Fact]
        public void TestInsert()
        {
            //Load rules
            var repository = new RuleRepository();
            repository.Load(x => x
                .From(Assembly.GetExecutingAssembly())
                .Where(rule => rule.Name.EndsWith("TestRule")));

            //Compile rules
            var factory = repository.Compile();

            //Create a working session
            var session = factory.CreateSession();

            //Load domain model
            var apple = new Apple();

            //Insert facts into rules engine's memory
            session.Insert(apple);

            //Start match/resolve/act cycle
            session.Fire();

            // Query for inserted facts
            var bananas = session.Query<Basket>().FirstOrDefault();

            // Assert that the rule has been applied
            Assert.Equal(apple, bananas.MyApple);
        }

        // This one tests the second rule, and fails
        [Fact]
        public void TestInsertWithExtension()
        {
            //Load rules
            var repository = new RuleRepository();
            repository.Load(x => x
                .From(Assembly.GetExecutingAssembly())
                .Where(rule => rule.Name.EndsWith("TestRuleWithExtension")));

            //Compile rules
            var factory = repository.Compile();

            //Create a working session
            var session = factory.CreateSession();

            //Load domain model
            var apple = new Apple();

            //Insert facts into rules engine's memory
            session.Insert(apple);

            //Start match/resolve/act cycle
            session.Fire();

            // Query for inserted facts
            var bananas = session.Query<Basket>().FirstOrDefault();

            // Assert that the rule has been applied
            Assert.Equal(apple, bananas.MyApple);
        }
    }
}

सवाल यह है कि डीएसएल एक्सटेंशन वाला दूसरा नियम ठीक से काम क्यों नहीं करता है? क्या मैं कुछ गलत कर रहा हूँ और मैं इसे कैसे ठीक कर सकता हूँ?

3
Captain Whippet 5 नवम्बर 2018, 19:56

1 उत्तर

सबसे बढ़िया उत्तर

NRules DSL के साथ ध्यान देने वाली पहली बात यह है कि जब आप किसी नियम में एक मिलान चर घोषित करते हैं और उससे जुड़ते हैं तो क्या होता है:

Apple apple = null;
When()
    .Match(() => apple);

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

Then()
    .Do(ctx => ctx.Insert(new Basket(apple)));

यहां "सेब" वही सेब चर है, जब क्लॉज होता है, इसलिए NRules इसे पहचानता है और अभिव्यक्तियों को एक साथ सही ढंग से सिलाई करता है।

जब आपने एक विस्तार विधि निकाली, तो आपने चर का नाम "फल" रखा:

public static IRightHandSideExpression AddToBasket(this IRightHandSideExpression rhs, IFruit fruit)
{
    return rhs.Do(ctx => ctx.Insert(new Basket(fruit)));
}

इंजन अब इसे उसी तथ्य संदर्भ के रूप में नहीं पहचानता है, क्योंकि "फल" और "सेब" मेल नहीं खाते।

तो, फिक्स # 1 केवल वैरिएबल को उसी तरह नाम देना है जैसे घोषणा:

public static class DslExtensions
{
    public static IRightHandSideExpression AddToBasket(this IRightHandSideExpression rhs, IFruit apple)
    {
        return rhs.Do(ctx => ctx.Insert(new Basket(apple)));
    }
}

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

तो, फिक्स # 2 लैम्ब्डा एक्सप्रेशन का उपयोग करके एक्सटेंशन विधि लिखना है।

public class TestRuleWithExtension : RuleBase
{
    public override void Define()
    {
        base.Define();

        Apple apple = null;
        When()
            .Match(() => apple);

        Then()
            .AddToBasket(() => apple);
    }
}

public static class DslExtensions
{
    public static IRightHandSideExpression AddToBasket(this IRightHandSideExpression rhs, Expression<Func<IFruit>> alias)
    {
        var context = Expression.Parameter(typeof(IContext), "ctx");

        var ctor = typeof(Basket).GetConstructor(new[] {typeof(IFruit)});
        var newBasket = Expression.New(ctor, alias.Body);

        var action = Expression.Lambda<Action<IContext>>(
            Expression.Call(context, nameof(IContext.Insert), null, newBasket), 
            context);
        return rhs.Do(action);
    }
}

ध्यान दें कि AddToBasket(() => apple) अब लैम्ब्डा एक्सप्रेशन को कैप्चर करता है, जिसे बाद में एक्सट्रेक्ट किया जाता है और एक्सटेंशन विधि के कार्यान्वयन में उपयोग किया जाता है। कुछ अभिव्यक्ति जादू के साथ मैंने आपके पास एक लैम्ब्डा अभिव्यक्ति के बराबर बनाया, लेकिन इस बार किसी विशिष्ट चर नामकरण पर भरोसा नहीं किया।

2
Sergiy Nikolayev 12 नवम्बर 2018, 00:12