WF学习笔记——规则与规则集

目录

一 概述

二 常用类型

三 定义规则条件

四 定义规则集

一 概述

  规则是一条关于数据的声明性语句。在规则中声明了希望在运行时计算的条件,根据结果执行不同的操作。

  规则由3部分组成:

  • 条件 Boolean类型表达式。
  • Then操作 条件为true时的操作。
  • Else操作 条件为false时的操作。

  要在程序中使用规则引擎,首先要将业务逻辑需求组织为单独的规则(Rule),然后再将规则组织到规则集(RuleSet)中。规则与规则集都是程序代码。定义的规则是由规则引擎来执行的。规则的实际执行和计算是基于数据的,并且能和其他规则进行交互。规则集在执行时会计算每个规则,并执行合适的操作。默认情况下,规则会根据规则名称的字母顺序来执行,但可以显式指定。默认不会显式指定计算顺序,因为工作流规则引擎支持“正向链接”,它通过发现并监视规则之间的依赖关系项来工作。如果规则修改的变量会影响之前计算过的规则在其条件中引用的话,正向连接就会使之前的规则再次进行计算。(可以显式地控制规则依赖项,也可以完全关闭正向链接。)

  当规则条件或操作引用了一个字段或属性时,规则引擎能够自动地标识依赖项,并控制正向链接。但是,当条件或操作执行方法时,规则引擎就无法自己来确定依赖项了,此时需要我们显式地指定依赖项(使用特性)。

  在工作流中添加了规则之后,它们就会被序列化到单独的.rules文件,并生成为项目的嵌入式资源。rules资源就会在执行期间进行反序列化。也可以讲该文件保存到项目外部,然后在代码中显式地进行反序列化操作。这种分离提供了在不修改应用程序代码的前提下,更新业务逻辑规则的方法。

  基于规则的应用程序具有以下优势:

  • 规则将业务规则和程序代码清晰地分离。
  • 规则是声明业务需求和数据间关系的一种极为简单的方式。
  • 规则能够更加轻松地将业务需求翻译成能够运转的应用程序。
  • 基于规则的应用程序为真实的业务规则提供了更大的可视性。
  • 基于规则的应用程序具有更强的扩展性和适应性。

二 常用类型

  RuleSet、Rule、RuleAction的关系如下图所示:

  每个RuleSet包含多个Rule,而每个Rule又包含多个RuleAction的派生类实例。

WF学习笔记——规则与规则集_第1张图片

  Rule类用来声明单个业务规则。该类是单个规则条件以及至多两个规则操作的容器。其中一个操作会在条件计算为true时执行,另一个操作(如果定义)则会在条件为false时执行。

WF学习笔记——规则与规则集_第2张图片

  Role类的ThenAction和ElseAction属性都是IList<RuleAction>类型的,RuleAction是抽象类,在构造实例时其常用的派生类型如下:

WF学习笔记——规则与规则集_第3张图片

三 定义规则条件

(一) 声明性规则条件(DeclarativeRuleCondition) 

  打开规则编辑器:

WF学习笔记——规则与规则集_第4张图片

  定投规则条件:

WF学习笔记——规则与规则集_第5张图片

(二) 代码条件(CodeCondition)

  通过对ConditionalEventArgs事件参数的Result进行赋值来返回结果,其代码形如:

        private void IsTwo(object sender, ConditionalEventArgs e) { e.Result = this.Number == 2; }

  使用代码条件时,如果涉及到某个活动,则需要考虑活动执行上下文。工作流中的代码可以访问代表了子活动的变量,但是需要以上下文安全的方式访问该活动。在定义代码条件中要使用标准的语法来获取当前活动,而非模板。该实例在以后的文章中再做探讨,本篇博文实例并不适合演示该语法。

四 定义规则集

(一)规矩集的简单设置

  打开规则集编辑器:

WF学习笔记——规则与规则集_第6张图片

  定义规则集:

WF学习笔记——规则与规则集_第7张图片

WF学习笔记——规则与规则集_第8张图片

