Skip to content

CQRS Repository Pattern

A practical guide to implementing Repository and Query adapters with C# Functorium


Does every new filter for your order list API mean adding yet another Repository method — GetByCustomer, GetRecent, SearchByKeyword…? Are read-only properties creeping into your domain model, polluting your write logic, and creating a vicious cycle where fixing one thing breaks another?

This tutorial solves that problem with Command and Query Responsibility Segregation (CQRS). Starting from domain entity fundamentals and progressing through Repository patterns, Query adapters, and use-case integration, you will learn every aspect of the CQRS pattern step by step through 22 hands-on projects.

“If you are adding a new Repository method every time a new query condition is needed, that is not design — it is inertia.”

LevelAudienceRecommended Scope
BeginnerDevelopers with basic CRUD and Entity experienceParts 0—1
IntermediateDevelopers who understand the Repository pattern and want deeper learningParts 2—3
AdvancedDevelopers interested in CQRS architecture design and use-case integrationParts 4—5 + Appendix

After completing this tutorial, you will be able to:

  1. Design separated Command (IRepository) and Query (IQueryPort) interfaces using the CQRS pattern
  2. Compose functional CQRS pipelines with FinT monad composition and Specification-based dynamic search
  3. Build a robust CQRS architecture with transaction pipelines and domain event flows

Start with why CQRS is needed and what problems it solves. Set up your environment and get an overview of the CQRS architecture.

Are two products with the same name the same product? Starting from Entity identity, build the domain modeling foundation for CQRS — covering Aggregate Root, domain events, and entity interfaces.

ChTopicKey Learning
1Entity and IdentityEntity<TId>, IEntityId, Ulid-based ID
2Aggregate RootAggregateRoot<TId>, domain invariants
3Domain EventsIDomainEvent, AddDomainEvent(), ClearDomainEvents
4Entity InterfacesIAuditable, ISoftDeletable

Part 2: Command Side — Repository Pattern

Section titled “Part 2: Command Side — Repository Pattern”

What interface do you need to persist domain models while guaranteeing their invariants? Progress from IRepository design for Aggregate Root-level write operations through InMemory and EF Core implementations to Unit of Work.

ChTopicKey Learning
1Repository InterfaceIRepository<TAggregate, TId>, 8 CRUD operations, FinT<IO, T>
2InMemory RepositoryInMemoryRepositoryBase, ConcurrentDictionary
3EF Core RepositoryEfCoreRepositoryBase, ToDomain/ToModel
4Unit of WorkIUnitOfWork, SaveChanges, IUnitOfWorkTransaction

Instead of adding a new method every time a query condition changes, handle dynamic searches with a single Specification. Optimize the read-only path through DTO projections and three pagination strategies.

ChTopicKey Learning
1IQueryPort InterfaceIQueryPort<TEntity, TDto>, Search/SearchByCursor/Stream
2DTO SeparationCommand DTO vs Query DTO, projections
3Pagination and SortingPageRequest, CursorPageRequest, SortExpression
4InMemory Query AdapterInMemoryQueryBase, GetProjectedItems
5Dapper Query AdapterDapperQueryBase, SQL generation

With Repository and Query adapters ready, it is time to integrate them into use cases. Dispatch Commands/Queries with the Mediator pattern, convert from FinT to FinResponse, wire up domain event flows and transaction pipelines to complete the CQRS architecture.

ChTopicKey Learning
1Command Use CaseICommandRequest, ICommandUsecase, FinResponse
2Query Use CaseIQueryRequest, IQueryUsecase, IQueryPort integration
3FinT -> FinResponseToFinResponse(), LINQ monadic composition
4Domain Event FlowIDomainEventCollector, Track, publish
5Transaction PipelineTransaction pipeline, Command auto-commit

Part 5: Domain-Specific Practical Examples

Section titled “Part 5: Domain-Specific Practical Examples”

Apply the CQRS patterns you have learned to real domains. See firsthand the benefits of Command/Query separation in order, customer, inventory, and catalog domains.

ChTopicKey Learning
1Order ManagementComplete order CQRS example
2Customer ManagementCustomer management + Specification search
3Inventory ManagementInventory + Soft Delete + cursor paging
4Catalog SearchComparison of 3 pagination approaches

