---
title: Read Models
---

Read Models in Arc provide automatic dependency injection and seamless integration with Chronicle's projection system. The client automatically resolves read models based on the identity/key extracted from the command using flexible resolution strategies, with values provided through the [Command Context](/arc/backend/commands/command-context/) by [Resolving EventSourceId](/arc/backend/chronicle/resolving-event-source-id/). If the command context also contains a Chronicle `Subject`, Arc uses it when releasing the resolved read model so `[PII]` properties decrypt under the same compliance identity that was used for the events.

## Overview

Arc automatically registers all read model types and provides them as transient services in the dependency injection container. When a read model is requested, it uses the resolved identity from the current command context to load the appropriate projection instance.

## Automatic Registration

Read models are automatically discovered and registered when you configure Arc. This includes:

- Read models from `IProjectionFor<T>` implementations
- Read models from model-bound projections

The system will scan for all read model types and register them with the dependency injection container.

## Taking Dependencies on Read Models

You can inject read models directly into your commands through the Handle method signature, just like aggregate roots:

```csharp
public record UpdateUserProfileCommand([Key] Guid UserId, string DisplayName, string Bio)
{
    public object Handle(UserProfile profile, ILogger<UpdateUserProfileCommand> logger)
    {
        // The 'profile' read model is automatically loaded using the UserId
        // which is marked with [Key] to identify which instance to load
        
        logger.LogInformation(
            "Updating profile for {Email} from {OldName} to {NewName}",
            profile.Email,
            profile.DisplayName,
            DisplayName);

        // You can use the read model for validation or context
        if (profile.Status == UserStatus.Suspended)
        {
            throw new CannotUpdateSuspendedUserProfile(UserId);
        }

        return new UpdateUserProfileCommand { DisplayName = DisplayName, Bio = Bio };
    }
}
```

## Id/Key Resolution

