Multi-Subscriber Compute

Summary

You can attach many compute subscribers to one property. Each later subscriber can read the prior subscriber's value, modify it, or fully replace it.

Subscribers run bottom-up (last registered first). A subscriber only triggers earlier ones when it reads args.PriorSubscriberValue.


How it runs

graph BT
    S1["S1 — base price = 30"]
    S2["S2 — adds 10 to prior"]
    S3["S3 — If contract → 45"]
    S3 -->|"If false<br/>→ skip to S2"| S2
    S2 -->|"reads<br/>PriorSubscriberValue"| S1
  • Start at the last subscriber.
  • If its If is false → move down to the next subscriber.
  • If its expression reads args.PriorSubscriberValue → the next-lower subscriber runs.
  • If its expression does not read args.PriorSubscriberValue → the lower subscribers are skipped.

Three patterns

1. Base value

invoice.Compute(d => d.UnitPrice)
    .From(d => 30m)
    .DirtyBy(d => d.Product);

2. Modify prior value

invoice.Compute(d => d.UnitPrice)
    .From((d, args) => args.PriorSubscriberValue + 10m)
    .DirtyBy(d => d.Product);

3. Override with skip

invoice.Compute(d => d.UnitPrice)
    .If(d => d.Doc.SellToCustomer.HasContractPricing())
    .From(d => 45m)              // does not read PriorSubscriberValue
    .DirtyBy(d => d.Product);

When If is true → S1 and S2 never run. When If is false → engine falls through to S2 → S2 reads prior → S1 runs.


Result table

Scenario S3 If Final value Subscribers that ran
Contract customer true 45 S3
Regular customer false 40 S3 (skipped) → S2 → S1
Only S1 registered 30 S1
Only S2 registered 10 S2 (prior = default(decimal) = 0)

PriorSubscriberValue

public TValue? PriorSubscriberValue { get; }
  • Lazy — does not evaluate prior subscribers unless you read it.
  • Cached — reading it twice in one subscriber runs prior only once.
  • Default — returns default(TValue) if there is no prior subscriber.

Multiple subscribers across [Logic] classes

Subscribers from different [Logic] classes can target the same property. Order is decided by feature ordering (see Features) at generator time, then by [RegisterLogic] method source order within one class.

graph LR
    A["Sales/SalesPriceBL<br/>.From(c => 100)"] --> B["_computeEvents"]
    C["Sales/Doc/SalesDocPriceBL<br/>.From((c,a) => a.PriorSubscriberValue + 50)"] --> B
    B --> D["Read entity.Price → 150"]