Skip to content

Counters

Model-bound projections provide three counter operations for tracking occurrences and quantities: Increment, Decrement, and Count.

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.Events;
using Cratis.Chronicle.Keys;
using Cratis.Chronicle.Projections.ModelBound;
[EventType]
public record MbCountersUserLoggedIn;
public record MbCountersUserStatistics(
[Key]
Guid UserId,
[Increment<MbCountersUserLoggedIn>]
int LoginCount);

Each time a UserLoggedIn event occurs, LoginCount is incremented by 1.

The Decrement attribute decrements a numeric property when an event occurs. This is useful for tracking decreasing counters.

[EventType]
public record MbCountersUserConnected;
[EventType]
public record MbCountersUserDisconnected;
public record MbCountersServerStatistics(
[Key]
Guid ServerId,
[Increment<MbCountersUserConnected>]
[Decrement<MbCountersUserDisconnected>]
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.

[EventType]
public record MbCountersOrderPlaced;
[EventType]
public record MbCountersOrderCancelled;
public record MbCountersEventMetrics(
[Key]
Guid Id,
[Count<MbCountersOrderPlaced>]
int TotalOrders,
[Count<MbCountersOrderCancelled>]
int CancelledOrders);

You can use multiple attributes on the same property to respond to different events:

[EventType]
public record MbCountersItemCreated(string Name, int InitialQuantity);
[EventType]
public record MbCountersItemRestocked;
[EventType]
public record MbCountersItemSold;
public record MbCountersInventoryItem(
[Key]
Guid ItemId,
[SetFrom<MbCountersItemCreated>(nameof(MbCountersItemCreated.Name))]
string Name,
[SetFrom<MbCountersItemCreated>(nameof(MbCountersItemCreated.InitialQuantity))]
[Increment<MbCountersItemRestocked>]
[Decrement<MbCountersItemSold>]
int Quantity,
[Count<MbCountersItemRestocked>]
int RestockCount,
[Count<MbCountersItemSold>]
int SalesCount);

Here’s a complete example tracking various metrics:

// Events
[EventType]
public record MbCountersUserLoggedInFull(DateTimeOffset Timestamp);
[EventType]
public record MbCountersUserLoggedOutFull(DateTimeOffset Timestamp);
[EventType]
public record MbCountersPurchaseMade(decimal Amount);
[EventType]
public record MbCountersRefundIssued(decimal Amount);
// Read Model
public record MbCountersUserActivity(
[Key]
Guid UserId,
// Track login/logout counts
[Count<MbCountersUserLoggedInFull>]
int TotalLogins,
[Count<MbCountersUserLoggedOutFull>]
int TotalLogouts,
// Track active sessions
[Increment<MbCountersUserLoggedInFull>]
[Decrement<MbCountersUserLoggedOutFull>]
int ActiveSessions,
// Track transaction counts
[Count<MbCountersPurchaseMade>]
int PurchaseCount,
[Count<MbCountersRefundIssued>]
int RefundCount,
// Track transaction values
[AddFrom<MbCountersPurchaseMade>(nameof(MbCountersPurchaseMade.Amount))]
[SubtractFrom<MbCountersRefundIssued>(nameof(MbCountersRefundIssued.Amount))]
decimal NetSpent);

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
  1. Use Increment/Decrement for tracking active/current states that change over time
  2. Use Count for analytics and metrics that track total occurrences
  3. Combine operations on the same property when tracking both current state and history
  4. Initialize counters with SetFrom when you have an initial value from a creation event