Counters
Model-bound projections provide three counter operations for tracking occurrences and quantities: Increment, Decrement, and Count.
Increment
Section titled “Increment”The Increment attribute increments a numeric property when an event occurs. This is useful for tracking counters that increase with specific events.
using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
public record UserStatistics( [Key] Guid UserId,
[Increment<UserLoggedIn>] int LoginCount);Each time a UserLoggedIn event occurs, LoginCount is incremented by 1.
Decrement
Section titled “Decrement”The Decrement attribute decrements a numeric property when an event occurs. This is useful for tracking decreasing counters.
public record ServerStatistics( [Key] Guid ServerId,
[Increment<UserConnected>] [Decrement<UserDisconnected>] int ActiveConnections);When a UserConnected event occurs, ActiveConnections increases by 1. When a UserDisconnected event occurs, it decreases by 1.
The Count attribute counts the total number of times an event occurs. Unlike Increment, Count doesn’t increment from a current value—it maintains an absolute count.
public record EventMetrics( [Key] Guid Id,
[Count<OrderPlaced>] int TotalOrders,
[Count<OrderCancelled>] int CancelledOrders);Multiple Events
Section titled “Multiple Events”You can use multiple attributes on the same property to respond to different events:
public record InventoryItem( [Key] Guid ItemId,
[SetFrom<ItemCreated>(nameof(ItemCreated.Name))] string Name,
[SetFrom<ItemCreated>(nameof(ItemCreated.InitialQuantity))] [Increment<ItemRestocked>] [Decrement<ItemSold>] int Quantity,
[Count<ItemRestocked>] int RestockCount,
[Count<ItemSold>] int SalesCount);Complete Example
Section titled “Complete Example”Here’s a complete example tracking various metrics:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
// Events[EventType]public record UserLoggedIn(DateTimeOffset Timestamp);
[EventType]public record UserLoggedOut(DateTimeOffset Timestamp);
[EventType]public record PurchaseMade(decimal Amount);
[EventType]public record RefundIssued(decimal Amount);
// Read Modelpublic record UserActivity( [Key] Guid UserId,
// Track login/logout counts [Count<UserLoggedIn>] int TotalLogins,
[Count<UserLoggedOut>] int TotalLogouts,
// Track active sessions [Increment<UserLoggedIn>] [Decrement<UserLoggedOut>] int ActiveSessions,
// Track transaction counts [Count<PurchaseMade>] int PurchaseCount,
[Count<RefundIssued>] int RefundCount,
// Track transaction values [AddFrom<PurchaseMade>(nameof(PurchaseMade.Amount))] [SubtractFrom<RefundIssued>(nameof(RefundIssued.Amount))] decimal NetSpent);Counter vs Count
Section titled “Counter vs Count”Increment/Decrement:
- Modifies the current value
- Useful for tracking active states (sessions, connections)
- Can be combined with SetFrom to establish initial values
- Changes are relative to current value
Count:
- Maintains absolute count of event occurrences
- Useful for analytics and reporting
- Independent of other operations
- Always represents total occurrences
Best Practices
Section titled “Best Practices”- Use Increment/Decrement for tracking active/current states that change over time
- Use Count for analytics and metrics that track total occurrences
- Combine operations on the same property when tracking both current state and history
- Initialize counters with SetFrom when you have an initial value from a creation event