The read model resolution works exactly the same way as [Aggregate Root](/arc/backend/chronicle/aggregates/aggregate-roots/) resolution. It depends on identifying which read model instance to load from the projection store. The system supports multiple strategies for resolving this identity, with [Resolving EventSourceId](/arc/backend/chronicle/resolving-event-source-id/) supplying the resolved value through the [Command Context Values](/arc/backend/commands/command-context/#command-context-values) pipeline. The resolution process works as follows:

1. **Identity Strategy Resolution**: The system inspects the command to determine the identity using one of the available strategies
2. **Command Context Lookup**: The resolved identity is retrieved from the current `CommandContext`
3. **Validation**: If no identity is found, an `UnableToResolveReadModelFromCommandContext` exception is thrown
4. **Projection Query**: The system queries Chronicle's projection store using `IReadModels.GetInstanceById()` with the resolved identity
5. **Subject Release**: If the command context has a resolved `Subject`, Arc releases the read model with that subject before returning it
6. **Instance Return**: The loaded read model instance is returned

### Id/Key Resolution Strategies

The system provides multiple strategies for resolving the identity used to load read models. The command can provide its identity through any of these approaches:

- **[Key] Attribute**: Mark a property with `[Key]` attribute (most explicit and recommended)
- **EventSourceId Type**: Have a property of type `EventSourceId` or a type that inherits from it
- **ICanProvideEventSourceId Interface**: Implement the interface and return the id from `GetEventSourceId()`
- **Tuple Composition**: Be part of a tuple that contains an `EventSourceId`

The `[Key]` attribute approach is the most flexible as it works with any type (Guid, string, int, etc.) and clearly communicates intent in the command definition.

## Example Usage

### Validation with Read Models

Read models are particularly useful for validation since they provide the current projected state:

```csharp
public record PlaceOrderCommand([Key] Guid OrderId, Guid CustomerId, OrderLine[] Items)
{
    public OrderPlaced Handle(OrderReadModel order)
    {
        // Use the read model to validate business rules
        if (order.Status != OrderStatus.Draft)
        {
            throw new CannotModifyNonDraftOrder(OrderId);
        }

        if (order.LineItems.Length + Items.Length > 100)
        {
            throw new TooManyOrderLines();
        }

        return new OrderPlaced
        {
            CustomerId = CustomerId,
            Items = Items
        };
    }
}
```

### Combining Read Models and Aggregate Roots

You can inject both read models and aggregate roots in the same handler:

```csharp
public record AddItemToCartCommand([Key] Guid CartId, Guid ProductId, int Quantity)
{
    public ItemAddedToCart Handle(
        ShoppingCart cart,              // Aggregate root
        ShoppingCartSummary summary,    // Read model
        ILogger<AddItemToCartCommand> logger)
    {
        // Use read model for display/validation
        logger.LogInformation(
            "Adding item to cart with {ItemCount} items totaling {Total}",
            summary.TotalItems,
            summary.TotalAmount);

        // Use aggregate root for command
        cart.AddItem(ProductId, Quantity);

        return new ItemAddedToCart { ProductId = ProductId, Quantity = Quantity };
    }
}
```

### Read-Only Commands

For commands that don't modify state, you can use only read models:

```csharp
public record GetUserStatsCommand([Key] Guid UserId)
{
    public UserStatsResponse Handle(UserStatistics stats)
    {
        // Just query the read model and return data
        return new UserStatsResponse
        {
            TotalPosts = stats.PostCount,
            TotalLikes = stats.LikeCount,
            MemberSince = stats.CreatedDate
        };
    }
}
```

### Using Read Models in Validators

Read models are particularly powerful when used in `CommandValidator<>` for complex validation scenarios. You can inject read models directly into your validator's constructor through dependency injection:

```csharp
public record AssignPersonToRoleCommand([Key] Guid RoleId, Guid PersonId)
{
    public PersonAssignedToRole Handle(Role role)
    {
        role.AssignPerson(PersonId);
        
        return new PersonAssignedToRole { PersonId = PersonId };
    }
}

public class AssignPersonToRoleValidator : CommandValidator<AssignPersonToRoleCommand>
{
    public AssignPersonToRoleValidator(RoleReadModel role)
    {
        RuleFor(x => x.PersonId)
            .NotEmpty()
            .WithMessage("Person ID is required");

        RuleFor(x => x.PersonId)
            .Must(personId => !role.AssignedPersonIds.Contains(personId))
            .WithMessage("Person is already assigned to this role");

        RuleFor(x => x)
            .Must(cmd => role.Status == RoleStatus.Active)
            .WithMessage("Cannot assign people to inactive roles");

        RuleFor(x => x)
            .Must(cmd => role.AssignedPersonIds.Length < role.MaxAssignments)
            .WithMessage($"Role has reached maximum assignments of {role.MaxAssignments}");
    }
}
```

Key points when using read models in validators:

- **Constructor Injection**: Read models are automatically resolved and injected, just like in command handlers
- **Event Source ID**: The read model instance is resolved using the same event source ID from the command
- **Subject-aware release**: If the command resolved a `Subject`, the injected read model is released with that subject before validation runs
- **Validation Context**: Perfect for validating against current state before executing the command
- **Rich Rules**: Access to full read model state enables complex business rule validation
- **Async Validation**: Use `MustAsync` for asynchronous validation rules that need to check external systems

For more details on command validation, see the [Validation](/arc/backend/commands/validation/) documentation.

## Error Handling

### UnableToResolveReadModelFromCommandContext

This exception is thrown when:

- No identity/key is available in the command context
- The resolved identity is `EventSourceId.Unspecified`

```csharp
public record InvalidCommand(string SomeProperty);
// No identity property, attribute, or interface implementation

// This will fail because no identity can be resolved
```

## Lifecycle Management

### Transient Scope

Read models are registered as transient services, meaning:

- The current projected state is fetched for each request
- The instance is tied to the specific identity/key resolved from the command context
- Read models are read-only snapshots of the current projection state
- The read model is automatically disposed after the command completes

### Read-Only Nature

Important characteristics of read models:

- **Read models are immutable** - Changes made to a read model instance are not persisted
- **For writes, use aggregate roots** - Only aggregate roots can emit events and change state
- **Eventually consistent** - Read models reflect the state as of the last processed event
- **Current as of query time** - The projection is fetched at the time of the command

## When to Use Read Models vs Aggregate Roots

Use **Read Models** when:
- You need to validate business rules against current state
- You need contextual information for logging
- You're implementing read-only queries
- You need denormalized or computed data
- You're validating commands with `CommandValidator<>`

Use **Aggregate Roots** when:
- You need to modify state and emit events
- You're implementing business logic that changes the domain
- You need transactional consistency within an aggregate boundary

Use **Both** when:
- You need to validate against current state (read model) before making changes (aggregate root)
- You want rich logging context while executing commands
- You need to perform validation in both the validator and the handler
