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);

View C# snippet source on GitHub

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);

View C# snippet source on GitHub

When a UserConnected event occurs, ActiveConnections increases by 1. When a UserDisconnected event occurs, it decreases by 1.

The Count attribute adds one to the property for every matching event, starting from zero, so on its own it holds the number of times the event occurred. It is not a separate running tally: it adds to whatever the property currently holds, so another attribute that writes the same property changes the number it counts on from.

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

View C# snippet source on GitHub

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);

View C# snippet source on GitHub

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);

View C# snippet source on GitHub

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