Skip to content

The search box knows all the secrets -- try it!

Polecat is part of the Critter Stack ecosystem.

JasperFx Logo JasperFx provides formal support for Polecat and other Critter Stack libraries. Please check our Support Plans for more details.

Single Stream Projections ​

Single stream projections build one aggregate document per event stream. This is the most common projection type.

Defining a Projection ​

Use conventional Apply methods:

cs
public class OrderSummary
{
    public Guid Id { get; set; }
    public string Status { get; set; } = "";
    public decimal TotalAmount { get; set; }
    public int ItemCount { get; set; }
    public DateTimeOffset? ShippedDate { get; set; }

    public static OrderSummary Create(OrderCreated e) =>
        new() { Status = "Created", TotalAmount = e.Amount };

    public void Apply(OrderItemAdded e)
    {
        TotalAmount += e.Price;
        ItemCount++;
    }

    public void Apply(OrderShipped e)
    {
        Status = "Shipped";
        ShippedDate = e.ShippedAt;
    }

    public bool ShouldDelete(OrderCancelled e) => true;
}

Registration ​

Inline ​

cs
opts.Projections.Add<SingleStreamProjection<OrderSummary, Guid>>(ProjectionLifecycle.Inline);

The projection runs in the same transaction as event appending. The aggregate is stored in pc_doc_ordersummary.

Async ​

cs
opts.Projections.Add<SingleStreamProjection<OrderSummary, Guid>>(ProjectionLifecycle.Async);

The async daemon processes events in the background.

Using IEvent Metadata ​

Access event metadata in your Apply methods:

cs
public void Apply(IEvent<OrderCreated> @event)
{
    Status = "Created";
    TotalAmount = @event.Data.Amount;
    CreatedAt = @event.Timestamp;
    CreatedBy = @event.Headers?["user"]?.ToString();
}

Identifying the Event Parameter ​

Notice that the examples above name the event parameter e in some methods and @event in others — both work. Polecat identifies the event argument of a conventional Create / Apply / ShouldDelete method (and an event projection's Project / Transform methods) by type, not by name, using the same rule for every projection type:

  1. A parameter typed IEvent<T> is always the event, and T is the event type — use this when you need event metadata such as Timestamp or Headers.
  2. Otherwise the single concrete parameter that is not an interface (IQuerySession, IDocumentOperations), not IEvent, not CancellationToken, and not the aggregate type is the event.

So Apply(OrderShipped e) and Apply(OrderShipped shipped) are equivalent — the parameter name is incidental. A conventional event parameter name is only consulted to disambiguate an unusual signature in which more than one parameter could be the event; the recognized names are @event, event, e, and ev.

Live Aggregation ​

Use a single stream projection for on-demand replay without persisting:

cs
var order = await session.Events.AggregateStreamAsync<OrderSummary>(streamId);

This replays all events in the stream through the Create and Apply methods.

Custom SingleStreamProjection Class ​

For more control, extend SingleStreamProjection<T>:

cs
public class OrderProjection : SingleStreamProjection<OrderSummary>
{
    public OrderSummary Create(OrderCreated e) =>
        new() { Status = "Created", TotalAmount = e.Amount };

    public void Apply(OrderItemAdded e, OrderSummary current)
    {
        current.TotalAmount += e.Price;
        current.ItemCount++;
    }
}

// Register
opts.Projections.Add<OrderProjection>(ProjectionLifecycle.Inline);

Released under the MIT License.