我使用NRules来定义所有继承自公共基类的规则,这个基类本身是从Rule继承的。
当我使用DSL扩展插入包装匹配对象的新事实时,传递给扩展方法的匹配对象似乎是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);
}
}
}问题是为什么带有DSL扩展的第二条规则不能正常工作?我做错什么了吗?我该怎么解决呢?
发布于 2018-11-11 22:44:04
NRules DSL首先要注意的是,当您在规则中声明匹配变量并绑定到它时会发生什么:
Apple apple = null;
When()
.Match(() => apple);实际上,没有为该变量分配任何值。它被捕获为表达式树,并提取其名称,并用于稍后查找引用相同变量的其他表达式。然后引擎用实际匹配的事实替换这些引用。例如:
Then()
.Do(ctx => ctx.Insert(new Basket(apple)));在这里," apple“是同一个apple变量,与When子句相同,因此NRules能够识别这一点,并正确地将表达式拼接在一起。
提取扩展方法时,将变量命名为“水果”:
public static IRightHandSideExpression AddToBasket(this IRightHandSideExpression rhs, IFruit fruit)
{
return rhs.Do(ctx => ctx.Insert(new Basket(fruit)));
}由于“水果”和“苹果”不匹配,引擎不再将其识别为同样的事实参考。
因此,fix #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是使用lambda表达式编写扩展方法。
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表达式,但这次不依赖于任何特定的变量命名。
https://stackoverflow.com/questions/53158857
复制相似问题