Skip to content

From Every Event

Use an every-event mapping when a read model property should update whenever any event in the projection is processed. This is usually audit metadata such as the last modification time, the latest event sequence number, or the correlation id of the operation that last touched the read model.

Every-event mappings are different from event-specific mappings. A set mapping applies only when its event type is processed. An every-event mapping applies after any event that belongs to the projection.

Event context is the most common source for every-event mappings because every event has the same context shape. This keeps audit fields independent from the event payloads.

Update an audit timestamp from every event
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Keys;
using Cratis.Chronicle.Projections.ModelBound;
[EventType]
public record InventoryProductRegisteredForEvery(string ProductName);
[EventType]
public record InventoryItemsAdjustedForEvery(int Quantity);
[FromEvent<InventoryProductRegisteredForEvery>]
[FromEvent<InventoryItemsAdjustedForEvery>]
public record InventoryStatusFromEvery(
[Key] Guid Id,
string ProductName,
[FromEvery(contextProperty: nameof(EventContext.Occurred))]
DateTimeOffset LastUpdated);

You can map multiple context fields when the read model needs a small audit trail.

Track audit metadata from every event
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Keys;
using Cratis.Chronicle.Projections.ModelBound;
[EventType]
public record AuditableInventoryChangedForEvery(string Reason);
[FromEvent<AuditableInventoryChangedForEvery>]
public record AuditableInventoryStatusFromEvery(
[Key] Guid Id,
[FromEvery(contextProperty: nameof(EventContext.Occurred))]
DateTimeOffset LastModified,
[FromEvery(contextProperty: nameof(EventContext.SequenceNumber))]
EventSequenceNumber LastEventSequence,
[FromEvery(contextProperty: nameof(EventContext.CorrelationId))]
string LastCorrelationId);

Every-event mappings can also read from event payload properties. Use this only when each event in the projection carries the same property name and compatible value type.

Read a shared event property from every event
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Keys;
using Cratis.Chronicle.Projections.ModelBound;
public enum OrderStateFromEvery
{
New,
Confirmed,
Shipped
}
[EventType]
public record OrderConfirmedForEvery(OrderStateFromEvery Status);
[EventType]
public record OrderShippedForEvery(OrderStateFromEvery Status);
[FromEvent<OrderConfirmedForEvery>]
[FromEvent<OrderShippedForEvery>]
public record OrderStatusFromEvery(
[Key] Guid Id,
[FromEvery(property: nameof(OrderConfirmedForEvery.Status))]
OrderStateFromEvery CurrentStatus);

If a client supports convention-based property mapping, omitting the source property uses the read model property name.

Use the read model property name by convention
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Keys;
using Cratis.Chronicle.Projections.ModelBound;
[EventType]
public record ProductRenamedForEveryConvention(string Name, int Version);
[EventType]
public record ProductPriceChangedForEveryConvention(decimal Price, int Version);
[FromEvent<ProductRenamedForEveryConvention>]
[FromEvent<ProductPriceChangedForEveryConvention>]
public record ProductVersionFromEveryConvention(
[Key] Guid Id,
string Name,
decimal Price,
[FromEvery]
int Version);

When an event is processed, Chronicle applies the event-specific mappings for that event and the every-event mappings for the projection. This lets the read model update business fields and audit metadata in the same event pass.

Combine specific mappings with every-event metadata
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Keys;
using Cratis.Chronicle.Projections.ModelBound;
[EventType]
public record UserRegisteredForEvery(string Name, string Email);
[EventType]
public record UserNameChangedForEvery(string NewName);
[EventType]
public record UserEmailChangedForEvery(string NewEmail);
[FromEvent<UserRegisteredForEvery>]
[FromEvent<UserNameChangedForEvery>]
[FromEvent<UserEmailChangedForEvery>]
public record UserProfileFromEvery(
[Key] Guid Id,
[SetFrom<UserRegisteredForEvery>(nameof(UserRegisteredForEvery.Name))]
[SetFrom<UserNameChangedForEvery>(nameof(UserNameChangedForEvery.NewName))]
string Name,
[SetFrom<UserRegisteredForEvery>(nameof(UserRegisteredForEvery.Email))]
[SetFrom<UserEmailChangedForEvery>(nameof(UserEmailChangedForEvery.NewEmail))]
string Email,
[FromEvery(contextProperty: nameof(EventContext.Occurred))]
DateTimeOffset LastUpdated);

For example, when the name-change event in the examples above is processed:

  1. The name mapping updates the read model’s name.
  2. The every-event mapping updates the last-modified field from the event context.

Every-event mappings are part of the projection definition Chronicle receives. Clients that expose both model-bound and standalone projection definitions send the same kind of every-event definition to Chronicle.

Declarative projection with every-event metadata
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Keys;
using Cratis.Chronicle.Projections;
[EventType]
public record InventoryRegisteredDeclarativeForEvery(string ProductName);
[EventType]
public record InventoryAdjustedDeclarativeForEvery(int Quantity);
public record InventoryStatusDeclarativeFromEvery(
[property: Key] Guid Id,
string ProductName,
DateTimeOffset LastUpdated);
public class InventoryStatusDeclarativeProjection : IProjectionFor<InventoryStatusDeclarativeFromEvery>
{
public void Define(IProjectionBuilderFor<InventoryStatusDeclarativeFromEvery> builder) => builder
.From<InventoryRegisteredDeclarativeForEvery>()
.From<InventoryAdjustedDeclarativeForEvery>()
.FromEvery(_ => _
.Set(m => m.LastUpdated)
.ToEventContextProperty(c => c.Occurred));
}
Use caseTypical source
Last modification timeEvent context occurrence time
Last event sequenceEvent context sequence number
Last operation idEvent context correlation id
Current status shared by all eventsEvent payload property

Prefer context properties for audit metadata. Use payload properties only when the event contracts are intentionally consistent. If a property should update only for selected event types, use an event-specific mapping instead.

  • Only one every-event mapping can target the same read model property.
  • Payload-property mappings are skipped for events that do not carry the source property.
  • Child-projection inclusion is client-specific; check the client-specific projection API if you need every-event mappings to include or exclude child projections.