[Part 1] Domain Entity Foundations Ch 1: Entity and Identity -> Ch 2: Aggregate Root -> Ch 3: Domain Events -> Ch 4: Entity Interfaces

[Part 2] Command Side — Repository Pattern Ch 1: Repository Interface -> Ch 2: InMemory Repository -> Ch 3: EF Core Repository -> Ch 4: Unit of Work

[Part 3] Query Side — Read-Only Patterns Ch 1: IQueryPort Interface -> Ch 2: DTO Separation -> Ch 3: Pagination and Sorting -> Ch 4: InMemory Query Adapter -> Ch 5: Dapper Query Adapter

[Part 4] CQRS Use-Case Integration Ch 1: Command Use Case -> Ch 2: Query Use Case -> Ch 3: FinT -> FinResponse -> Ch 4: Domain Event Flow -> Ch 5: Transaction Pipeline

[Part 5] Domain-Specific Practical Examples Ch 1: Order Management -> Ch 2: Customer Management -> Ch 3: Inventory Management -> Ch 4: Catalog Search


Command Side (Write)
├── IRepository<TAggregate, TId>
│ ├── Create / GetById / Update / Delete
│ ├── CreateRange / GetByIds / UpdateRange / DeleteRange
│ └── Return type: FinT<IO, T>
├── InMemoryRepositoryBase (ConcurrentDictionary-based)
├── EfCoreRepositoryBase (EF Core-based)
└── IUnitOfWork
├── SaveChanges() : FinT<IO, Unit>
└── BeginTransactionAsync() : IUnitOfWorkTransaction
Query Side (Read)
├── IQueryPort<TEntity, TDto>
│ ├── Search(spec, page, sort) : FinT<IO, PagedResult<TDto>>
│ ├── SearchByCursor(spec, cursor, sort) : FinT<IO, CursorPagedResult<TDto>>
│ └── Stream(spec, sort) : IAsyncEnumerable<TDto>
├── InMemoryQueryBase
└── DapperQueryBase
Use-Case Integration
├── ICommandRequest<TSuccess> : ICommand<FinResponse<TSuccess>>
├── ICommandUsecase<TCommand, TSuccess> : ICommandHandler
├── IQueryRequest<TSuccess> : IQuery<FinResponse<TSuccess>>
├── IQueryUsecase<TQuery, TSuccess> : IQueryHandler
└── ToFinResponse() : Fin<A> -> FinResponse<A>
Specification (Search Conditions)
├── Specification<T> (abstract class)
│ ├── IsSatisfiedBy(T) : bool
│ ├── And() / Or() / Not() composition
│ ├── & / | / ! operators
│ └── All (identity element, dynamic filter builder seed)
└── ExpressionSpecification<T> (EF Core/SQL support)
├── ToExpression() → Expression<Func<T, bool>>
└── sealed IsSatisfiedBy (compilation + caching)

  • .NET 10.0 SDK or later
  • VS Code + C# Dev Kit extension
  • Basic knowledge of C# syntax
  • Basic understanding of Entity and CRUD concepts

cqrs-repository/
├── Part0-Introduction/ # Part 0: Introduction
├── Part1-Domain-Entity-Foundations/ # Part 1: Domain Entity Foundations (4)
│ ├── 01-Entity-And-Identity/
│ ├── 02-Aggregate-Root/
│ ├── 03-Domain-Events/
│ └── 04-Entity-Interfaces/
├── Part2-Command-Repository/ # Part 2: Command Side Repository (4)
│ ├── 01-Repository-Interface/
│ ├── 02-InMemory-Repository/
│ ├── 03-EfCore-Repository/
│ └── 04-Unit-Of-Work/
├── Part3-Query-Patterns/ # Part 3: Query Side Read-Only (5)
│ ├── 01-QueryPort-Interface/
│ ├── 02-DTO-Separation/
│ ├── 03-Pagination-And-Sorting/
│ ├── 04-InMemory-Query-Adapter/
│ └── 05-Dapper-Query-Adapter/
├── Part4-CQRS-Usecase-Integration/ # Part 4: Use-Case Integration (5)
│ ├── 01-Command-Usecase/
│ ├── 02-Query-Usecase/
│ ├── 03-FinT-To-FinResponse/
│ ├── 04-Domain-Event-Flow/
│ └── 05-Transaction-Pipeline/
├── Part5-Domain-Examples/ # Part 5: Domain-Specific Practical Examples (4)
│ ├── 01-Ecommerce-Order-Management/
│ ├── 02-Customer-Management/
│ ├── 03-Inventory-Management/
│ └── 04-Catalog-Search/
├── Appendix/ # Appendix
└── README.md # This document

