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
TStateThe type representing the states of the state machine.
TTriggerThe 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
stateAccessorFunc<TState>A function that returns the current state. Not invoked until first use.
stateMutatorAction<TState>An action that sets the current state. Not invoked until first use.
configureAction<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
triggerTTriggerThe 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.transitionif 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.