Skip to content

Get read models

Goal: your events project into a read model — now you need it back out. One book, the whole catalog, a page for a grid, or a live stream that updates the UI as events arrive. IReadModels (on IEventStore.ReadModels) covers all of these; the choice is between eventually consistent reads served from the materialized sink — the default — and strongly consistent reads of a passive read model, computed from the event log on every call.

GetInstanceById returns the instance for one key. For a materialized read model — the default — it reads the stored instance from the sink, so it is eventually consistent. For a [Passive] read model it replays the key’s events through the projection or reducer on demand, reflecting everything appended up to this moment:

using Cratis.Chronicle;
using Cratis.Chronicle.Events;
public record ScenariosQueryBook(string Title, bool OnLoan);
public class ScenariosQueryBookService(IEventStore eventStore)
{
public Task<ScenariosQueryBook> GetBook(EventSourceId bookId) =>
eventStore.ReadModels.GetInstanceById<ScenariosQueryBook>(bookId.Value);
}

View C# snippet source on GitHub

ReadModelKey converts implicitly from string, Guid, and EventSourceId, so you can pass the id you already have.

GetInstances returns every instance in one call — read from the sink for a materialized read model, replayed from the event log for a passive one. Either way the whole set comes back at once, so filter the result with LINQ and keep it for small sets:

using Cratis.Chronicle;
public class ScenariosQueryOnLoanBooks(IEventStore eventStore)
{
public async Task<IEnumerable<ScenariosQueryBook>> GetOnLoan()
{
var books = await eventStore.ReadModels.GetInstances<ScenariosQueryBook>();
return books.Where(b => b.OnLoan);
}
}

View C# snippet source on GitHub

For list views and grids, skip the replay: Materialized reads the instances the projection has already persisted to the sink — eventually consistent (typically milliseconds behind), fast regardless of history, and paged:

using Cratis.Chronicle;
public class ScenariosQueryBookPageService(IEventStore eventStore)
{
public Task<IEnumerable<ScenariosQueryBook>> GetPage() =>
eventStore.ReadModels.Materialized.GetInstances<ScenariosQueryBook>(skip: 0, take: 20);
}

View C# snippet source on GitHub

Watch<T>() returns an IObservable<ReadModelChangeset<T>> that emits whenever any instance of that read model type changes — the changeset carries the key, the new state, and whether the instance was removed:

using Cratis.Chronicle;
public class ScenariosQueryBookWatcher(IEventStore eventStore)
{
public IDisposable Watch() =>
eventStore.ReadModels.Watch<ScenariosQueryBook>()
.Subscribe(changeset =>
{
if (changeset.Removed || changeset.ReadModel is null)
{
return;
}
Console.WriteLine($"{changeset.ModelKey}: on loan = {changeset.ReadModel.OnLoan}");
});
}

View C# snippet source on GitHub

Dispose the subscription when you’re done. If you need explicit lifecycle control — for example awaiting the watcher’s Subscribed before producing events — use GetWatcherFor<Book>() instead.

Materialized.ObserveInstances pushes a fresh page snapshot whenever the stored data changes — a live dashboard or table with no polling:

using Cratis.Chronicle;
public class ScenariosQueryLiveBookPage(IEventStore eventStore)
{
public IDisposable Subscribe(Action<IEnumerable<ScenariosQueryBook>> updateView) =>
eventStore.ReadModels.Materialized
.ObserveInstances<ScenariosQueryBook>(take: 50)
.Subscribe(books => updateView(books));
}

View C# snippet source on GitHub

When a command appends BookBorrowed, the projection updates the materialized Book and every subscriber receives the new page. The brief gap between append and push is the normal eventual consistency window — usually imperceptible.

You needUseConsistency
One instance, exact current stateGetInstanceById<T>(key)Strong
All instances, exact current stateGetInstances<T>()Strong
A page for a list or gridMaterialized.GetInstances<T>(skip, take)Eventual
To react to every change of a typeWatch<T>()Pushed as changes apply
A live-updating pageMaterialized.ObserveInstances<T>(skip, take)Eventual, pushed