Introduction

Command Query Responsibility Segregation (CQRS) is a pattern that separates the responsibility of reading data (queries) from the responsibility of modifying data (commands). This separation allows for more scalable, maintainable, and performance-optimized applications. In this article, we will explore an advanced implementation of CQRS using .NET Core and the MediatR library. We’ll walk through the architecture, its benefits, and practical code examples in C#.

1. Understanding CQRS and Its Benefits

What is CQRS?

CQRS is a design pattern that divides an application's operations into two distinct types.

Key Benefits of CQRS

2. Setting Up Your .NET Core Project with MediatR

Creating a New .NET Core Project

Start by creating a new .NET Core project.

dotnet new webapi -n AdvancedCQRS
cd AdvancedCQRS

Install the MediatR package and its extensions to handle CQRS.

dotnet add package MediatR.Extensions.Microsoft.DependencyInjection

3. Implementing the CQRS Pattern with MediatR

Defining Commands and Command Handlers

Let’s define a command for creating a new product in an e-commerce system.

public class CreateProductCommand : IRequest<int>
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }
}

Next, we create a handler for this command.

public class CreateProductCommandHandler : IRequestHandler<CreateProductCommand, int>
{
    private readonly ApplicationDbContext _context;

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

    public async Task<int> Handle(CreateProductCommand request, CancellationToken cancellationToken)
    {
        var product = new Product
        {
            Name = request.Name,
            Price = request.Price,
            Category = request.Category
        };

        _context.Products.Add(product);
        await _context.SaveChangesAsync();

        return product.Id;
    }
}

Defining Queries and Query Handlers

Now, let’s define a query to retrieve product details.

public class GetProductByIdQuery : IRequest<ProductDto>
{
    public int Id { get; set; }
}

The handler for this query.

public class GetProductByIdQueryHandler : IRequestHandler<GetProductByIdQuery, ProductDto>
{
    private readonly ApplicationDbContext _context;

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

    public async Task<ProductDto> Handle(GetProductByIdQuery request, CancellationToken cancellationToken)
    {
        var product = await _context.Products.FindAsync(request.Id);

        if (product == null) return null;

        return new ProductDto
        {
            Id = product.Id,
            Name = product.Name,
            Price = product.Price,
            Category = product.Category
        };
    }
}

4. Configuring the CQRS Handlers in .NET Core

To integrate MediatR and configure your CQRS handlers, you need to update the `Startup.cs` file.

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();

        // Register MediatR
        services.AddMediatR(typeof(Startup));

        // Register your DbContext
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        // Register other services
        services.AddTransient<IRequestHandler<CreateProductCommand, int>, CreateProductCommandHandler>();
        services.AddTransient<IRequestHandler<GetProductByIdQuery, ProductDto>, GetProductByIdQueryHandler>();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

5. Expanding Your CQRS Implementation

6. Best Practices for Implementing CQRS

Conclusion

Implementing advanced CQRS with .NET Core and MediatR allows you to build scalable, maintainable, and performant applications. By separating commands and queries, you can optimize each aspect of your application independently, making it easier to handle complex business requirements. The code examples provided in this article should give you a solid foundation for implementing CQRS in your own .NET Core projects. As you continue to develop your architecture, consider incorporating event sourcing, pipeline behaviors, and other advanced techniques to further enhance your system.