Event Projections

sub-classing the Marten.Events.EventProjection class will let you efficiently write a projection where you can explicitly define document operations on individual events. In essence, the EventProjection recipe effectively does pattern matching for you.

To show off what EventProjection does, here's a sample that uses pretty well everything that EventProjection supports:

public class SampleEventProjection : EventProjection
{
    public SampleEventProjection()
    {
        // Inline document operations
        Project<Event1>((e, ops) =>
        {
            // I'm creating a single new document, but
            // I can do as many operations as I want
            ops.Store(new Document1
            {
                Id = e.Id
            });
        });

        Project<StopEvent1>((e, ops) =>
        {
            ops.Delete<Document1>(e.Id);
        });

        ProjectAsync<Event3>(async (e, ops) =>
        {
            var lookup = await ops.LoadAsync<Lookup>(e.LookupId);
            // now use the lookup document and the event to carry
            // out other document operations against the ops parameter
        });
    }

    // This is the conventional method equivalents to the inline calls above
    public Document1 Create(Event1 e) => new Document1 {Id = e.Id};

    public void Project(StopEvent1 e, IDocumentOperations ops)
        => ops.Delete<Document1>(e.Id);

    public async Task Project(Event3 e, IDocumentOperations ops)
    {
        var lookup = await ops.LoadAsync<Lookup>(e.LookupId);
        // now use the lookup document and the event to carry
        // out other document operations against the ops parameter
    }

    // This will apply to *any* event that implements the ISpecialEvent
    // interface. Likewise, the pattern matching will also work with
    // common base classes
    public void Project(ISpecialEvent e, IDocumentOperations ops)
    {

    }
}

snippet source | anchor

And that projection can run either inline or asynchronously with the registration as shown below:

var store = DocumentStore.For(opts =>
{
    opts.Connection("some connection string");

    // Run inline...
    opts.Projections.Add(new SampleEventProjection(), ProjectionLifecycle.Inline);

    // Or nope, run it asynchronously
    opts.Projections.Add(new SampleEventProjection(), ProjectionLifecycle.Async);
});

snippet source | anchor

The EventProjection supplies the ProjectEvent() and ProjectEventAsync() methods if you prefer to use inline Lambda methods to define the operations that way. Your other option is to use either the Create() or Project() method conventions.

Create() Method Convention

The Create() method can accept these arguments:

  • The actual event type or Event<T> where T is the event type. One of these is required
  • IEvent to get access to the event metadata
  • Optionally take in IDocumentOperations if you need to access other data. This interface supports all the functionality of IQuerySession

The Create() method needs to return either:

  • The document to be created
  • Or Task<T> where the T is the document that is going to be created in this projection

Project() Method Convention

The Project() methods can accept these arguments:

  • The actual event type or Event<T> where T is the event type. One of these is required.
  • IEvent to get access to the event metadata
  • IDocumentOperations is mandatory, and this is what you'd use to register any document operations

The return value must be either void or Task depending on whether or not the method needs to be asynchronous