CQRS全称为Command Query Responsibility Segregation,即命令查询职责分离,是一种将系统的写操作(命令)和读操作(查询)分开处理的架构模式。在传统的CRUD架构中,读写操作通常共用同一套模型和逻辑,当系统复杂度提升时,容易出现逻辑耦合、性能瓶颈等问题。而CQRS模式通过分离读写侧,让两者可以独立优化,比如写侧可以专注于数据一致性和业务逻辑,读侧可以针对查询场景做缓存、数据冗余等优化。MediatR是一个基于中介者模式的轻量级库,能够帮助我们优雅地实现CQRS中的命令和查询的派发与处理,避免组件之间的直接依赖。

CQRS模式核心概念
CQRS模式主要包含两个核心部分:
- 命令(Command):代表系统的写操作,比如创建用户、更新订单状态等,命令通常只返回执行结果,不返回业务数据。
- 查询(Query):代表系统的读操作,比如获取用户列表、查询订单详情等,查询只返回数据,不会修改系统状态。
两者完全独立,拥有各自的模型、处理逻辑甚至数据存储,这也是CQRS和传统架构最大的区别。
MediatR库基础介绍
MediatR的核心作用是作为中介者,接收请求(命令或查询)并将其派发给对应的处理器,发送方不需要知道处理方的存在,只需要依赖MediatR即可。它主要支持两种请求类型:
IRequest<T>:对应查询请求,T是返回结果的类型。IRequest:对应命令请求,没有返回结果或者返回简单的执行状态。
对应的处理器分别是IRequestHandler<TRequest, TResponse>和IRequestHandler<TRequest>。
环境准备与依赖引入
首先创建一个.NET 6及以上的控制台项目或者Web API项目,然后通过NuGet安装MediatR依赖:
<!-- 项目文件中的PackageReference --> <PackageReference Include="MediatR" Version="12.2.0" /> <PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.1.0" />
如果是控制台项目,需要在Program.cs中注册MediatR服务:
using MediatR; using Microsoft.Extensions.DependencyInjection; var services = new ServiceCollection(); // 注册MediatR,会自动扫描程序集中的请求处理器 services.AddMediatR(typeof(Program).Assembly); var provider = services.BuildServiceProvider(); var mediator = provider.GetRequiredService<IMediator>();
实现查询侧功能
首先定义一个查询请求,比如获取用户详情的查询:
using MediatR;
// 查询请求,返回用户详情
public class GetUserByIdQuery : IRequest<UserDto>
{
public int UserId { get; set; }
}
// 用户数据传输对象
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
然后实现对应的查询处理器:
using MediatR;
public class GetUserByIdQueryHandler : IRequestHandler<GetUserByIdQuery, UserDto>
{
// 模拟用户数据存储
private readonly List<UserDto> _users = new()
{
new UserDto { Id = 1, Name = "张三", Email = "zhangsan@ipipp.com" },
new UserDto { Id = 2, Name = "李四", Email = "lisi@ipipp.com" }
};
public Task<UserDto> Handle(GetUserByIdQuery request, CancellationToken cancellationToken)
{
var user = _users.FirstOrDefault(u => u.Id == request.UserId);
return Task.FromResult(user);
}
}
调用查询的代码如下:
// 发送查询请求
var query = new GetUserByIdQuery { UserId = 1 };
var user = await mediator.Send(query);
Console.WriteLine($"用户ID:{user?.Id},姓名:{user?.Name}");
实现命令侧功能
接下来定义创建用户的命令:
using MediatR;
// 命令请求,无返回结果
public class CreateUserCommand : IRequest
{
public string Name { get; set; }
public string Email { get; set; }
}
实现命令处理器:
using MediatR;
public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand>
{
// 模拟用户存储
private readonly List<UserDto> _users = new();
public Task Handle(CreateUserCommand request, CancellationToken cancellationToken)
{
var newUser = new UserDto
{
Id = _users.Count + 1,
Name = request.Name,
Email = request.Email
};
_users.Add(newUser);
Console.WriteLine($"用户{request.Name}创建成功,ID:{newUser.Id}");
return Task.CompletedTask;
}
}
调用命令的代码如下:
// 发送命令请求
var command = new CreateUserCommand { Name = "王五", Email = "wangwu@ipipp.com" };
await mediator.Send(command);
进阶实践:命令与查询的验证
实际开发中,我们通常需要在命令或查询执行前做参数验证,MediatR支持通过管道行为(Pipeline Behavior)实现通用逻辑。比如实现一个验证行为:
using MediatR;
using System.ComponentModel.DataAnnotations;
// 通用验证行为
public class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
// 使用DataAnnotations做参数验证
var validationContext = new ValidationContext(request);
var validationResults = new List<ValidationResult>();
if (!Validator.TryValidateObject(request, validationContext, validationResults, true))
{
throw new ArgumentException(string.Join(",", validationResults.Select(r => r.ErrorMessage)));
}
return await next();
}
}
然后在命令和查询的属性上添加验证特性:
using System.ComponentModel.DataAnnotations;
using MediatR;
public class CreateUserCommand : IRequest
{
[Required(ErrorMessage = "用户名不能为空")]
public string Name { get; set; }
[Required(ErrorMessage = "邮箱不能为空")]
[EmailAddress(ErrorMessage = "邮箱格式不正确")]
public string Email { get; set; }
}
最后注册验证行为:
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
注意事项
- 不是所有项目都适合使用CQRS,简单的CRUD项目使用CQRS会增加不必要的复杂度,建议在系统复杂度较高、读写场景差异大的场景下使用。
- MediatR是中介者模式的实现,不要过度使用,避免所有逻辑都通过MediatR派发,导致请求链路过长,不利于调试。
- 如果读写侧使用不同的数据库,需要考虑数据同步的问题,通常可以通过事件驱动的方式实现写侧数据变更后同步到读侧存储。