Table of Contents

Counters

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

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

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.

Count

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

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

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 Model
public 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

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

  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