CQRS (Command Query Responsibility Segregation) Pattern in C#: Definition, Use Cases, Pros, Cons, and Examples

CQRS (Command Query Responsibility Segregation) Pattern in C#: Definition, Use Cases, Pros, Cons, and Examples

CQRS (Command Query Responsibility Segregation) is a software design pattern in C# that separates operations that modify data (Commands) from operations that read data (Queries) into different models or handlers.

CQRS divides an application's responsibilities into two independent parts: Commands and Queries. Commands are responsible for changing the system state such as creating, updating, or deleting data, while Queries only retrieve data without modifying anything. In C#, CQRS is commonly implemented using separate command handlers and query handlers, often with libraries like MediatR in ASP.NET Core applications. This separation improves scalability, maintainability, and flexibility because read and write operations can evolve independently. CQRS is especially useful in complex enterprise systems where business logic and data retrieval requirements differ significantly.

Why We Use CQRS in C#?

We use CQRS in C# because it helps solve problems related to complexity, scalability, and maintainability in large applications.

Main reasons include:

• Separates read logic from write logic
• Makes business logic easier to manage
• Improves scalability for high-read systems
• Enables independent optimization of reads and writes
• Reduces complexity in large enterprise applications
• Works well with Domain-Driven Design (DDD)
• Supports event-driven and microservice architectures
• Improves testability by isolating responsibilities

When Should We Use CQRS?

CQRS should be used when applications become too complex for traditional CRUD architecture.

Typical programming problems where CQRS is beneficial:

• Complex business rules and workflows
• Systems with heavy read/write imbalance
• Large enterprise applications
• Microservices architectures
• Event-driven systems
• Financial or banking systems
• E-commerce platforms
• Real-time analytics systems
• Applications requiring high scalability
• Systems with multiple data representations

Avoid CQRS for:

• Small applications
• Simple CRUD systems
• Projects with minimal business logic
• Applications where complexity outweighs benefits

CQRS Core Structure in C#

Basic CQRS components:

• Command
• Command Handler
• Query
• Query Handler
• DTO/ViewModel
• Mediator (optional)

Example flow:

Client -> Command -> Command Handler -> Database
Client -> Query -> Query Handler -> Database

CQRS Pattern Examples in C#

Example 1: Create Product (Command Example)

// Command
public class CreateProductCommand
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

// Command Handler
public class CreateProductCommandHandler
{
    private readonly AppDbContext _context;

    public CreateProductCommandHandler(AppDbContext context)
    {
        _context = context;
    }

    public async Task Handle(CreateProductCommand command)
    {
        var product = new Product
        {
            Name = command.Name,
            Price = command.Price
        };

        _context.Products.Add(product);

        await _context.SaveChangesAsync();
    }
}

// Usage
var command = new CreateProductCommand
{
    Name = "Laptop",
    Price = 2500
};

await handler.Handle(command);

Example 2: Get Product By Id (Query Example)

// Query
public class GetProductByIdQuery
{
    public int Id { get; set; }
}

// Query Handler
public class GetProductByIdQueryHandler
{
    private readonly AppDbContext _context;

    public GetProductByIdQueryHandler(AppDbContext context)
    {
        _context = context;
    }

    public async Task Handle(GetProductByIdQuery query)
    {
        return await _context.Products
            .Where(p => p.Id == query.Id)
            .Select(p => new ProductDto
            {
                Id = p.Id,
                Name = p.Name,
                Price = p.Price
            })
            .FirstOrDefaultAsync();
    }
}

// DTO
public class ProductDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Example 3: CQRS with MediatR in ASP.NET Core

Install Package
dotnet add package MediatR

// Command
using MediatR;

public class CreateOrderCommand : IRequest
{
    public string CustomerName { get; set; }
    public decimal TotalAmount { get; set; }
}

// Handler
using MediatR;

public class CreateOrderCommandHandler 
    : IRequestHandler<CreateOrderCommand, int>
{
    private readonly AppDbContext _context;

    public CreateOrderCommandHandler(AppDbContext context)
    {
        _context = context;
    }

    public async Task Handle(
        CreateOrderCommand request,
        CancellationToken cancellationToken)
    {
        var order = new Order
        {
            CustomerName = request.CustomerName,
            TotalAmount = request.TotalAmount
        };

        _context.Orders.Add(order);

        await _context.SaveChangesAsync(cancellationToken);

        return order.Id;
    }
}

// Controller Usage
[ApiController]
[Route("api/orders")]
public class OrdersController : ControllerBase
{
    private readonly IMediator _mediator;

    public OrdersController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task Create(CreateOrderCommand command)
    {
        int orderId = await _mediator.Send(command);

        return Ok(orderId);
    }
}

Advantages of CQRS in C#

Advantage Description
Separation of Concerns Read and write operations are isolated from each other.
Better Scalability Read and write sides can scale independently.
Improved Maintainability Business logic becomes easier to organize and maintain.
Optimized Queries Query models can be tailored specifically for reading data.
Better Testability Handlers can be tested independently.
Supports DDD Works naturally with Domain-Driven Design principles.
Flexible Architecture Allows independent evolution of read/write models.

Disadvantages (Weak Points) of CQRS in C#

Disadvantage Description
Increased Complexity Architecture becomes more complicated compared to CRUD systems.
More Code Requires additional classes such as commands, queries, and handlers.
Steeper Learning Curve Developers need to understand multiple architectural concepts.
Data Synchronization Issues Separate read/write models may cause eventual consistency problems.
Harder Debugging Tracing execution flow can become more difficult.
Overkill for Small Apps Simple applications usually do not benefit from CQRS.

CQRS vs Similar Patterns and Alternatives

Feature CQRS Traditional CRUD Layered Architecture Event Sourcing MVC Pattern
Main Idea Separate reads and writes Single model for all operations Separate application layers Store all state changes as events Separate UI and business logic
Complexity High Low Medium Very High Medium
Scalability Excellent Limited Good Excellent Good
Best For Complex enterprise systems Simple applications Business applications Audit-heavy systems Web applications
Read/Write Separation Yes No Partial Usually No
Learning Curve High Low Medium Very High Low
Performance Optimization Excellent Limited Moderate Excellent Moderate
Data Consistency May use eventual consistency Strong consistency Strong consistency Eventual consistency common Strong consistency

Summary

CQRS is a powerful architectural pattern in C# that separates write operations from read operations to improve scalability, maintainability, and flexibility. It is highly effective for large and complex enterprise applications but introduces additional architectural complexity. CQRS works especially well with ASP.NET Core, MediatR, Domain-Driven Design, microservices, and event-driven systems. However, for small CRUD applications, the added complexity may not justify its usage.