Skip to content

FromEvery Attribute

The FromEvery attribute allows you to set properties that should be updated for every event that the projection processes, regardless of the specific event type. This is particularly useful for tracking metadata like timestamps, sequence numbers, or other audit fields that should be updated whenever any event affects the read model.

Unlike event-specific attributes like SetFrom<TEvent> which only apply when a specific event type occurs, FromEvery applies to all events that are part of the projection. This ensures that certain properties are always kept up-to-date across all event types.

The most common use case is to map from event context properties like the occurrence timestamp:

using Cratis.Chronicle.Events;
using Cratis.Chronicle.Keys;
using Cratis.Chronicle.Projections.ModelBound;
public record InventoryStatus(
[Key]
Guid Id,
[SetFrom<ProductRegisteredInInventory>(nameof(ProductRegisteredInInventory.ProductName))]
string ProductName,
[AddFrom<ItemsAddedToInventory>(nameof(ItemsAddedToInventory.Quantity))]
[SubtractFrom<ItemsRemovedFromInventory>(nameof(ItemsRemovedFromInventory.Quantity))]
int CurrentStock,
[FromEvery(contextProperty: nameof(EventContext.Occurred))]
DateTimeOffset LastUpdated);

In this example, LastUpdated will be set to the event’s occurrence time for every event that affects this inventory item, whether it’s a product registration, items being added, or items being removed.

You can map to any property available on the EventContext:

public record AuditableEntity(
[Key] Guid Id,
[FromEvery(contextProperty: nameof(EventContext.Occurred))]
DateTimeOffset LastModified,
[FromEvery(contextProperty: nameof(EventContext.SequenceNumber))]
ulong LastEventSequence,
[FromEvery(contextProperty: nameof(EventContext.CorrelationId))]
string LastCorrelationId);

You can also map from properties that exist on every event type in your projection. When you specify a property name, the attribute will attempt to read that property from each event:

public record OrderStatus(
[Key] Guid Id,
[FromEvery(property: "Status")]
string CurrentStatus);

This assumes that all events in the projection have a Status property. If an event doesn’t have the specified property, the mapping is skipped for that event.

If you don’t specify either property or contextProperty, the attribute will use the read model property name to look for a matching property on each event:

public record Product(
[Key] Guid Id,
[FromEvery] // Looks for 'Version' property on all events
int Version);

FromEvery works seamlessly with other projection attributes. Each event can trigger both event-specific mappings and the FromEvery mapping:

public record UserProfile(
[Key] Guid Id,
[SetFrom<UserCreated>(nameof(UserCreated.Name))]
[SetFrom<UserNameChanged>(nameof(UserNameChanged.NewName))]
string Name,
[SetFrom<UserCreated>(nameof(UserCreated.Email))]
[SetFrom<UserEmailChanged>(nameof(UserEmailChanged.NewEmail))]
string Email,
[FromEvery(contextProperty: nameof(EventContext.Occurred))]
DateTimeOffset LastUpdated);

When a UserNameChanged event occurs:

  1. The Name property is updated via SetFrom<UserNameChanged>
  2. The LastUpdated property is updated via FromEvery

Track when a read model was last modified:

[FromEvery(contextProperty: nameof(EventContext.Occurred))]
DateTimeOffset LastModified

Keep track of the last event sequence number:

[FromEvery(contextProperty: nameof(EventContext.SequenceNumber))]
ulong LastEventSequence

Maintain the correlation ID of the last operation:

[FromEvery(contextProperty: nameof(EventContext.CorrelationId))]
string LastOperationId

Track who or what caused the last change:

[FromEvery(contextProperty: nameof(EventContext.CausedBy))]
string LastModifiedBy

The FromEvery attribute provides the same functionality as the FromEvery() method in fluent projections:

Fluent Projection:

public class InventoryStatusProjection : IProjectionFor<InventoryStatus>
{
public void Define(IProjectionBuilderFor<InventoryStatus> builder) => builder
.AutoMap()
.FromEvery(_ => _
.Set(m => m.LastUpdated).ToEventContextProperty(c => c.Occurred))
.From<ProductRegisteredInInventory>()
.From<ItemsAddedToInventory>()
.From<ItemsRemovedFromInventory>();
}

Model-Bound Projection:

[FromEvent<ProductRegisteredInInventory>]
public record InventoryStatus(
[Key] Guid Id,
string ProductName,
int CurrentStock,
[FromEvery(contextProperty: nameof(EventContext.Occurred))] DateTimeOffset LastUpdated);

Both approaches produce identical results, but the model-bound approach keeps the metadata definition directly on the read model.

  1. Use for metadata: FromEvery is ideal for tracking metadata like timestamps, sequence numbers, and correlation IDs
  2. Avoid business logic: Don’t use FromEvery for business-critical properties that should only change under specific conditions
  3. Context properties preferred: When possible, prefer mapping from event context properties over event properties for consistency
  4. Clear naming: Use property names that clearly indicate they’re updated by all events (e.g., LastUpdated, LastModified)
  • The FromEvery attribute applies to all events in the projection - you cannot selectively exclude specific event types
  • When mapping from event properties, if an event doesn’t have the specified property, the mapping is silently skipped
  • Only one FromEvery attribute can be applied per property (multiple updates to the same property from different event context fields should use the fluent API instead)