CQRS (Command Query Responsibility Segregation) is a software architecture pattern that separates the responsibilities of reading data (queries) and writing data (commands) into distinct models. This improves scalability, performance, and maintainability, especially in complex domains.
What is CQRS?
- Commands: Modify state (create, update, delete). They return void or a simple result (e.g., success/failure).
- Queries: Read data. They do not modify state and return data (DTOs).
- Segregation: By splitting these concerns, each side can be optimized independently — e.g., read model could use denormalized views for performance.

Why Use CQRS?
- Clear separation of concerns (read vs write logic)
- Easier to scale reads and writes independently
- Enables complex business logic on writes without polluting queries
- Simplifies event sourcing and audit trails
How to Implement CQRS in .NET
1. Define Command and Query Models
// Command
public class CreateStudentCommand
{
public string Name { get; set; }
public string Email { get; set; }
}
// Query
public class GetStudentByIdQuery
{
public long Id { get; set; }
}
2. Create Handlers for Each
public class CreateStudentCommandHandler
{
private readonly AppDbContext _context;
public CreateStudentCommandHandler(AppDbContext context)
{
_context = context;
}
public async Task<bool> Handle(CreateStudentCommand command)
{
var student = new Student
{
Name = command.Name,
Email = command.Email
};
_context.Students.Add(student);
await _context.SaveChangesAsync();
return true;
}
}
public class GetStudentByIdQueryHandler
{
private readonly AppDbContext _context;
public GetStudentByIdQueryHandler(AppDbContext context)
{
_context = context;
}
public async Task<StudentDto> Handle(GetStudentByIdQuery query)
{
return await _context.Students
.Where(s => s.Id == query.Id)
.Select(s => new StudentDto
{
Id = s.Id,
Name = s.Name,
Email = s.Email
})
.FirstOrDefaultAsync();
}
}
3. Optional: Use Mediator Pattern (e.g., with MediatR)
dotnet add package MediatR.Extensions.Microsoft.DependencyInjection
Define command and handler:
public record CreateStudentCommand(string Name, string Email) : IRequest<bool>;
public class CreateStudentCommandHandler : IRequestHandler<CreateStudentCommand, bool>
{
private readonly AppDbContext _context;
public CreateStudentCommandHandler(AppDbContext context) => _context = context;
public async Task<bool> Handle(CreateStudentCommand request, CancellationToken cancellationToken)
{
_context.Students.Add(new Student { Name = request.Name, Email = request.Email });
await _context.SaveChangesAsync();
return true;
}
}
CQRS + Event Sourcing (Advanced Use Case)
For even more decoupling, commands can raise events which are stored and later replayed to rebuild the system state. Libraries like EventStoreDB or NEventStore can be used.

When to Use CQRS
Use CQRS When... | Avoid CQRS When... |
---|---|
Complex domains with many business rules | Simple CRUD apps |
Need to scale reads/writes independently | Small teams or tight deadlines |
Heavy read operations with different needs | One model suffices for read & write |
Leave Comment