Repository Pattern vs CQRS

Understanding the difference between two commonly confused architecture patterns in modern .NET systems

Posted by Hüseyin Sekmenoğlu on December 24, 2025 Architecture & Patterns Backend Development

Introduction

The Repository Pattern abstracts persistence. CQRS separates read and write responsibilities.

Understanding this distinction is important because misuse of either pattern often leads to unnecessary complexity. Many systems end up with over engineered repositories or premature CQRS implementations.


Repository Pattern in practice

The Repository Pattern abstracts data access behind an interface that behaves like a collection of aggregates.

The goal is not to hide the database completely. The goal is to keep persistence logic outside the domain and application services.

Typical interface:

public interface IOrderRepository
{
    Order GetById(Guid id);
    IEnumerable<Order> GetByCustomerId(Guid customerId);
    void Add(Order order);
    void Update(Order order);
    void Remove(Order order);
}

In a typical layered architecture the flow looks like this:

UI
 ↓
Application Service
 ↓
Repository
 ↓
ORM / Data Access
 ↓
Database

Application services work with aggregates while the repository handles persistence.

Example usage inside an application service:

public class OrderService
{
    private readonly IOrderRepository repository;

    public OrderService(IOrderRepository repository)
    {
        this.repository = repository;
    }

    public void CreateOrder(Guid customerId)
    {
        var order = new Order(customerId);

        repository.Add(order);
    }
}

This keeps the service focused on business behavior rather than persistence mechanics.


Repository pattern limitations

Repositories work well when the domain model aligns with the data model. Problems begin when read scenarios diverge from aggregate boundaries.

Typical issues include:

Query inflation

Repositories accumulate query specific methods.

GetOrdersByDateRange()
GetOrdersByStatus()
GetOrdersForDashboard()
GetOrdersWithCustomerAndPayments()

Eventually repositories become read APIs.

Inefficient read models

Returning domain entities for read operations forces unnecessary object graphs.

Example:

Order -> OrderLines -> Product -> Supplier

Even when the UI only needs:

OrderId
CustomerName
Total
Status

Generic repository anti pattern

Many projects introduce a generic repository abstraction.

IGenericRepository<T>

This usually becomes a thin wrapper around the ORM and adds little value.


CQRS fundamentals

CQRS starts from a simple observation.

Reading data and modifying data are fundamentally different operations.

Commands change state. Queries return data.

CQRS formalizes this separation by introducing different models for each concern.

Command side

Commands represent business intent.

Example command:

public class CreateOrderCommand
{
    public Guid CustomerId { get; set; }
}

Handled by a command handler:

public class CreateOrderHandler
{
    private readonly IOrderRepository repository;

    public CreateOrderHandler(IOrderRepository repository)
    {
        this.repository = repository;
    }

    public void Handle(CreateOrderCommand command)
    {
        var order = new Order(command.CustomerId);

        repository.Add(order);
    }
}

Commands usually operate on domain aggregates.


Query side

Queries retrieve data optimized for the consumer.

Example query:

public class GetOrderSummaryQuery
{
    public Guid OrderId { get; set; }
}

Query handler:

public class GetOrderSummaryHandler
{
    private readonly IDbConnection connection;

    public GetOrderSummaryHandler(IDbConnection connection)
    {
        this.connection = connection;
    }

    public OrderSummaryDto Handle(GetOrderSummaryQuery query)
    {
        const string sql = @"
            SELECT
                o.Id,
                c.Name AS CustomerName,
                o.Total,
                o.Status
            FROM Orders o
            JOIN Customers c ON c.Id = o.CustomerId
            WHERE o.Id = @Id";

        return connection.QuerySingle<OrderSummaryDto>(sql, new { Id = query.OrderId });
    }
}

The query side returns DTOs designed specifically for the UI or API.


Architectural comparison

The important difference between the patterns is scope.

Aspect

Repository Pattern

CQRS

Primary concern

Persistence abstraction

Responsibility separation

Architecture level

Data access

Application architecture

Model structure

Single domain model

Separate read and write models

Complexity

Low

Medium to high

Repositories are an implementation detail. CQRS is an architectural style.


How CQRS and repositories work together

In real systems the most common arrangement is:

Command side uses repositories.

Query side bypasses them.

Command flow:

UI
 ↓
Command
 ↓
Command Handler
 ↓
Domain Aggregate
 ↓
Repository
 ↓
Database

Query flow:

UI
 ↓
Query
 ↓
Query Handler
 ↓
SQL / Dapper
 ↓
Database

Repositories remain responsible for aggregate persistence while query handlers use optimized SQL or projections.

This avoids turning repositories into read APIs.


When repositories are enough

Many applications do not benefit from CQRS.

Repositories alone are usually sufficient when:

  • The application is CRUD focused

  • Domain logic is moderate

  • Read requirements align with aggregates

  • System scale is predictable

Typical architecture:

Controllers
 ↓
Application Services
 ↓
Repositories
 ↓
ORM
 ↓
Database

This architecture is simple and easy to maintain.


When CQRS becomes useful

CQRS becomes valuable when read complexity diverges from write complexity.

Common scenarios include:

High read throughput

Systems with large numbers of read requests.

Complex dashboards or reporting

Queries become difficult to express through aggregates.

Different read models

UI models differ significantly from domain models.

Example:

Write model:

Order
OrderLine
Payment
Shipment

Read model:

OrderSummary
CustomerOrders
DailySalesDashboard

These projections often require denormalized structures.


A pragmatic adoption strategy

CQRS should rarely be introduced at the beginning of a project.

A typical evolution path looks like this:

Layered architecture
↓
Repositories and domain services
↓
Introduce query handlers for complex reads
↓
Adopt full CQRS separation

This keeps complexity proportional to system needs.


Final thoughts

The Repository Pattern and CQRS are frequently discussed together which makes them appear interchangeable. In reality they address different concerns.

Repositories manage how aggregates are persisted.

CQRS manages how the application handles reads and writes.

In most modern .NET systems the practical architecture looks like this:

Command Side
Command → Handler → Domain → Repository → Database

Query Side
Query → Handler → SQL/Dapper → Database

This approach preserves the benefits of domain modeling on the write side while allowing highly optimized reads.

For many systems repositories alone are sufficient. CQRS becomes valuable when the complexity of read models starts to diverge from the domain model.

Recognizing that moment is an architectural judgment call that typically comes with experience.