Forward Chaining of Rules

Chaining is based on the identified dependencies among rules; more specifically, the dependencies among the actions of a rule and the conditions of other rules. These dependencies can be identified or declared in one of three ways:

  • Implicit

  • Attribute-based

  • Explicit

Implicit

Implicit dependencies are identified automatically by the workflow runtime engine. When a RuleSet is first executed, each rule is analyzed to assess the fields/properties that it reads in its condition and writes to in its actions. This is done by traversing the expression in the condition and the statements in the actions. For example, assume the following rules:

Rule 1
IF this.discount > 0
THEN this.total = (1-this.discount) * this.subtotal

Rule 2
IF this.subtotal > 10000
THEN this.discount = 0.05

The workflow runtime engine would evaluate the rules and identify that Rule 1 reads the discount and subtotal fields and writes to the total field. Rule 2 reads the subtotal field and writes to the discount field; therefore, Rule 1 has a dependency on Rule 2. The result is that the workflow runtime engine will make sure that Rule 1 is evaluated or reevaluated whenever Rule 2 executes its Then action.

Dependencies are identified at the leaf node level, as shown in the following RuleSet example.

Rule 1
IF this.order.Discount > 0
THEN this.order.Total = (1-this.order.Discount) * this.order.Subtotal

Rule 2
IF this.order.Subtotal > 10000
THEN this.order.Discount= 0.05

Rule 3 
IF this.order.CustomerType = "Residential"
THEN ...

A dependency would still be identified between Rules 1 and 2. However, Rule 3 would not have a dependency on either Rule 1 or 2, because neither updates the CustomerType property. In other words, the dependency is identified at the level of the CustomerType property, not the Order object itself.

Through implicit chaining, Windows Workflow Foundation does most of the necessary chaining for the user. Therefore, the user does not have to explicitly model updates, and can usually be unconcerned with chaining or the need for it. This makes the modeling of complex rulesets more straightforward and a mostly hidden capability of the workflow runtime engine.

The two other mechanisms to drive chaining, attribute-based and explicit, are provided for more complex and specific scenarios.

Attribute-Based

For method calls within a rule, it becomes more difficult to deterministically evaluate the reads/writes that occur. To address this problem, Windows Workflow Foundation provides three attributes that can be applied to a method to indicate its actions:

  • RuleRead

  • RuleWrite

  • RuleInvoke

Attributing a method with the RuleReadAttribute attribute indicates that it reads the indicated property. Similarly, the RuleWriteAttribute attribute can be used to indicate that a method updates a given field or property. Assume that our first two rules under the implicit section were rewritten as:

Rule 1
IF this.discount > 0
THEN this.total = (1-this.discount) * this.subtotal

Rule 2
IF this.subtotal > 10000
THEN this.SetDiscount(0.05)

The SetDiscount method could then be attributed as follows. This would enable the engine to identify that Rule 1 is dependent on Rule 2 because of the use of the discount field.

[RuleWrite("discount")] 
void SetDiscount(double requestedDiscount)
{
...//Some code that updates the discount field.
}

The RuleInvokeAttribute attribute can be used to dictate dependencies that are caused by linked method calls. For example, assume the following modification to the rules and methods:

Rule 1
IF this.discount > 0
THEN this.total = (1-this.discount) * this.subtotal

Rule 2
IF this.subtotal > 10000
THEN this.SetDiscountWrapper(0.05)

[RuleInvoke("SetDiscount")]
void SetDiscountWrapper(double requestedDiscount)
{
     ...
     SetDiscount(requestedDiscount);
     ...
}

[RuleWrite("discount")] 
void SetDiscount(double requestedDiscount)
{
}

Rule 2’s action calls SetDiscountWrapper. This in turn calls SetDiscount, which writes to the discount field. The RuleInvokeAttribute attribute enables this indirect write to be declared and detected by the workflow runtime engine.

You should recognize that the field or property that is referenced in the attribute path refers to a field or property on the same class as the method. This is not necessarily the root object passed to the RuleSet for execution. For example, you might attribute the Order class as follows:

