StreamKey Attribute¶
Verified by tests
StreamIdGeneratorTests, StreamIdExtractorTests — library CI run #31657041675 (2026-08-13)
Updated
The [StreamKey] attribute no longer exists as a separate attribute. It has been unified into [StreamId], which now serves both purposes: identifying the event store stream an event belongs to, and grouping/ordering events per stream for perspective processing. Apply [StreamId] everywhere older documentation said [StreamKey].
Migration¶
StreamKey to StreamId
// Before (historical API - no longer compiles)
public record ProductCreatedEvent : IEvent {
[StreamKey]
public Guid ProductId { get; init; }
}
// After (current API)
public record ProductCreatedEvent : IEvent {
[StreamId]
public Guid ProductId { get; init; }
}
The same replacement applies to perspective model types - models mark their stream identity property with [StreamId]:
Model StreamId
public record ProductDto {
[StreamId] // Identifies which product this model represents
public Guid ProductId { get; init; }
public string Name { get; init; } = string.Empty;
public decimal Price { get; init; }
}
public class ProductCatalogPerspective : IPerspectiveFor<ProductDto, ProductCreatedEvent> {
public ProductDto Apply(ProductDto currentData, ProductCreatedEvent eventData) {
return new ProductDto {
ProductId = eventData.ProductId,
Name = eventData.Name,
Price = eventData.Price
};
}
}
How Perspective Ordering Works Today¶
The role the historical [StreamKey] attribute played in perspectives is now filled by [StreamId]:
- The source generators discover
[StreamId]on event, command, and perspective model properties and generate zero-reflection extractors (StreamIdExtractorsin your assembly's.Generatednamespace). - At runtime, perspective processing groups events by their extracted stream ID, so all events for one aggregate (e.g. Order #123) are applied in order within that stream.
- Events are applied to the model through pure
Apply()methods; the updated model is saved with its checkpoint per stream.
Requirements¶
- Exactly one
[StreamId]property per event type used in a perspective - Perspective model types must also have a
[StreamId]property, or no runner is generated - Property type must yield a
Guid:Guid,Guid?, a WhizbangId-style value type, or astring/other type whose value parses as a Guid (null or non-parseable values extract no stream ID)
Diagnostics¶
The source generators validate [StreamId] usage for perspectives:
WHIZ030: Missing StreamId¶
Error: An event used in a perspective has no [StreamId] property.
WHIZ030: Missing StreamId
// ❌ Causes WHIZ030
public record ProductEvent : IEvent {
public Guid ProductId { get; init; } // No [StreamId]!
}
// ✅ Fixed
public record ProductEvent : IEvent {
[StreamId]
public Guid ProductId { get; init; }
}
See WHIZ030 Diagnostic for details.
WHIZ031: Multiple StreamIds¶
Error: An event has multiple properties marked with [StreamId].
WHIZ031: Multiple StreamIds
// ❌ Causes WHIZ031
public record OrderEvent : IEvent {
[StreamId]
public Guid OrderId { get; init; }
[StreamId] // Only one [StreamId] allowed!
public Guid CustomerId { get; init; }
}
// ✅ Fixed - Choose the primary aggregate
public record OrderEvent : IEvent {
[StreamId] // Order is the primary aggregate
public Guid OrderId { get; init; }
public Guid CustomerId { get; init; } // Related entity, not stream ID
}
See WHIZ031 Diagnostic for details.
WHIZ033: Perspective Model Missing StreamId¶
Warning: A perspective's model type has no [StreamId] property. The perspective will not get a generated runner until the model marks its stream identity property.
Best Practices¶
- Put
[StreamId]on the aggregate root identifier (e.g.OrderId), not on related entity IDs or timestamps - Use the same property name across all events of one aggregate and on the matching model
- Prefer time-ordered UUIDv7 values (
TrackedGuid.NewMedo()or GenerateStreamId) for stream IDs
See Also¶
- StreamId Attribute - The unified attribute (full reference)
- GenerateStreamId Attribute - Auto-generate stream IDs at dispatch time
- Perspectives - Pure function perspectives
- WHIZ030 Diagnostic - Missing StreamId error
- WHIZ031 Diagnostic - Multiple StreamId error