(二) 正向链接与ChainingBehavior

  当规则条件或操作引用了一个字段或属性时,规则引擎就能轻松地标识依赖项,并使用它来控制正向链接。如果某个操作修改了字段或属性,它就会标识其他在条件中引用同一个字段或属性的规则。

  VS默认不会指定规则执行顺序,我个人的习惯是不管是使用何种ChainingBehavior类型,我都会显式指定规则执行顺序,原因是这样可以增加代码的可读性。如果不指定执行顺序,那么,在构造操作表达式时就应该考虑到操作执行的顺序的无关性情况,而不应该构造出会由执行顺序造成结果变动的表达式。例如:

规则名称 条件 Then Else 优先级 描述
Discount this.Count > 10 this.Total = this.Price * this.Count * 0.95 + 15 this.Total = this.Price * this.Count + 15 2 多余10件打95折
Vip this.IsVip this.Total = this.Total * 0.9   0 会员打9折
Freight this.Total - 15 >= 50 this.Total = this.Total - 15   1 总价高于50元免运费

  这3个规则,在不同执行顺序下,执行结果是不同的。使用,与其考虑执行顺序无关性,不如显式指定执行顺序来的简单,而且程序的可读性更好。所以,我习惯在构造规则条件时,显式地指定执行顺序。

1 “顺序的”与“完全链接”

  当输入以下参数时,“Price=10,Count=9,IsVip=true”时,如果选择“顺序的”会输出“81”,其过程如下:

  1. Total=10*9+15=105
  2. Total=105-15=90
  3. Total=90*0.9=81

  如果选择“完全链接”会输出“54”,其过程如下:

  1. Total=10*9+15=105
  2. Total=105-15=90
  3. Total=90-15=75
  4. Total=75-15=60
  5. Total=60*0.9=54

  可见,原因在于Freight规则在重复计算,所以,当我将该规则的重新计算设置为“永不”时,“完全链接”与“顺序的”的计算结果相同。

2 “顺序的”与“仅显示更新”

  其它规则不变,修改Vip的Then为:

this.Total = this.Total * 0.9
Update("this/Total/") 

  如果选择“仅显示更新”会输出“66”,其过程如下:

  1. Total=10*9+15=105
  2. Total=105-15=90
  3. Total=90*0.9=81
  4. Total=81-15=66

  Update("this/Total/")用于表示该操作会修改Total属性。但因为,未在Freight规则中使用Update标识,所以Freight规则修改Total时不会触发重新计算,而Vip规则导致了重新计算。同样,如果我们将重复触及计算的规则的重新计算设置为“永不”同样会得到与“顺序的”相同的计算结果。

(三) 挂起规则

  在规则操作中输入“Halt”,将导其后的停止计算。例如,我将上例中的Discount规则的Else操作改为:

this.Total = this.Price * this.Count + 15
Halt

  则计算结果为“105”,计算过程为:

  1. Total=10*9+15=105

  计算过程只执行了一步,原因是执行了Halt,其后的规则不会继续执行。

(四) 显式关联的依赖项

   当条件或或操作执行方法时,规则引擎无法自动确定依赖项。WF提供了一组特性可以应用到方法(或属性)上,来解决当条件或操作执行方法时规则引擎就无法自己来确定依赖项的问题。这些特性包括:

 名称  描述
 RuleReadAttribute  标识该方法或属性会去读取的字段或属性
 RuleWriteAttribute  标识该方法或属性会去修改的字段或属性
 RuleInvokeAttribute   标识该方法或属性会去调用的其他方法或属性 

   将上例规则修改为:

规则名称 条件 Then Else 优先级
Discount this.Count > 10 this.Total = this.Price * this.Count * 0.95 + 15 this.Total = this.Price * this.Count + 15 2
Vip this.isVip() this.Total = this.Total * 0.9   1
Freight this.Total - 15 >= 50

this.Total = this.Total - 15

this.changeVip()

  0

  新增方法如下:

        //[RuleRead("IsVip")]
        private bool isVip() { return IsVip; } //[RuleWrite("IsVip")]
        private void changeVip() { IsVip = !IsVip; }

  当未标记特性时,输出为“64.5”,其计算过程如下:

  1. Total=10*9+15=105
  2. Total=105*0.9=94.5
  3. Total=94.5-15=79.5
  4. Total=79.5-15=64.5

  可见,依赖未自动标识。

  当标记特性时,输出为“58.05”,其计算过程如下:

  1. Total=10*9+15=105
  2. Total=105*0.9=94.5
  3. Total=94.5-15=79.5
  4. Total=79.5-15=64.5
  5. Total=64.5*0.9=58.05

  此时规则引擎通过特性确定了依赖项。

