Table of Contents

Class LazyStateMachine<TState, TTrigger>

Namespace
Trellis.StateMachine
Assembly
Trellis.StateMachine.dll

A lazy wrapper around Stateless.StateMachine<TState, TTrigger> that defers machine construction until first use, solving the EF Core materialization problem.

public sealed class LazyStateMachine<TState, TTrigger> where TState : notnull where TTrigger : notnull

Type Parameters

TState

The type representing the states of the state machine.

TTrigger

The type representing the triggers/events of the state machine.

Inheritance
LazyStateMachine<TState, TTrigger>
Inherited Members
Extension Methods

Remarks

EF Core invokes the parameterless constructor before populating entity properties. If a state machine is configured eagerly in the constructor using a stateAccessor lambda (e.g., () => Status), the accessor reads a default or uninitialized value — reference-type and value-object states throw, while enum states silently start the machine in the wrong state. This forces aggregates to use a manual null-coalescing pattern: _machine ??= ConfigureStateMachine().

LazyStateMachine<TState, TTrigger> eliminates that boilerplate by deferring both the stateAccessor/stateMutator invocation and the machine configuration until the first call to FireResult(TTrigger) or Machine.

This type is not thread-safe, consistent with the underlying Stateless.StateMachine<TState, TTrigger>. DDD aggregates are single-threaded consistency boundaries; concurrent access to the same instance is a design error.

Usage:

private readonly LazyStateMachine<OrderStatus, string> _machine;

public Order()
{
    _machine = new LazyStateMachine<OrderStatus, string>(
        () => Status,
        s => Status = s,
        ConfigureStateMachine);
}

private static void ConfigureStateMachine(StateMachine<OrderStatus, string> machine)
{
    machine.Configure(OrderStatus.Draft)
        .Permit("submit", OrderStatus.Submitted);
}

Constructors

LazyStateMachine(Func<TState>, Action<TState>, Action<StateMachine<TState, TTrigger>>)

Initializes a new instance of the LazyStateMachine<TState, TTrigger> class.

public LazyStateMachine(Func<TState> stateAccessor, Action<TState> stateMutator, Action<StateMachine<TState, TTrigger>> configure)

Parameters

stateAccessor Func<TState>

A function that returns the current state. Not invoked until first use.

stateMutator Action<TState>

An action that sets the current state. Not invoked until first use.

configure Action<StateMachine<TState, TTrigger>>

A callback that configures the state machine's transitions. Invoked once, when the machine is first accessed.

Properties

Machine

Gets the underlying Stateless.StateMachine<TState, TTrigger>, creating and configuring it on first access.

public StateMachine<TState, TTrigger> Machine { get; }

Property Value

StateMachine<TState, TTrigger>

Remarks

The stateAccessor and stateMutator lambdas are first invoked when this property is accessed, ensuring entity properties are fully populated by EF Core before the machine reads state.

Methods

FireResult(TTrigger)

Fires the specified trigger and returns the new state as a Result<TValue>.

public Result<TState> FireResult(TTrigger trigger)

Parameters

trigger TTrigger

The trigger to fire.

Returns

Result<TState>

A Result<TValue> containing the new state if the transition is valid, or an Error.UnprocessableContent with reason code state.machine.invalid.transition if the trigger cannot be fired from the current state.

Remarks

Delegates to FireResult<TState, TTrigger>(StateMachine<TState, TTrigger>, TTrigger). On first call, the underlying machine is lazily created and configured.