Skip to content

Saga Guide

Sagas coordinate long-running business processes as a sequence of events and actions with state tracking and compensation.

public enum CheckInState
{
Initial,
GuestVerified,
RoomAssigned,
Completed,
Cancelled
}
[Saga<CheckInState>]
public partial class CheckInSaga : ISaga<CheckInState>
{
public Guid Id { get; set; }
public CheckInState State { get; set; }
public string CorrelationId { get; set; } = "";
public DateTimeOffset StartedAt { get; set; }
public DateTimeOffset? CompletedAt { get; set; }
// Saga-specific data
public string? RoomNumber { get; set; }
[SagaStart]
public VerifyGuestAction Handle(GuestArrived @event)
{
return new VerifyGuestAction(@event.GuestId);
}
[InState(CheckInState.GuestVerified, NextState = CheckInState.RoomAssigned)]
[CompensateWith<CancelCheckInAction>]
public AssignRoomAction Handle(IdentityVerified @event)
{
return new AssignRoomAction(@event.GuestId);
}
[InState(CheckInState.RoomAssigned, NextState = CheckInState.Completed)]
public object? Handle(RoomAssigned @event)
{
RoomNumber = @event.RoomNumber;
CompletedAt = DateTimeOffset.UtcNow;
return null; // Terminal -- no further action
}
}
public interface ISaga<TState> where TState : struct, Enum
{
Guid Id { get; set; }
TState State { get; set; }
string CorrelationId { get; set; }
DateTimeOffset StartedAt { get; set; }
DateTimeOffset? CompletedAt { get; set; }
}
AttributeTargetDescription
[Saga<TState>]ClassMarks a saga with state enum
[SagaStart]MethodEntry point handler (Initial state)
[InState(state, NextState)]MethodHandler for a specific state
[CompensateWith<TAction>]MethodAction dispatched on failure (rollback)
[SagaTimeout(Seconds)]ClassAuto-cancel if not completed within timeout

The SG generates {Saga}.Orchestrator.g.cs with:

  • State routing: switch on current state + incoming event type
  • Compensation chain: on failure, dispatches [CompensateWith] actions in reverse order
  • Transition validation: compile-time graph analysis from [InState] + NextState
msg.EnableSagas(); // InMemory repositories (dev)
msg.EnableSagaPersistence(); // EF Core repositories (prod)

EF Core persistence uses two tables:

TablePurpose
__SagaInstancesSaga state, correlation, timestamps
__SagaStepsIndividual step execution history

When a step fails, the orchestrator walks the compensation chain in reverse order. Each [CompensateWith<TAction>] action is dispatched as a message, allowing the compensating logic to be asynchronous and independently retriable.

The compensation chain includes only steps that have already been executed. If step 3 fails, only compensations for steps 2 and 1 are dispatched (in that order).

Use [SagaTimeout(Seconds)] on the saga class to auto-cancel sagas that do not complete within the specified duration. The timeout is checked by a background service that polls for stale saga instances.

[Saga<CheckInState>]
[SagaTimeout(Seconds = 300)] // 5-minute timeout
public partial class CheckInSaga : ISaga<CheckInState> { ... }

When a saga times out, its state is set to the terminal/cancelled state and any configured compensation actions are dispatched.

IDSeverityMessage
PRAG0810ErrorInconsistent saga state transition
PRAG0811InfoSaga state has no handler (terminal states OK)
PRAG0812WarningUnreachable saga states
PRAG0813Error[Saga<T>] where T is not an enum
PRAG0814ErrorSaga without [SagaStart]