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.
Overview
Section titled “Overview”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.
Basic Usage
Section titled “Basic Usage”Mapping from Event Context Properties
Section titled “Mapping from Event Context Properties”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.
Available Event Context Properties
Section titled “Available Event Context Properties”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);Mapping from Event Properties
Section titled “Mapping from Event Properties”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.
Convention-Based Mapping
Section titled “Convention-Based Mapping”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);Combining with Other Attributes
Section titled “Combining with Other Attributes”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:
- The
Nameproperty is updated viaSetFrom<UserNameChanged> - The
LastUpdatedproperty is updated viaFromEvery
Use Cases
Section titled “Use Cases”Audit Timestamps
Section titled “Audit Timestamps”Track when a read model was last modified:
[FromEvery(contextProperty: nameof(EventContext.Occurred))]DateTimeOffset LastModifiedEvent Sequencing
Section titled “Event Sequencing”Keep track of the last event sequence number:
[FromEvery(contextProperty: nameof(EventContext.SequenceNumber))]ulong LastEventSequenceCorrelation Tracking
Section titled “Correlation Tracking”Maintain the correlation ID of the last operation:
[FromEvery(contextProperty: nameof(EventContext.CorrelationId))]string LastOperationIdCausation Tracking
Section titled “Causation Tracking”Track who or what caused the last change:
[FromEvery(contextProperty: nameof(EventContext.CausedBy))]string LastModifiedByComparison with Fluent API
Section titled “Comparison with Fluent API”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.
Best Practices
Section titled “Best Practices”- Use for metadata:
FromEveryis ideal for tracking metadata like timestamps, sequence numbers, and correlation IDs - Avoid business logic: Don’t use
FromEveryfor business-critical properties that should only change under specific conditions - Context properties preferred: When possible, prefer mapping from event context properties over event properties for consistency
- Clear naming: Use property names that clearly indicate they’re updated by all events (e.g.,
LastUpdated,LastModified)
Limitations
Section titled “Limitations”- The
FromEveryattribute 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
FromEveryattribute can be applied per property (multiple updates to the same property from different event context fields should use the fluent API instead)