---
title: Declarative Projections
---

Declarative projections let you define read models with a fluent, code-first projection builder. They are ideal when you need explicit mapping, joins, or hierarchical projections that go beyond simple attribute mapping.

## Overview

Declarative projections implement `IProjectionFor<TReadModel>` and use an `IProjectionBuilderFor<TReadModel>` to define how events map to read model properties. The builder gives you control over mapping, relationships, and event context while keeping the projection definition separate from the read model type.

## Basic Example

```csharp
using Cratis.Chronicle.Projections;

[EventType]
public record UserRegistered(string Name, string Email, DateTimeOffset RegisteredAt);

public record UserProfile(string Name, string Email, DateTimeOffset RegisteredAt);

public class UserProfileProjection : IProjectionFor<UserProfile>
{
  public void Define(IProjectionBuilderFor<UserProfile> builder) => builder
    .From<UserRegistered>();
}
```

Auto-mapping is enabled by default at the top level, so matching properties are mapped automatically. When you need explicit mappings, you can use `.Set()`, `.Add()`, `.Subtract()`, and other builder operations.

## Discovery

Projection types are discovered by implementing `IProjectionFor<TReadModel>`. Event types used in projection definitions must be marked with `[EventType]`.

## Key Features

- **Auto mapping by default** with the option to turn it off or override per event
- **Explicit property mappings** for custom transformations
- **Hierarchies** with child collections and nested projections
- **Joins** across streams for richer read models
- **FromEvery** for applying mappings to all events
- **Event context** access for timestamps, sequence numbers, and IDs
- **Event sequence selection** for sourcing from non-default sequences
- **Passive and not rewindable projections** for specialized observation behavior

## When to Use

Use declarative projections when:

- You need control over mapping logic and transformations
- You need relationships or hierarchical read models
- You want to share a projection definition across multiple read models

Use model-bound projections when:

- The mapping is straightforward and attribute-based
- You want to keep projection metadata close to the read model
- You prefer concise, declarative attributes over fluent definitions

## Projection recipes

| Recipe | Description |
| ------ | ----------- |
| [Simple projection](/chronicle/projections/declarative/simple-projection/) | Basic projection using AutoMap() |
| [AutoMap](/chronicle/projections/declarative/auto-map/) | Automatic property mapping at different levels |
| [Passive](/chronicle/projections/declarative/passive/) | In-memory projections for on-demand lookups |
| [Set properties](/chronicle/projections/declarative/set-properties/) | Explicit property mapping and transformations |
| [Children](/chronicle/projections/declarative/children/) | Hierarchical data models with child collections |
| [Joins](/chronicle/projections/declarative/joins/) | Cross-stream projections using joins |
| [Functions](/chronicle/projections/declarative/functions/) | Arithmetic and other functions |
| [Composite keys](/chronicle/projections/declarative/composite-keys/) | Multi-property key identification |
| [Event context](/chronicle/projections/declarative/event-context/) | Using event metadata in projections |
| [FromEvery](/chronicle/projections/declarative/from-every/) | Setting properties for all events in a projection |
| [FromAll](/chronicle/projections/declarative/from-all/) | Subscribe to all event types without filtering |
| [Initial values](/chronicle/projections/declarative/initial-values/) | Default values for read model properties |
| [RemoveWithJoin](/chronicle/projections/declarative/remove-with-join/) | Cross-stream child removal |
| [FromEventSequence](/chronicle/projections/declarative/from-event-sequence/) | Sourcing events from specific event sequences |
| [NotRewindable](/chronicle/projections/declarative/not-rewindable/) | Forward-only projections that cannot be replayed |

## Reading Your Declarative Projections

Once you've defined a declarative projection, you can retrieve and observe the resulting read models using the `IReadModels` API:

- [Getting a Single Instance](/chronicle/read-models/getting-single-instance/) - Retrieve a specific instance by key with strong consistency
- [Getting a Collection of Instances](/chronicle/read-models/getting-collection-instances/) - Retrieve all instances for reporting and analysis
- [Getting Snapshots](/chronicle/read-models/getting-snapshots/) - Retrieve historical state snapshots grouped by correlation ID
- [Watching Read Models](/chronicle/read-models/watching-read-models/) - Observe real-time changes as events are applied


## Key concepts

### Auto-mapping vs explicit mapping

- **Auto-mapping**: Automatically maps properties with matching names between events and read models
  - Use `.AutoMap()` in fluent projections (`IProjectionFor<T>`)
  - Use `[FromEvent<TEvent>]` in model-bound projections for the same functionality
- **Explicit mapping**: Gives you full control over property transformations and mappings
  - Use `.Set()`, `.Add()`, etc. in fluent projections
  - Use `[SetFrom<TEvent>]`, `[AddFrom<TEvent>]`, etc. in model-bound projections

### Event handling

- Projections can handle multiple event types
- Each event type can have its own property mappings
- Properties are updated incrementally as events are processed

### Keys and identification

- **EventSourceId** is used as the default key for both read models and parent identification in child collections
- **Child identifiers**: Use `.IdentifiedBy()` to specify how child items are uniquely identified within collections
- **Parent key resolution**:
  - By default, the EventSourceId is used to identify the parent when adding children
  - Use `.UsingParentKey(e => e.Property)` when the parent identifier is in the event content
  - Use `.UsingParentKeyFromContext(ctx => ctx.EventSourceId)` to explicitly document default behavior
- **Child key specification**: Use `.UsingKey(e => e.Property)` to extract the child identifier from event content
- **Joins**: Use keys to link data from different event streams using `.On(m => m.Property)`

### Performance

- Projections are automatically maintained as events are appended
- The system handles indexing and query optimization
- Consider join complexity and update frequency when designing projections
