# CQRS & MediatR Patterns

> **Skill**: `cqrs-mediatr`  
> **Compatibility**: .NET 10+, MediatR 12+, FluentValidation

## Overview

CQRS (Command Query Responsibility Segregation) separates read and write operations for better scalability and maintainability. MediatR provides the infrastructure for implementing CQRS patterns with pipeline behaviors.

## When to Use

- Separating read/write operations
- Creating Commands and Queries
- Implementing MediatR handlers
- Adding cross-cutting concerns via Pipeline Behaviors
- Ensuring idempotency

## CQRS Overview

```
┌──────────────────────────────────────┐
│          API Controller              │
└────────┬─────────────────┬───────────┘
         │                 │
   ┌─────▼─────┐     ┌─────▼─────┐
   │ Commands  │     │  Queries  │
   │ (Write)   │     │  (Read)   │
   └─────┬─────┘     └─────┬─────┘
         │                 │
   ┌─────▼─────┐     ┌─────▼─────┐
   │  Handler  │     │  Handler  │
   └─────┬─────┘     └─────┬─────┘
         │                 │
   ┌─────▼─────┐     ┌─────▼─────┐
   │  EF Core  │     │   Dapper  │
   └───────────┘     └───────────┘
```

| Aspect | Command | Query |
|--------|---------|-------|
| **Purpose** | Modify state | Read data |
| **Returns** | Result/void | Data/DTO |
| **ORM** | EF Core | Dapper/Raw SQL |

## Quick Start

### 1. Define Command

```csharp
public record CreateOrderCommand(
    string UserId,
    Address ShippingAddress,
    List<OrderItemDto> Items) : IRequest<OrderResult>;

public record OrderResult(Guid OrderId);
```

### 2. Create Handler

```csharp
public class CreateOrderCommandHandler 
    : IRequestHandler<CreateOrderCommand, OrderResult>
{
    private readonly IOrderRepository _repository;
    
    public async Task<OrderResult> Handle(
        CreateOrderCommand request,
        CancellationToken ct)
    {
        var order = new Order(request.UserId, request.ShippingAddress);
        
        foreach (var item in request.Items)
            order.AddItem(item.ProductId, item.Quantity, item.UnitPrice);
        
        await _repository.AddAsync(order, ct);
        await _repository.UnitOfWork.SaveChangesAsync(ct);
        
        return new OrderResult(order.Id);
    }
}
```

### 3. Query with Dapper

```csharp
public record GetUserOrdersQuery(
    string UserId,
    int Skip = 0,
    int Take = 20) : IRequest<PagedResult<OrderSummaryDto>>;

public class GetUserOrdersQueryHandler 
    : IRequestHandler<GetUserOrdersQuery, PagedResult<OrderSummaryDto>>
{
    private readonly IDbConnection _connection;
    
    public async Task<PagedResult<OrderSummaryDto>> Handle(
        GetUserOrdersQuery request,
        CancellationToken ct)
    {
        const string sql = @"
            SELECT Id, Status, TotalAmount, CreatedAt
            FROM Orders
            WHERE UserId = @UserId
            ORDER BY CreatedAt DESC
            OFFSET @Skip ROWS FETCH NEXT @Take ROWS ONLY";
        
        var orders = await _connection.QueryAsync<OrderSummaryDto>(
            sql, new { request.UserId, request.Skip, request.Take });
        
       var total = await _connection.ExecuteScalarAsync<int>(
            "SELECT COUNT(*) FROM Orders WHERE UserId = @UserId",
            new { request.UserId });
        
        return new PagedResult<OrderSummaryDto>(orders.ToList(), total);
    }
}
```

### 4. Use in Controller

```csharp
[ApiController]
[Route("api/v{version:apiVersion}/orders")]
public class OrdersController : ControllerBase
{
    private readonly IMediator _mediator;
    
    [HttpPost]
    public async Task<ActionResult<ApiResponse<OrderResult>>> CreateOrder(
        CreateOrderRequest request,
        CancellationToken ct)
    {
        var command = new CreateOrderCommand(
            GetUserId(), request.ShippingAddress, request.Items);
        
        var result = await _mediator.Send(command, ct);
        
        return CreatedAtAction(nameof(GetOrder),
            new { orderId = result.OrderId },
            ApiResponse<OrderResult>.Ok(result));
    }
}
```

## Pipeline Behaviors

### Validation Behavior

```csharp
public class ValidationBehavior<TRequest, TResponse> 
    : IPipelineBehavior<TRequest, TResponse>
    where TRequest : IRequest<TResponse>
{
    private readonly IEnumerable<IValidator<TRequest>> _validators;
    
    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken ct)
    {
        if (!_validators.Any())
            return await next();
        
        var context = new ValidationContext<TRequest>(request);
        var failures = (await Task.WhenAll(
            _validators.Select(v => v.ValidateAsync(context, ct))))
            .SelectMany(r => r.Errors)
            .Where(f => f != null)
            .ToList();
        
        if (failures.Count != 0)
            throw new ValidationException(failures);
        
        return await next();
    }
}
```

### Logging Behavior

```csharp
public class LoggingBehavior<TRequest, TResponse> 
    : IPipelineBehavior<TRequest, TResponse>
{
    private readonly ILogger _logger;
    
    public async Task<TResponse> Handle(
        TRequest request,
        RequestHandlerDelegate<TResponse> next,
        CancellationToken ct)
    {
        var requestName = typeof(TRequest).Name;
        _logger.LogInformation("Handling {RequestName}", requestName);
        
        var stopwatch = Stopwatch.StartNew();
        var response = await next();
        stopwatch.Stop();
        
        _logger.LogInformation(
            "Handled {RequestName} in {ElapsedMs}ms",
            requestName, stopwatch.ElapsedMilliseconds);
        
        return response;
    }
}
```

### Register Behaviors

```csharp
builder.Services.AddMediatR(cfg =>
{
    cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
    cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
});
```

## Common Mistakes

### ❌ Using EF Core for Simple Reads

```csharp
// BAD: Heavy EF Core for queries
return await _context.Orders
    .Include(o => o.OrderItems)
    .Where(o => o.UserId == userId)
    .ToListAsync();
```

### ✅ Use Dapper for Optimized Reads

```csharp
// GOOD: Lightweight Dapper query
return await _connection.QueryAsync<OrderDto>(
    "SELECT Id, Status, TotalAmount FROM Orders WHERE UserId = @UserId",
    new { userId });
```

## Related Skills

- [API Design](./api-design.md) - Controller patterns
- [Repository Pattern](./repository-pattern.md) - Data access
- [Error Handling](./error-handling-patterns.md) - Validation errors
- [Testing Patterns](././testing-patterns.md) - Handler testing

## Additional Resources

- **Full Skill**: [`.agent/skills/cqrs-mediatr/SKILL.md`](../../.agent/skills/cqrs-mediatr/SKILL.md)
- **Examples**: [`.agent/skills/cqrs-mediatr/references/REFERENCE.md`](../../.agent/skills/cqrs-mediatr/references/REFERENCE.md)

---

**Vietnamese Version**: [Mẫu CQRS & MediatR](../../vi/skills/cqrs-mediatr.md)
