问题
I want to run a rule based on the result of a previous rule. How can I achieve this functionality using forward chaining? I don't want to create a different class object for each rule to achieve forward chaining.
Here in this example an InstantDiscount object is created just for this one rule to achieve forward chaining.
public class PreferredCustomerDiscountRule : Rule
{
public override void Define()
{
Customer customer = null;
IEnumerable<Order> orders = null;
Double total = Double.NaN;
When()
.Match<Customer>(() => customer, c => c.IsPreferred)
.Query(() => orders, x => x
.Match<Order>(
o => o.Customer == customer,
o => o.IsOpen)
.Collect())
.Let(() => total, () => orders.Sum(x => x.Amount))
.Having(() => total > 1000);
Then()
.Yield(_ => new InstantDiscount(customer, total * 0.05));
}
}
public class PrintInstantDiscountRule : Rule
{
public override void Define()
{
InstantDiscount discount = null;
When()
.Match(() => discount);
Then()
.Do(_ => Console.WriteLine("Customer {0} has instant discount of {1}",
discount.Customer.Name, discount.Amount));
}
}
回答1:
Forward chaining is the process where one rule changes the working memory of the rules engine in such a way as to activate some other rules. This can be achieved by inserting new facts into the rules engine (using Yield or IContext.Insert in NRules), or by changing some of the existing facts (using IContext.Update).
Here is the original example, reformulated to attach the discount to the Customer fact, and then update that fact to achieve forward chaining.
public class PreferredCustomerDiscountRule : Rule
{
public override void Define()
{
Customer customer = null;
IEnumerable<Order> orders = null;
Double total = Double.NaN;
When()
.Match<Customer>(() => customer, c => c.IsPreferred, c => !c.DiscountPercent.HasValue)
.Query(() => orders, x => x
.Match<Order>(
o => o.Customer == customer,
o => o.IsOpen)
.Collect())
.Let(() => total, () => orders.Sum(x => x.Amount))
.Having(() => total > 1000);
Then()
.Do(ctx => ApplyDiscount(customer, 0.05))
.Do(ctx => ctx.Update(customer));
}
private static void ApplyDiscount(Customer customer, double discount)
{
customer.DiscountPercent = discount;
}
}
public class DicsountNotificationRule : Rule
{
public override void Define()
{
Customer customer = null;
When()
.Match(() => customer, c => c.DiscountPercent.HasValue);
Then()
.Do(_ => Console.WriteLine("Customer {0} has instant discount of {1}%",
customer.Name, customer.DiscountPercent));
}
}
When forward chaining by updating existing facts, care must be taken to not re-activate the rule that updated the fact, to avoid undesirable recursion. There are several mechanisms to control recursion in NRules:
- Write conditions in such a way that the update invalidates conditions of the rule (this is what we did in the above example; once the discount is set, the rule will no longer match)
- Use Repeatability attribute on the rule to prevent re-firing
- Use agenda filters to only activate the rule when certain changes occur in the matching facts.
The later two options are described in NRules documentation.
来源:https://stackoverflow.com/questions/49946688/best-way-to-achieve-forward-chaining-in-nrules