Compute Event
Summary
The Compute event defines how a property value is calculated from other properties or data. Computed properties are lazily evaluated — they only recalculate when they are dirty (marked as needing recomputation). A property becomes dirty when one of its declared dependencies changes.
When does it fire?
A compute event fires when a dirty property is read (via its getter). It does not fire on every property change — only when the computed value is actually needed.
Get property → Is dirty? → Yes → Run Compute → Return new value
→ No → Return cached value
Syntax
salesOrderDetail.Compute(d => d.TotalPrice)
.From(d => d.Quantity * d.UnitPrice)
.DirtyBy(d => new { d.Quantity, d.UnitPrice });
Fluent API
| Step | Method | Description |
|---|---|---|
| Optional | .If(condition) |
Only compute when the condition is true |
| Required | .From(expression) |
The expression that produces the computed value |
| Recommended | .DirtyBy(properties) |
Which properties, when changed, mark this property as dirty |
| For collections | .DirtyWithRelation(collection) |
Marks dirty when items in a child collection change |
Scenarios
1. Calculating a line total from quantity and price
A sales order detail's total price is the product of quantity and unit price. Whenever either value changes, the total must be recalculated.
[Logic]
public class SalesOrderDetailBL(SalesOrderDetail.Logic salesOrderDetail)
{
[RegisterLogic]
public void ComputeTotalPrice()
{
salesOrderDetail.Compute(d => d.TotalPrice)
.From(d => d.Quantity * d.UnitPrice)
.DirtyBy(d => new { d.Quantity, d.UnitPrice });
}
}
2. Deriving a display name from multiple fields
A contact's full name is built from first and last name. It recomputes whenever either name part changes.
[Logic]
public class ContactBL(Contact.Logic contact)
{
[RegisterLogic]
public void ComputeFullName()
{
contact.Compute(c => c.FullName)
.From(c => $"{c.FirstName} {c.LastName}".Trim())
.DirtyBy(c => new { c.FirstName, c.LastName });
}
}
3. Computing a parent total from child collection
A sales order's subtotal is the sum of its detail line totals. The DirtyWithRelation method connects the parent to the child collection so that adding, removing, or changing a detail line marks the subtotal as dirty.
[Logic]
public class SalesOrderBL(SalesOrder.Logic salesOrder)
{
[RegisterLogic]
public void ComputeSubtotal()
{
salesOrder.Compute(o => o.Subtotal)
.From(o => o.Details.Sum(d => d.TotalPrice))
.DirtyWithRelation(o => o.Details)
.DirtyBy(d => d.TotalPrice);
}
[RegisterLogic]
public void ComputeTotal()
{
salesOrder.Compute(o => o.Total)
.From(o => o.Subtotal + o.ShippingAmount)
.DirtyBy(o => new { o.Subtotal, o.ShippingAmount });
}
}
4. Computing a sales order total from the detail collection
A sales order's total combines the subtotal (sum of detail lines) with shipping. The subtotal uses DirtyWithRelation to track the child Details collection — whenever a detail line is added, removed, or its TotalPrice changes, the subtotal is marked dirty. The total then depends on the subtotal and shipping amount using regular DirtyBy.
[Logic]
public class SalesOrderBL(SalesOrder.Logic salesOrder)
{
[RegisterLogic]
public void Totals()
{
salesOrder.Subtotal.Compute()
.From(doc => doc.Details.Sum(detail => detail.TotalPrice))
.DirtyWithRelation(o => o.Details)
.DirtyBy(d => [d.TotalPrice]);
salesOrder.Total.Compute()
.From(doc => doc.Subtotal + doc.ShippingAmount)
.DirtyBy(doc => [doc.Subtotal, doc.ShippingAmount]);
}
}
5. Compute with service injection
When the compute expression needs external data or services, use the overload that provides args.
[Logic]
public class CustomerBL(Customer.Logic customer)
{
[RegisterLogic]
public void ComputeOpenOrderCount()
{
customer.Compute(c => c.OpenOrderCount)
.From((c, args) =>
{
return args.GetEntities<SalesOrder>()
.Count(o => o.SellToCustomer == c
&& o.Status == SalesOrderStatus.Open);
});
}
}
Notes
- If
DirtyByis omitted, the property is only dirty on load (it computes once per entity load). - A Compute and Changed subscriber pair on the same property will not cause cycles. The Changed subscriber does not fire when the value is set by the Compute subscriber.
- Compute conditions (
.If()) must be fast — they are evaluated on every read of a dirty property.