Custom Projections
To build your own Marten projection, you just need a class that implements the Marten.Events.Projections.IProjection
interface shown below:
cs
/// <summary>
/// Interface for all event projections
/// IProjection implementations define the projection type and handle its projection document lifecycle
/// Optimized for inline usage
/// </summary>
public interface IProjection
{
/// <summary>
/// Apply inline projections during synchronous operations
/// </summary>
/// <param name="operations"></param>
/// <param name="streams"></param>
void Apply(IDocumentOperations operations, IReadOnlyList<StreamAction> streams);
/// <summary>
/// Apply inline projections during asynchronous operations
/// </summary>
/// <param name="operations"></param>
/// <param name="streams"></param>
/// <param name="cancellation"></param>
/// <returns></returns>
Task ApplyAsync(IDocumentOperations operations, IReadOnlyList<StreamAction> streams,
CancellationToken cancellation);
}
The StreamAction
aggregates outstanding events by the event stream, which is how Marten tracks events inside of an IDocumentSession
that has yet to be committed. The IDocumentOperations
interface will give you access to a large subset of the IDocumentSession
API to make document changes or deletions. Here's a sample custom projection from our tests:
cs
public class QuestPatchTestProjection: IProjection
{
public Guid Id { get; set; }
public string Name { get; set; }
public void Apply(IDocumentOperations operations, IReadOnlyList<StreamAction> streams)
{
var questEvents = streams.SelectMany(x => x.Events).OrderBy(s => s.Sequence).Select(s => s.Data);
foreach (var @event in questEvents)
{
if (@event is Quest quest)
{
operations.Store(new QuestPatchTestProjection { Id = quest.Id });
}
else if (@event is QuestStarted started)
{
operations.Patch<QuestPatchTestProjection>(started.Id).Set(x => x.Name, "New Name");
}
}
}
public Task ApplyAsync(IDocumentOperations operations, IReadOnlyList<StreamAction> streams,
CancellationToken cancellation)
{
Apply(operations, streams);
return Task.CompletedTask;
}
}
And the custom projection can be registered in your Marten DocumentStore
like this:
cs
var store = DocumentStore.For(opts =>
{
opts.Connection("some connection string");
// Use inline lifecycle
opts.Projections.Add(new QuestPatchTestProjection(), ProjectionLifecycle.Inline);
// Or use this as an asychronous projection
opts.Projections.Add(new QuestPatchTestProjection(), ProjectionLifecycle.Async);
});