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);

View C# snippet source on GitHub

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);

View C# snippet source on GitHub

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);

View C# snippet source on GitHub

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);

View C# snippet source on GitHub

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);

View C# snippet source on GitHub

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));
}

View C# snippet source on GitHub

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.

The kernel now honors child inclusion in every-event mappings. In .NET, [FromEvery] includes events handled only by child projections, including nested child collections. A child’s event updates the containing parent’s audit fields using the child’s resolved parent key; it does not create a root document keyed by the child identifier.

A child-only event advances a root [FromEvery] timestamp even without a root [FromEvent] for that event. In the declaration language, every includes child events by default; use exclude children to keep mappings local to their level.

Put [FromEvery] on a child’s own property to update that child instead. The mapping applies on the child’s creating event as well as later events handled by that child. The same ownership rule applies to nested objects and [FromAll]: a mapping belongs to the object that declares it. Parent fields are never copied into a newly added child.

For compatibility with clients that still put child mappings at the root, see Child and nested properties. A property declared on both root and child cannot be disambiguated until the client is upgraded.

  • 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. A source property that is present with a null value still maps null.
  • The .NET model-bound [FromEvery] attribute has no child-exclusion option today. Use a declarative projection with ExcludeChildProjections() when you need that opt-out.