首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >NRules:使用DSL扩展自定义基类的规则

NRules:使用DSL扩展自定义基类的规则
EN

Stack Overflow用户
提问于 2018-11-05 16:56:55
回答 1查看 1.2K关注 0票数 3

我使用NRules来定义所有继承自公共基类的规则,这个基类本身是从Rule继承的。

当我使用DSL扩展插入包装匹配对象的新事实时,传递给扩展方法的匹配对象似乎是null

下面是一个独立的例子,应该能说明这个问题。我使用xUnit测试框架来定义两个规则,每个规则都具有相同的测试。第一个通过,第二个失败。

代码语言:javascript
复制
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);
        }
    }
}

问题是为什么带有DSL扩展的第二条规则不能正常工作?我做错什么了吗?我该怎么解决呢?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-11-11 22:44:04

NRules DSL首先要注意的是,当您在规则中声明匹配变量并绑定到它时会发生什么:

代码语言:javascript
复制
Apple apple = null;
When()
    .Match(() => apple);

实际上,没有为该变量分配任何值。它被捕获为表达式树,并提取其名称,并用于稍后查找引用相同变量的其他表达式。然后引擎用实际匹配的事实替换这些引用。例如:

代码语言:javascript
复制
Then()
    .Do(ctx => ctx.Insert(new Basket(apple)));

在这里," apple“是同一个apple变量,与When子句相同,因此NRules能够识别这一点,并正确地将表达式拼接在一起。

提取扩展方法时,将变量命名为“水果”:

代码语言:javascript
复制
public static IRightHandSideExpression AddToBasket(this IRightHandSideExpression rhs, IFruit fruit)
{
    return rhs.Do(ctx => ctx.Insert(new Basket(fruit)));
}

由于“水果”和“苹果”不匹配,引擎不再将其识别为同样的事实参考。

因此,fix #1只需将变量命名为与声明相同的方式:

代码语言:javascript
复制
public static class DslExtensions
{
    public static IRightHandSideExpression AddToBasket(this IRightHandSideExpression rhs, IFruit apple)
    {
        return rhs.Do(ctx => ctx.Insert(new Basket(apple)));
    }
}

显然,这并不理想,因为您依赖于变量的匹配命名。由于NRules根据表达式树进行操作,构建泛型扩展方法的一个更好的方法是在表达式树上编写泛型扩展方法,而不再依赖于变量命名。

因此,修复#2是使用lambda表达式编写扩展方法。

代码语言:javascript
复制
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)现在捕获lambda表达式,该表达式稍后被提取并用于扩展方法的实现。使用一些表达式魔术,我构建了一个与您所拥有的表达式相当的lambda表达式,但这次不依赖于任何特定的变量命名。

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/53158857

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档