。简介
作为尚未,我一直无法找到一个合适的规则引擎使用在我的。NET项目。当然,也有ILOG和BizTalk有这样大的家伙已经完全成熟的规则引擎。但是,我需要的东西简单和自由,我可以到我的。NET应用程序中嵌入。
。NET 3.0中引入的Windows Workflow Foundation,其中有一个以规则为基础的活动。当你到一个工作流程中的某一点,你可以运行在对象上的一些规则,并用它来改变对象和/或决定该走的路旁边的工作流程。基本上,工作流,可以不存在没有某种规则。袭击约我本实施细则的是,它使用的条件和行动的CodeDOM。最近,我写了一个{A}],使我的CodeDOM的编程生活变得更轻松。那么,为什么不把解析器来的?
我应该提到的前期,这决不是一个真正的规则引擎。 NET 3.0中的规则,采用正向链,这意味着还有很多,你没有做。但是,前进链不能跨类型。这个工作是在以后的文章中的主题。演示
示范如何将本规则引擎的工作原理,可以在以下链接找到。这是一个小游戏,我来说明如何可以在应用程序中使用DmRules:]。目标
我需要我的嵌入规则引擎能够处理某些事情。为了澄清:虽然我与Prolog的方式在高校的实验,我有没有兴趣,这里在实施。我更感兴趣的是获得一个核心功能集,是在商业应用中对我有用。此外,它是我的信念,业务分析师,不知道如何编写不应该尝试编写规则。所以,我不会尝试作出的规则写很容易,他们这样做!下面是一些我希望我的规则库做的事情:处理计算 - 当您更新数量在一个对象的某个地方,任何其他对象使用,这个数字在计算应重新评估。例如,我有一个类,计算出多少钱,我需要在我的每月预算分配。计算的部分是对天然气的预算。另一个对象,目前天然气价格。 ,价格变动时,我每月总预算的数量应该改变以及。气价的对象不应该依赖于它的对象,应自动更新。断言/审计 - 当值超出可接受的范围内,你可能想作出回应。你可以提出一些错误标志,或者到一个日志记录的问题。正向链 - 如上所述,如果在一个对象改变,应重新评估A,则对象B,对象B,对象取决于。基本上,该系统应能够应对变化没有明确告诉它更新的编码。非侵入性 - 实际代码的变化,以方便规则应该是微不足道的。配置简单 - 我想有一个非常大的,丑陋的XML文件定义的规则,以避免。一个没有规则引擎的培训初级程序员应该能够阅读和理解从XML配置规则。适应变化 - 让我们面对它,也有其他的方式来处理计算,并断言和审计。面向方面的编程或只需使用。NET的事件等技术都相当足够。但是,真正重要的是能够改变的规则是如何工作的。规则执行的条件,计算,或行动可能会改变。规则引擎使您能够处理这些变化。一个在Windows Workflow Foundation入门规则
让我们看看运行规则的一个例子。微软已经提供了一个基本的规则编辑器,让你写的条件和行动,而不是使用CodeDom中的GUI。使用该工具,而是我们将自己写的CodeDOM。
在这里我们的例子中类:public class Class1
{
private int _Foo;
private string _Bar;
public int Foo
{
get { return _Foo; }
set { _Foo = value; }
}
public string Bar
{
get { return _Bar; }
set { _Bar = value; }
}
}
现在,我们可以写一个类到Class1的适用规则。首先,我们必须包括正确的引用。 System.Workflow.Activities和System.Workflow.ComponentModel您的项目中添加引用。现在,我们将通过代码一步一步走。我们不需要太多的命名空间包括:{C}
,我们正在使用的核心类是所谓的规则集。使用微软的工具时,这个对象是序列化的规则文件。RuleSet rs = new RuleSet();
Rule r = new Rule("Rule A");
rs.Rules.Add(r);
WF中的规则有一个条件,一组动作来执行,如果条件为真,和一组动作来执行,如果条件为假。所有这些,都写在CodeDom中。我们要测试的条件是,如果物业美孚等于3。CodeThisReferenceExpression thisRef = new CodeThisReferenceExpression();
CodePropertyReferenceExpression fooRef = new
CodePropertyReferenceExpression(thisRef, "Foo");
CodeBinaryOperatorExpression fooCond = new CodeBinaryOperatorExpression();
fooCond.Left = fooRef;
fooCond.Operator = CodeBinaryOperatorType.ValueEquality;
fooCond.Right = new CodePrimitiveExpression(3);
正如你可以看到,我没有使用我的CodeDOM表达式解析器库。这将留待以后。不管怎么说,我们采取我们的表达和运用规则条件。r.Condition = new RuleExpressionCondition(fooCond);
现在,我们需要一个要执行的动作,至少表明规则的工作:CodePropertyReferenceExpression barRef = new
CodePropertyReferenceExpression(thisRef, "Bar");
CodeAssignStatement barThen = new CodeAssignStatement();
barThen.Left = barRef;
barThen.Right = new CodePrimitiveExpression("Gotcha");
这将改变值栏quot; Gotchaquot;只要条件为真。我们只需要添加它作为一个quot; thenquot,行动:
现在,我们要申请一个验证我们的规则。验证器基地本身的一种类型。基本上,它是要验证你的规则上使用Class1的有效属性/方法。这也使我想起一个有趣的讨论点。规则集是一套规则适用于一种类型的时间。它非常有意义,为什么它是这样,但它确实向前链等,也有要处理的,为了有一个有用的规则引擎施加的限制。RuleValidation rv = new RuleValidation(typeof(Class1), null);
rs.Validate(rv);
现在,我们要执行的规则。为了测试规则的正常工作,我们将在增加foo的值,直到它击中3循环,然后检查酒吧的价值:Class1 c = new Class1();
c.Bar = "Uh-uh";
for (int i = 0; i < 4; i++)
{
c.Foo = i;
RuleExecution rexec = new RuleExecution(rv, c);
rs.Execute(rexec);
Console.WriteLine("i: " + i + "\n\tFoo: " + c.Foo + "\n\tBar: " + c.Bar);
}
输出结果:i: 0
Foo: 0
Bar: Uh-uh
i: 1
Foo: 1
Bar: Uh-uh
i: 2
Foo: 2
Bar: Uh-uh
i: 3
Foo: 3
Bar: Gotcha
这个测试是包含在源代码包,这样你就可以看到自己。你绝对可以看到,该系统具有一定的潜力。但是,除非我们要结婚了微软的规则书写工具,我们将不得不想出一个办法来处理与CodeDom中。输入我的CodeDOM表达式解析器。DmCodeDom - CodeDom中助手库
像我表明一个表达式解析器],是不够的处理规则,因为它不处理报表在所有。所以,我介绍了另一个小帮手库帮我写报表。这个辅助库只涵盖的基础知识:转让宣言IF / ELSE - 不允许在WF规则河套 - WF规则也不允许表达式语句
为了说明,以下简单的循环,所有上述要素:for (int i = 0; i < 7; i++)
{
if (i % 2 == 1)
foo.Bar();
}
在这里,你将如何把它写在CodeDom中:CodeIterationStatement cis = new CodeIterationStatement();
CodeVariableReferenceExpression refI = new
CodeVariableReferenceExpression("i");
cis.InitStatement = new CodeVariableDeclarationStatement(
typeof(int), "i", new CodePrimitiveExpression(0));
cis.IncrementStatement = new CodeAssignStatement(refI,
new CodeBinaryOperatorExpression(refI,
CodeBinaryOperatorType.Add,
new CodePrimitiveExpression(1)));
cis.TestExpression = new CodeBinaryOperatorExpression(refI,
CodeBinaryOperatorType.LessThan,
new CodePrimitiveExpression(7));
CodeConditionStatement ccs = new CodeConditionStatement();
ccs.Condition = new
CodeBinaryOperatorExpression(new
CodeBinaryOperatorExpression(refI,
CodeBinaryOperatorType.Modulus,
new CodePrimitiveExpression(2)),
CodeBinaryOperatorType.ValueEquality,
new CodePrimitiveExpression(1));
CodeMethodInvokeExpression cmie = new
CodeMethodInvokeExpression(new
CodeVariableReferenceExpression("foo"), "Bar",
new CodeExpression[0]);
ccs.TrueStatements.Add(cmie);
cis.Statements.Add(ccs);
唷! CodeDom中可以得到丑陋的快速。 ,这是一个简单的循环。我们真的这样做。下面是我做我的助手类的确切相同的循环:ForLoop fl = new ForLoop();
fl.InitStmt = new Declaration("int", "i", "0");
fl.IncrStmt = new Assignment("i", "i + 1");
fl.Cond = "i < 7";
IfElse ie = new IfElse();
ie.Cond = "i % 2 == 1";
ie.TrueStmts.Add(new ExprStmt("foo.Bar()"));
fl.LoopStmts.Add(ie);
我之所以选择像ForLoop和分配使用的类,因为我希望能够通过XmlSerializer类序列化到XML。下面是上面的循环看起来像什么,如果序列化到XML:<DmCdStmt
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xsi:type="ForLoop" cond="i < 7">
<InitStmt xsi:type="Declaration" type="int" varName="i" initExp="0" />
<IncrStmt xsi:type="Assignment" left="i"
right="i + 1" />
<LoopStmts>
<DmCdStmt xsi:type="IfElse" cond="i % 2 == 1">
<TrueStmts>
<DmCdStmt xsi:type="ExprStmt" expr="foo.Bar()" />
</TrueStmts>
<FalseStmts />
</DmCdStmt>
</LoopStmts>
</DmCdStmt>
我很高兴。NET 2.0中,他们终于固定XmlSerializer的,这样它可以让你来序列化和反序列化,而不是只具体类的抽象基类。的XML可能会有点简单,但将采取实施作IXmlSerializable,做我自己的努力实在是不值得的收益。
有一天,我实际上可能变成一个严重的CodeDOM帮手库DmCodeDom。如果你有兴趣,后在这篇文章的底部(或给我投票的5 {S0})发表评论。使用代码
显示此代码是如何工作的最好的办法是用一个例子。我有两个简单的类:Order和OrderItem的。每个订单项目的集合。为了有一个总的汇总所有项目的成本。我要处理的有两种情况:项目是从订单中删除,并一个项目的价格或数量的改变而改变。
下面是Order类:public class Order {
private List<OrderItem> _Items = new List<OrderItem>();
private int _NumItems = 0;
private float _Total = 0f;
public List<orderitem> Items {
get { return _Items; }
}
public int NumItems {
get { return _NumItems; }
}
public float Total {
get { return _Total; }
}
internal void RecalculateTotal() {
_Total = 0f;
foreach (OrderItem oi in _Items)
_Total += oi.Price * oi.Units;
}
public Order() {}
public OrderItem NewItem() {
return new OrderItem(this);
}
public OrderItem NewItem(float price, int units) {
return new OrderItem(price, units, this);
}
}</orderitem>
一个非常简单的的类,你可以看到。它有一个项目清单,并报告了订单的总成本。注意这里的一件事是,有一个项目数的属性不返回Items.Count。这样做的目的,使规则能识别一个项目的是添加或删除。当然,还有其他方法可以做到这一点,但我选择了这个方法。
你可能想象的OrderItem是什么样子。下面是这个类:public class OrderItem : BaseObject {
private float _Price = 0f;
private int _Units = 0;
private Order _Order = null;
public float Price {
get { return _Price; }
set {
if (value != _Price) {
_Price = value;
MarkDirty();
}
}
}
public int Units {
get { return _Units; }
set {
if (value != _Units) {
_Units = value;
MarkDirty();
}
}
}
public Order Order {
get { return _Order; }
}
internal OrderItem(Order order) {
_Order = order;
}
internal OrderItem(float price, int units, Order order) {
_Price = price;
_Units = units;
_Order = order;
}
}
有几件事情要注意这个类。被标记为内部的构造,因为我希望他们能够创建Order类。单位或价格的变化将标记为脏的对象。如果你从来没见过此之前,它基本上信号,被改变的对象。这是不够好,通知他们重新评估的规则。 CSLA使用这种技术,认识到需要更改保存到数据库中。肮脏的标志是包含在BaseObject类,它看起来像这样:
创建和运行规则public abstract class BaseObject {
protected bool _IsDirty = false;
public bool IsDirty {
get { return _IsDirty; }
}
public void MarkDirty() {
_IsDirty = true;
}
}
我们写的第一条规则是要处理的项目数的变化。此规则应用于Order类。我们将手写整个事情,然后我会告诉以后如何你可以把应用程序配置的规则。首先,让我们的设置规则:DmRule dr = new DmRule("this.NumItems != this.Items.Count",
"Rule1",
new DmCdStmt[] {
new ExprStmt("this.RecalculateTotal()"),
new Assignment("this._NumItems", "this.Items.Count"),
},
new DmCdStmt[0]
);
Parser parser = new Parser();
parser.Fields.Add("_NumItems");
DmRuleSet drs = new DmRuleSet();
drs.RuleTypes.Add(new DmRuleTypeSet(typeof(Order), new DmRule[] { dr }));
drs.Eval(parser);
因此,我们创建一个DmRule对象,以表示我们的规则。规则的条件是比较的项目数与该Items.Count属性。如果他们不匹配,那么一个项目中添加或删除。如果规则的计算结果为true时,会发生两件事:重新计算,总项目数是改变,以反映目前的项目数量。我宁愿重新计算规则的动作总,但规则库不允许CodeIterationStatements。
创建规则后,我们与之相匹配的一个类型,并添加DmRuleSet将处理规则执行。您可能还注意到,该表达式解析器需要知道,_NumItems是一个领域。
我们要做的是创建一个订单,然后给它添加一个项目。当我们运行的规则,它应该弄清楚,增加了一个项目,并重新计算总。
输出结果是:
使用RuleExec简化一切Before:
NumItems: 0
Total: 0
After:
NumItems: 1
Total: 4.6
在库中包含的是一个静态类称为RuleExec。这个类需要自动为您照顾一些事情:规则可以指定应用程序的配置,而不是手动创建。通过检查类型字段自动添加到表达式解析器。一个对象的运行规则,可以在一行代码。
这部分,我们将添加另一个规则。此规则适用于OrderItem的类。当一个变化是,无论是单位或单位数量的价格,该项目将被标记为脏。当发生这种情况,我们要重新计算订单总额。我们将添加此规则Order类的现有规则,并把整个事情在App.config:<configuration>
<configSections>
<section
name="dmRulesConfig"
type="DmRules.Configuration.DmRulesConfigHandler, DmRules"
/>
</configSections>
<dmRulesConfig>
<DmRuleSet
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<RuleTypes>
<DmRuleTypeSet type="DmRules.TestHarness.Order, DmRules.TestHarness">
<Rules>
<DmRule cond="this.NumItems != this.Items.Count" name="Rule1">
<ThenStmts>
<DmCdStmt xsi:type="ExprStmt" expr="this.RecalculateTotal()" />
<DmCdStmt xsi:type="Assignment" left="this._NumItems"
right="this.Items.Count" />
</ThenStmts>
<ElseStmts />
</DmRule>
</Rules>
</DmRuleTypeSet>
<DmRuleTypeSet type="DmRules.TestHarness.OrderItem, DmRules.TestHarness">
<Rules>
<DmRule cond="this.IsDirty" name="Rule1">
<ThenStmts>
<DmCdStmt xsi:type="ExprStmt"
expr="this.Order.RecalculateTotal()" />
<DmCdStmt xsi:type="Assignment" left="this._IsDirty"
right="false" />
</ThenStmts>
<ElseStmts />
</DmRule>
</Rules>
</DmRuleTypeSet>
</RuleTypes>
</DmRuleSet>
</dmRulesConfig>
</configuration>
从这个例子,你应该能够看到如何将XML格式化。如果你经常使用XmlSerializer,那么这应该很熟悉。 OrderItem的规则将检查的对象是标记为脏。如果是这样的话,它告诉家长为了重新计算其总量,并改变脏标志回false。还要注意如何在此XML指定完整的类型名称。规则总是与一个类型。该规则的名字也相同,但因为它们是不同类型,它的确定。
RuleExec使编码工作轻松了许多。我们不必担心运行分析器自己或做了工作,是在上一节做任何的设置。下面的代码将创建一个命令,添加一个OrderItem,并更改该项目的单位数:Order o = new Order();
OrderItem oi = o.NewItem(2.3f, 2);
o.Items.Add(oi);
RuleExec.ApplyRules(o);
oi.Units = 5;
Console.WriteLine("Before:");
Console.WriteLine(" Total: " + o.Total);
RuleExec.ApplyRules(oi);
Console.WriteLine("After:");
Console.WriteLine(" Total: " + o.Total);
看起来容易得多,是吧?下面是这段代码的输出结果:
优先级和制止Before:
Total: 4.6
After:
Total: 11.5
虽然我猜字游戏,我发现它不能保证将运行规程规定英寸他们甚至不运行在相同的顺序,他们进入到规则集。有时,它是必要的运行在一个特定的顺序的规则。解决的办法是使用优先。<DmRule cond="this.Foo == 5" name="Rule1" priority="2">
<ThenStmts>
<DmCdStmt xsi:type="Assignment" left="this.Bar" right=""High"" />
</ThenStmt>
</DmRule>
<DmRule cond="this.Bar == "High"" name="Rule2" priority="1">
<ThenStmts>
<DmCdStmt xsi:type="Assignment" left="this.SomeProperty" right="true" />
</ThenStmt>
</DmRule>
在此规则集的优先级属性显示的Rule1应该之前运行规则2。虽然它可能似乎的Rule1将在规则2运行反正,这是无法保证的。为了保证顺序,我们指定的优先级。在这种特殊情况下,正向链会接管,并认识到,我们改变了酒吧财产。如果规则1规则2运行前,可能发生的最坏的是,规则集运行两次。但是,想象一下你有几个规则,所有更改属性。除非你使用的优先次序,规则最终可能会运行比他们有更多的时间。
所需要的另一个特点是停止。 Workflow Foundation规则允许的暂停命令。这些命令可以插入到一个组,然后或其他行动的任何地方。对此我感到困惑的是,然后或其他行动允许的条件和循环,所以总有一个顺序流。因此,停止后的任何遥不可及。考虑到这一点,它是安全做出停止在一个规则的属性。<DmRule cond="this.Foo < 10" name="Rule1" haltAfterThen="true"
haltAfterElse="true" priority="1000">
<ThenStmts>...</ThenStmts>
<ElseStmts>...</ElseStmts>
</DmRule>
显然,停止默认为false,并且不需要指定。同样为优先。默认情况下,所有优先级均为零,和Workflow Foundation的决定,他们运行的顺序摘要
。。NET 3.0中新的Windows Workflow Foundation中添加了一个规则库,它可以是非常有用的。有次当你编写的应用程序,你觉得你需要规则,但没有工作流程。这是我已经把这个库的原因。微软提供了一个规则编写GUI,最终会写。规则文件,对与原始的源代码。这是对工作流的环境不错,但序列化是不是一个人类可读的格式。使用我的CodeDOM表达式解析器,我可以写在应用程序配置规则。我的目的是向人们介绍这个规则库,并给他们的能力嵌入到他们的应用程序规则。里面有什么
里面的文件列表:CodeDomExpParser - 我的CodeDOM表达式解析器库。DmCodeDom - CodeDom中的辅助库。Assignment.cs - 创建一个CodeAssignmentStatement。Declaration.cs - 创建一个CodeVariableDeclarationStatement。DmCdStmt.cs - 基类。
ExprStmt.cs - 创建一个CodeExpressionStatement。ForLoop.cs - 创建一个CodeIterationStatement。IfElse.cs - 创建一个CodeConditionStatement。DmCodeDom.TestHarness - NUnit的测试工具的DmCodeDom库。TestDmCd.cs - 包含在文章中提到的代码。DmRules配置\ DmRulesConfigHandler.cs - 配置节处理程序来读取应用程序配置的规则。DmRule.cs - 代表一个规则。DmRuleSet.cs - 保存所有的规则。DmRuleTypeSet.cs - 对同一个类型的规则。RuleExec.cs - 运行规则的静态辅助类。DmRules.TestHarness - DmRules NUnit的测试工具。BaseObject.cs - 一个基类,IsDirty属性。Order.cs - 订单类上面使用。OrderItem.cs - 上面使用的OrderItem类。TestDmRules.cs - 包含在文章中使用的代码。历史0.1:2006-06-20:初始版本
更新CodeDomExpParser库工作。NET 2.0中。创建与运行规则,每个类型的能力DmRules的初始版本。库的未来首要考虑的是跨类型链。0.2:2006-07-10:优先事项和制止
规则不能保证在任何特定的顺序运行。分配优先级,可以解决这个问题。有时,有必要停止处理其他规则,停止命令已添加这一点。