(五) 用代码创建规则集

  使用代码创建规则集,使用户能够在运行时动态生成规则集,其代码形如:

            //创建实例的引用
            CodeThisReferenceExpression ctreThis = new CodeThisReferenceExpression(); //创建属性的引用
            CodePropertyReferenceExpression cpreCount = new CodePropertyReferenceExpression(ctreThis, "Count"); CodePropertyReferenceExpression cprePrice = new CodePropertyReferenceExpression(ctreThis, "Price"); CodePropertyReferenceExpression cpreTotal = new CodePropertyReferenceExpression(ctreThis, "Total"); //创建规则集
            RuleSet rs = new RuleSet("CodeTest"); //创建规则
            Rule rule = new Rule("Discount"); //创建规则的条件“Count>0”
            CodeBinaryOperatorExpression discountCondition = new CodeBinaryOperatorExpression(cpreCount, CodeBinaryOperatorType.GreaterThan, new CodePrimitiveExpression(10)); //创建Then操作“Total=Count*Price*0.95”
            CodeAssignStatement discountThen = new CodeAssignStatement(cpreTotal, new CodeBinaryOperatorExpression(new CodeBinaryOperatorExpression(cpreCount, CodeBinaryOperatorType.Multiply, cprePrice), CodeBinaryOperatorType.Multiply, new CodePrimitiveExpression(0.95) )); //创建Else操作“Total=Count*Price”
            CodeAssignStatement discountElse = new CodeAssignStatement(cpreTotal, new CodeBinaryOperatorExpression(cpreCount, CodeBinaryOperatorType.Multiply, cprePrice)); //将条件关联到相应规则
            rule.Condition = new RuleExpressionCondition(discountCondition); //为规则添加Then操作
            rule.ThenActions.Add(new RuleStatementAction(discountThen)); //为规则添加Else操作
            rule.ElseActions.Add(new RuleStatementAction(discountElse)); //将规则放入规则集
 rs.Rules.Add(rule); //执行规则集
            RuleValidation rv = new RuleValidation(typeof(Workflow), null); if(rs.Validate(rv)) { RuleExecution re = new RuleExecution(rv, this); rs.Execute(re); } else { foreach(ValidationError error in rv.Errors) { Console.WriteLine(error.ErrorText); } }

   RuleValidation对象用来验证定义在规则集中的规则针对某种特定的对象类型是否有效。上例中使用Workflow,说明是针对该工作流开执行规则集的。使用RuleValidation对象验证完RuleSet之后,创建了一个RuleExecution对象。RuleExecution是一个执行包装器,它的作用是将RuleValidation对象和执行规则集的目标汇集在一起,“this”表示针对的是工作流实例来执行规则集。最后使用RuleSet对象的Execute方法来针对目标工作流实例执行该规则集。如果规则集验证失败,RuleValidation对象提供了一个错误集合(Errors),可以查看错误描述。

  使用代码执行工作流规则集的另一种情景是:先通过反射从资源中获取规则,或者从外部保存的.rules文件中获取规则,并使用WorkflowMarkupSerializer将规则反序列化为RuleDefinitions对象。(RuleDefinitions作为一个容器,能表示.rules文件的完整内容,它的内部是一个或多个RuleSet实例的定义。)通过RuleDefinitions得到相应的RuleSet后,就可以使用代码执行规则集了,其代码形如:

            Assembly assembly = Assembly.GetAssembly(typeof(Workflow)); Stream stream = assembly.GetManifestResourceStream("Workflow.rules"); using (XmlReader xr = XmlReader.Create(new StreamReader(stream))) { WorkflowMarkupSerializer wms = new WorkflowMarkupSerializer(); RuleDefinitions rd = wms.Deserialize(xr) as RuleDefinitions; if (rd != null) { if(rd.RuleSets.Contains("SetTotal")) { RuleSet rs=rd.RuleSets["SetTotal"]; RuleValidation rv = new RuleValidation(typeof(Workflow), null); if (rs.Validate(rv)) { RuleExecution re = new RuleExecution(rv, this); rs.Execute(re); } else { foreach (ValidationError error in rv.Errors) { Console.WriteLine(error.ErrorText); } } } } }

 

你可能感兴趣的:(学习笔记)