public class Order
{
     private double discount;

     public double Discount
     {
         get { return discount;}
         set { discount = value;}
     }

     [RuleWrite("Discount")] 
     void CalculateDiscount(double requestedDiscount, double weighting)
     {
...       //Some code that updates the discount field.
     }
}

You could then use an instance of this class in a workflow as follows:

public class Workflow1 : SequentialWorkflowActivity
{
     private Order discount;
     ...
}

Execution of Rule 2 would cause reevaluation of Rule 1:

Rule 1
IF this.order.Discount > 5
THEN ...

Rule 2
IF ...
THEN this.order.CalculateDiscount( 5.0, .7)

Using Attributes

Some additional comments on the use of attributes:

  • Attributes can be used to specify how parameters are used in the method. For example, the following method is attributed to indicate that it modifies the Discount property on the passed Order instance.

    [RuleWrite("currentOrder/Discount", RuleAttributeTarget.Parameter)]
    private void SetDiscount(Order currentOrder, double discount)
    {
        currentOrder.Discount = discount;
    }
    
  • Wildcard characters can also be used in the rule attributes. For example, RuleWrite("order/*") can be used to indicate that all fields on the object referenced by the "order" field are modified by the method. However, the wildcard character can be used only at the end of the path; an attribute like RuleWrite("*/Discount") is invalid.

  • An attribute like RuleWrite("order") can be used with reference types to indicate that the reference has changed, for example, to indicate that the variable now points to a different Order instance. All rules that use a field/property on the variable are assumed to be affected, in addition to all rules that test the instance reference itself; for example, IF this.order == this.order2.

  • The default behavior if no method attributes are specified is that a method call is assumed not to read or write any fields/properties on the target object (the object on which the method is invoked). Additionally, the method call is assumed to read parameters and write parameters according to keywords (ref, out, ByVal, ByRef, and so on) defined in the .NET Framework.

Explicit

The final mechanism for indicating field/property dependencies is by using the Update statement. The Update statement takes as its argument either a string that represents the path to a field or property or an expression that represents a field/property access. For example, either of the following two statements can be typed into the RuleSet Editor to create an Update statement on the Name property of a Customer instance on the workflow.

Update("this/customer/Name") 
OR
Update(this.customer.Name)

Note

The RuleSet Editor will always display the update as Update("this/customer/Name").

The Update statement indicates that the rule writes to the indicated field/property. This would have the same effect as a direct set of the field/property in the rule or the calling of a method with a RuleWriteAttribute for the field/property.

The Update statement also supports the use of wildcard characters. For example, you can add the following Update statement to a rule:

Update("this/customer/*")

This causes any rules that use any property on the Customer instance in their conditions to be reevaluated. In other words, the following two rules would be reevaluated:

IF this.customer.ZipCode == 98052
THEN ...

IF this.customer.CreditScore < 600
THEN ...

Generally, you do not have to model explicit Update statements in most scenarios; Implicit chaining should provide the required dependency analysis and chaining. Method attributing should support the most prevalent scenarios where implicit chaining cannot identify the dependencies. Generally, method attributing would be the preferred method to indicate dependencies over the use of the Update statement, because the dependency can be identified one time on the method and used across many different rules that use that method. Also, in scenarios where the rule writer and method implementer are different individuals (or the work is done at different times), method attributing enables the method writer, with a better understanding of the code, to identify what the dependencies are of the method.

However, there are some scenarios in which the Update statement is the appropriate solution, such as when a field/property is passed to a method on a class that you, the workflow writer, do not control and therefore cannot attribute. This is demonstrated in the following example.

IF ...
THEN this.customer.UpdateCreditScore(this.currentCreditScore)
Update(this.currentCreditScore)

See Also

Reference

RuleSet
RuleUpdateAction
RuleHaltAction
RuleWriteAttribute
RuleReadAttribute
RuleReadWriteAttribute
RuleInvokeAttribute

Concepts

Using RuleSets in Workflows
Rules Evaluation in RuleSets