Лучший способ добиться прямой цепочки в NRULES

Я хочу запустить правило на основе результата предыдущего правила. Как я могу достичь этой функциональности, используя прямую цепочку? Я не хочу создавать разные объекты класса для каждого правила, чтобы добиться прямой цепочки.

Здесь, в этом примере, объект InstantDiscount создается только для этого единственного правила для достижения прямого связывания.

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 ответ

Решение

Прямое связывание - это процесс, в котором одно правило изменяет рабочую память механизма правил таким образом, чтобы активировать некоторые другие правила. Этого можно достичь, вставив новые факты в механизм правил (используя Yield или IContext.Insert в NRules) или изменив некоторые из существующих фактов (используя IContext.Update).

Вот оригинальный пример, переформулированный так, чтобы привязать скидку к факту "Заказчик", а затем обновить этот факт, чтобы получить прямое сцепление.

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));
    }
}

При прямом объединении в цепочку путем обновления существующих фактов необходимо позаботиться о том, чтобы повторно не активировать правило, обновляющее факт, во избежание нежелательной рекурсии. Существует несколько механизмов управления рекурсией в NRules:

  • Запишите условия таким образом, чтобы обновление аннулировало условия правила (это то, что мы сделали в приведенном выше примере; после установки скидки правило больше не будет соответствовать)
  • Используйте атрибут Repeatability в правиле, чтобы предотвратить повторный запуск
  • Используйте фильтры повестки дня, чтобы активировать правило, только когда происходят определенные изменения в соответствующих фактах.

Два последних варианта описаны в документации NRules.

Другие вопросы по тегам