All example projects in every Part include unit tests. Tests follow the Unit Testing Guide conventions.

Terminal window
# Test the entire tutorial
dotnet test --solution Docs.Site/src/content/docs/tutorials/cqrs-repository/cqrs-repository.slnx
# Test an individual project
dotnet test --project Docs.Site/src/content/docs/tutorials/cqrs-repository/Part1-Domain-Entity-Foundations/01-Entity-And-Identity/EntityAndIdentity.Tests.Unit

Part 1: Domain Entity Foundations (4)

ChTest ProjectKey Test Content
1EntityAndIdentity.Tests.UnitEntity<TId>, IEntityId behavior verification
2AggregateRoot.Tests.UnitAggregateRoot invariant verification
3DomainEvents.Tests.UnitDomain event add/remove verification
4EntityInterfaces.Tests.UnitIAuditable, ISoftDeletable verification

Part 2: Command Side — Repository Pattern (4)

ChTest ProjectKey Test Content
1RepositoryInterface.Tests.UnitIRepository 8 CRUD operation verification
2InMemoryRepository.Tests.UnitInMemory implementation verification
3EfCoreRepository.Tests.UnitEF Core implementation verification
4UnitOfWork.Tests.UnitSaveChanges, transaction verification

Part 3: Query Side — Read-Only Patterns (5)

ChTest ProjectKey Test Content
1QueryPortInterface.Tests.UnitIQueryPort Search/Stream verification
2DtoSeparation.Tests.UnitCommand/Query DTO separation verification
3PaginationAndSorting.Tests.UnitPagination, sorting verification
4InMemoryQueryAdapter.Tests.UnitInMemory Query adapter verification
5DapperQueryAdapter.Tests.UnitDapper SQL generation verification

Part 4: CQRS Use-Case Integration (5)

ChTest ProjectKey Test Content
1CommandUsecase.Tests.UnitCommand handler verification
2QueryUsecase.Tests.UnitQuery handler verification
3FinTToFinResponse.Tests.UnitToFinResponse conversion verification
4DomainEventFlow.Tests.UnitEvent collection/publish verification
5TransactionPipeline.Tests.UnitTransaction pipeline verification

Part 5: Domain-Specific Practical Examples (4)

ChTest ProjectKey Test Content
1EcommerceOrderManagement.Tests.UnitOrder CQRS verification
2CustomerManagement.Tests.UnitCustomer management verification
3InventoryManagement.Tests.UnitInventory management verification
4CatalogSearch.Tests.UnitPagination comparison verification

Follows the T1_T2_T3 naming convention:

// Method_ExpectedResult_Scenario
[Fact]
public void Create_ReturnsAggregate_WhenValid()
{
// Arrange
var order = Order.Create(OrderId.New(), customerId);
// Act
var actual = await repository.Create(order).RunAsync();
// Assert
actual.IsSucc.ShouldBeTrue();
}

All example code for this tutorial can be found in the Functorium project:

  • Repository interfaces: Src/Functorium/Domains/Repositories/
  • Repository implementations: Src/Functorium.Adapters/Repositories/
  • Query adapters: Src/Functorium/Applications/Queries/
  • Use-case interfaces: Src/Functorium/Applications/Usecases/
  • Transaction pipeline: Src/Functorium/Adapters/Observabilities/Pipelines/
  • Tutorial projects: Docs.Site/src/content/docs/tutorials/cqrs-repository/

This tutorial is more effective when studied together with:


This tutorial was written based on real-world experience developing the CQRS framework in the Functorium project.