ReadOnly Event

Summary

The ReadOnly event controls whether a property can be written to. When a read-only condition evaluates to true, any attempt to set the property throws an InvalidOperationException. Multiple read-only subscribers can exist on the same property — if any condition is true, the property is read-only.

When does it fire?

Read-only checks are evaluated when a property setter is called, before any other processing (AutoCorrect, Validate, etc.):

Set property → ReadOnly? → Yes → Throw InvalidOperationException
                         → No  → Continue (AutoCorrect → Validate → ...)

Read-only state is also evaluated when the API retrieves read-only metadata for the UI. This means read-only conditions are called frequently and must be extremely fast.

Syntax

salesOrderDetail.ReadOnly(d => d.Quantity)
    .If(d => d.SalesOrder != null && d.SalesOrder.Status == SalesOrderStatus.Closed);

Fluent API

Step Method Description
Required .If(condition) Condition that, when true, makes the property read-only

Scenarios

1. Locking the properties on a Sales Order when the order is finalized

Properties on the sales order are also locked once the order is finalized.

[Logic]
public class SalesOrderFinalizeBL(SalesOrder.Logic salesOrder, SalesOrderDetail.Logic salesOrderDetail)
{
    [RegisterLogic]
    public void ReadOnlyWhenFinalized()
    {
        salesOrder.ReadOnly(o => o.SellToCustomer).If(IsOrderFinalized);
        salesOrder.ReadOnly(o => o.OrderDate).If(IsOrderFinalized);
        salesOrder.ReadOnly(o => o.ShippingAmount).If(IsOrderFinalized);

        salesOrderDetail.ReadOnly(d => d.Product).If(IsOrderFinalized);
        salesOrderDetail.ReadOnly(d => d.Description).If(IsOrderFinalized);
        salesOrderDetail.ReadOnly(d => d.Quantity).If(IsOrderFinalized);
        salesOrderDetail.ReadOnly(d => d.UnitPrice).If(IsOrderFinalized);

    }

    static bool IsOrderFinalized(SalesOrder o) =>
        o.Status == SalesOrderStatus.Shipped || o.Status == SalesOrderStatus.Closed;
    
    static bool IsOrderFinalized(SalesOrderDetail d) =>
        d.SalesOrder != null
        && (d.SalesOrder.Status == SalesOrderStatus.Shipped
         || d.SalesOrder.Status == SalesOrderStatus.Closed);

}

2. Multiple read-only conditions on the same property

A property can have multiple read-only rules. If any condition is true, the property is read-only.

[Logic]
public class SalesOrderDetailBL(SalesOrderDetail.Logic salesOrderDetail)
{
    [RegisterLogic]
    public void ReadOnlyOnOrderStatus()
    {
        salesOrderDetail.Quantity.ReadOnly()
            .If(d => d.SalesOrder != null && d.SalesOrder.Status == SalesOrderStatus.Closed);
    }

    [RegisterLogic]
    public void ReadOnlyOnDetailShipped()
    {
        // Also read-only when the detail line has been shipped
        salesOrderDetail.Quantity.ReadOnly()
            .If(d => d.IsShipped);
    }
}

3. Checking read-only state before setting a property

When business logic needs to set a property that might be read-only, check the state first.

var isReadOnly = orderDetail._State.Quantity.IsReadOnly;
if (!isReadOnly)
    orderDetail.Quantity = newQuantity;

Performance

Read-only conditions are evaluated on every property set and when the API retrieves property metadata. Your condition must be extremely fast:

  • Do: Reference in-memory properties on the entity or its parent.
  • Do not: Make database calls, LINQ queries, or expensive service calls. If you need data from the database or a service, compute it once into a property and reference that property in the read-only condition. This will only compute when necessary.

Notes

  • Attempting to set a read-only property throws InvalidOperationException.
  • The read-only state is tracked on _State.<Property>.IsReadOnly and is available to the API so the UI can disable fields.
  • Read-only conditions are cumulative — multiple subscribers combine with OR logic.