Skip to content

State store

The LoadState extension method on IEventReader reads an event stream and folds it into a State<T> instance. Unlike LoadAggregate, it does not require an aggregate — it works directly with state types, making it the foundation for both the aggregate-based and functional command services.

The simplest overload takes a stream name and returns a FoldedEventStream<TState>:

var result = await eventReader.LoadState<BookingState>(
new StreamName("Booking-123"),
cancellationToken: cancellationToken
);
var state = result.State; // the folded state instance
var version = result.StreamVersion; // for optimistic concurrency on subsequent writes
var events = result.Events; // the raw event objects

By default, LoadState throws StreamNotFound if the stream doesn’t exist. Pass failIfNotFound: false to get an empty state instead:

var result = await eventReader.LoadState<BookingState>(
new StreamName("Booking-123"),
failIfNotFound: false,
cancellationToken: cancellationToken
);
// result.State is a new BookingState() with no events applied
// result.StreamVersion is ExpectedStreamVersion.NoStream

When you have a typed identity derived from Id, use the overload that takes a StreamNameMap and an id. This resolves the stream name using the registered mapping (or the default convention) and automatically sets the Id property on State<TState, TId> types:

var streamNameMap = new StreamNameMap();
var result = await eventReader.LoadState<BookingState, BookingId>(
streamNameMap,
new BookingId("123"),
cancellationToken: cancellationToken
);
// result.State.Id is set to the BookingId("123") instance

LoadState returns a FoldedEventStream<TState> record that contains everything needed for subsequent operations:

PropertyTypeDescription
StateTStateThe state instance built by folding all stream events.
StreamNameStreamNameThe stream the events were read from.
StreamVersionExpectedStreamVersionThe version of the last event in the stream.
Eventsobject[]The deserialized event payloads.

The StreamVersion value is used for optimistic concurrency when appending new events to the same stream:

var loaded = await eventReader.LoadState<BookingState>(streamName);
// ... produce new events based on the state ...
await eventWriter.Store(
loaded.StreamName,
loaded.StreamVersion,
newEvents,
cancellationToken: cancellationToken
);

LoadState reads the full stream using ReadStream (which handles pagination internally), then folds the events into a new state instance by calling State<T>.When(event) for each event in sequence. The fold produces an immutable state object that reflects the entire stream history.

This is the same mechanism used internally by:

  • The functional command service (CommandService<TState>) to load state before executing command handlers
  • The aggregate command service (CommandService<TAggregate, TState, TId>) via LoadAggregate, which wraps LoadState and attaches the result to an aggregate instance