MediatR需在Program.cs中用AddMediatR注册并指定含处理器的程序集,确保IMediator以Scoped生命周期注入;命令应为纯意图载体,查询与命令必须严格分离,Handler须用async Task<T>签名并正确await异步操作。

MediatR 在 C# 里怎么注册和用起来
不配好依赖注入容器,IMediator 根本拿不到实例,后面全白搭。.NET 6+ 推荐在 Program.cs 里用 AddMediatR 扩展方法,它会自动扫描程序集中所有 IRequestHandler、INotificationHandler 实现类。
常见错误现象:InvalidOperationException: No service for type 'MediatR.IMediator' —— 就是没注册,或注册了但没加 AsScoped()(因为 MediatR 默认要求作用域生命周期)。
- 必须确保调用
AddMediatR时传入包含处理器的程序集,比如AddMediatR(typeof(CreateUserCommandHandler).Assembly) - 不要手动 new
Mediator实例,它内部依赖IServiceProvider解析处理器 - 如果你用的是 Minimal Hosting 模型,别漏掉
builder.Services.AddMediatR(...)这一行
CQRS 中命令(Command)怎么写才不踩坑
命令不是 DTO,也不是 Entity,它是“意图”的载体。一个 IRequest<TResponse> 类只该表达「我要做什么」,不该带业务逻辑、验证或数据库操作。
使用场景:用户点击“提交订单”,你发一个 CreateOrderCommand,而不是直接调用仓储 Save。
- 命令类建议继承
IRequest<Unit>(无返回)或IRequest<Guid>(返回 ID),别用void—— MediatR 不支持void返回类型 - 别在命令类里放
[Required]等数据注解,验证应交给专门的IRequestPreProcessor或独立的 FluentValidation 规则类 - 避免把 EF Core 的
DbContext或IRepository塞进命令类里 —— 它只负责传参,不是执行者
public record CreateOrderCommand(
Guid UserId,
List<OrderItemDto> Items) : IRequest<Guid>;
查询(Query)和命令混用时为啥总出问题
Query 和 Command 在语义和基础设施上应该隔离。共用同一个 Handler 类、或者让一个 Handler 既改库又查库,会破坏 CQRS 最基本的分离前提 —— 读写路径不同,缓存策略、事务边界、甚至数据库都可能不一样。
典型错误现象:用 GetUserQuery 处理器里顺手更新了用户最后登录时间,结果缓存失效、事务锁表、读请求变慢。
- Query 类型必须实现
IRequest<TResponse>,且对应 Handler 只能读,不能调用SaveChangesAsync - 别为了省事让一个 Handler 同时实现
IRequestHandler<CreateUserCommand>和IRequestHandler<GetUserQuery, UserDto>—— 编译虽过,但违背模式本意 - 如果真需要“查完立刻记日志”这类副作用,用
INotification+INotificationHandler,而不是塞进 Query Handler
为什么 Handler 里 await 失败但没报错
MediatR 默认不捕获 Handler 抛出的异常并包装,而是原样往上抛。但如果你用了异步流(比如 IAsyncEnumerable<T>)、或忘了在 Handler 方法签名里写 async Task<T>,就容易出现“执行了但没反应”或“返回默认值”的静默失败。
性能影响:同步写法(比如直接 return new UserDto())没问题;但涉及 DB 查询、HTTP 调用时,不用 await 就等于丢弃 Task,可能造成连接泄漏或超时未处理。
- Handler 方法签名必须是
Task<TResponse> Handle(...),不能是Task Handle(...)(除非响应类型是Unit) - EF Core 查询务必用
ToListAsync()、FirstOrDefaultAsync(),别用ToList()或FirstOrDefault()—— 否则同步阻塞线程池 - 调试时留意 Visual Studio 的“异常设置”里是否勾选了“仅我的代码”,否则底层
AggregateException可能被吞掉
CQRS 的复杂点不在写几个接口,而在于你得时刻意识到:命令走一条路,查询走另一条路,连日志记录、权限检查、甚至序列化方式,都可能因这条分界线而不同。很多人卡在“好像跑通了”,其实只是读写还没真正分开。


















