Command Query Responsibility Segregation (CQRS) is a powerful pattern that separates read and write operations in an application, promoting scalability, maintainability, and performance. By splitting commands (which change state) from queries (which read state), CQRS allows each to be optimized independently.
In this article, we’ll explore the CQRS pattern and provide a step-by-step guide to implementing it in a .NET Core application.
Table of Contents
- Introduction to CQRS
- Benefits of Using CQRS
- Setting Up the Development Environment
- Creating the Solution Structure
- Implementing the Command Side
- Implementing the Query Side
- Integrating with a Database
- Using MediatR for CQRS in .NET Core
- Testing the Application
- Conclusion
1. Introduction to CQRS
CQRS stands for Command Query Responsibility Segregation. It splits the application into two main parts:
- Commands: Operations that change the state of the application (create, update, delete).
- Queries: Operations that read data without changing the state.

2. Benefits of Using CQRS
- Scalability: Each part can be scaled independently.
- Performance: Optimized read and write models improve performance.
- Maintainability: Clear separation of concerns makes the codebase easier to maintain.
- Flexibility: Allows for different data storage strategies for reads and writes.
3. Setting Up the Development Environment
Ensure you have the following tools installed:
- .NET Core SDK
- Visual Studio or Visual Studio Code
4. Creating the Solution Structure
We will create a solution with three projects: API, Command, and Query.
- Create the Solution and Projects: Open your terminal or command prompt and run:
mkdir CQRSApp
cd CQRSApp
dotnet new sln
dotnet new webapi -o Api
dotnet new classlib -o Command
dotnet new classlib -o Query
dotnet sln add Api/Api.csproj
dotnet sln add Command/Command.csproj
dotnet sln add Query/Query.csproj
- Add Project References: Add references to ensure the projects can communicate:
dotnet add Api/Api.csproj reference Command/Command.csproj
dotnet add Api/Api.csproj reference Query/Query.csproj
5. Implementing the Command Side
The Command side handles operations that change the state of the application.
- Define Command Models: In the
Command
project, create aModels
folder and add aProductCommandModel.cs
file:
namespace Command.Models
{
public class ProductCommandModel
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
}
2. Create Command Handlers: In the Command
project, create a Handlers
folder and add a ProductCommandHandler.cs
file:
using System.Threading.Tasks;
using Command.Models;
namespace Command.Handlers
{
public class ProductCommandHandler
{
public async Task HandleCreateAsync(ProductCommandModel model)
{
// Add logic to handle create operation
}
public async Task HandleUpdateAsync(ProductCommandModel model)
{
// Add logic to handle update operation
}
public async Task HandleDeleteAsync(int id)
{
// Add logic to handle delete operation
}
}
}
6. Implementing the Query Side
The Query side handles operations that read data from the application.
- Define Query Models: In the
Query
project, create aModels
folder and add aProductQueryModel.cs
file:
namespace Query.Models
{
public class ProductQueryModel
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
}
2. Create Query Handlers: In the Query
project, create a Handlers
folder and add a ProductQueryHandler.cs
file:
using System.Collections.Generic;
using System.Threading.Tasks;
using Query.Models;
namespace Query.Handlers
{
public class ProductQueryHandler
{
public async Task<IEnumerable<ProductQueryModel>> HandleGetAllAsync()
{
// Add logic to handle get all operation
}
public async Task<ProductQueryModel> HandleGetByIdAsync(int id)
{
// Add logic to handle get by id operation
}
}
}
7. Integrating with a Database
Integrate the Command and Query sides with a database to persist and retrieve data.
- Add Entity Framework Core Packages: In both
Command
andQuery
projects, add EF Core packages:
dotnet add Command/Command.csproj package Microsoft.EntityFrameworkCore
dotnet add Command/Command.csproj package Microsoft.EntityFrameworkCore.SqlServer
dotnet add Query/Query.csproj package Microsoft.EntityFrameworkCore
dotnet add Query/Query.csproj package Microsoft.EntityFrameworkCore.SqlServer
2. Create DbContext: In the Command
project, add a DataContext.cs
file:
using Microsoft.EntityFrameworkCore;
namespace Command
{
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
public DbSet<Models.ProductCommandModel> Products { get; set; }
}
}
In the Query
project, add a DataContext.cs
file:
using Microsoft.EntityFrameworkCore;
namespace Query
{
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
public DbSet<Models.ProductQueryModel> Products { get; set; }
}
}
8. Using MediatR for CQRS in .NET Core
MediatR is a popular library for implementing CQRS and Mediator patterns in .NET Core.
- Add MediatR Package: In the
Api
project, add the MediatR package:
dotnet add Api/Api.csproj package MediatR.Extensions.Microsoft.DependencyInjection
2. Register MediatR in Startup.cs: Modify Startup.cs
in the Api
project:
using Command.Handlers;
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Query.Handlers;
namespace Api
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<Command.DataContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddDbContext<Query.DataContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMediatR(typeof(ProductCommandHandler).Assembly);
services.AddMediatR(typeof(ProductQueryHandler).Assembly);
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
9. Testing the Application
Test the entire application to ensure the CQRS pattern is correctly implemented.
- Run Migrations and Update Database: Apply migrations to set up the database schema:
dotnet ef migrations add InitialCreate -p Command -s Api
dotnet ef database update -p Command -s Api
2. Test API Endpoints: Run the Api
project and use tools like Postman or Swagger to test the endpoints for creating, updating, deleting, and querying products.
10. Conclusion
Implementing the CQRS pattern in .NET Core separates read and write operations, improving scalability, maintainability, and performance. By following this guide, you have created a .NET Core application with a clear separation of concerns, using MediatR to manage commands and queries effectively.