必须调用AddMediatR并显式注册含Handler的程序集,否则Send抛InvalidOperationException; IRequest选void泛型非法,Query只读、Command只写,领域事件须用IPublisher确保事务一致。

没注册 IMediator 或注册方式不对,IMediator 就根本拿不到——后面所有 Send 都会直接抛 InvalidOperationException。
Program.cs 里怎么注册才不报错
必须调用 AddMediatR,且不能只写 builder.Services.AddMediatR()(.NET 6+ 默认参数会漏掉跨程序集的 Handler)。常见错误是只传了当前入口程序集,但 Handler 实际在类库中。
- Handler 和请求类都在主项目(如 Web API):用
AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly)) - Handler 单独放在
Application或Handlers类库中:必须显式传入那个程序集,比如AddMediatR(typeof(CreateUserCommandHandler).Assembly) - 漏掉
using MediatR;——AddMediatR方法根本不会出现在智能提示里 - 没加
.AsScoped()(或没走默认 Scoped 生命周期):MediatR 内部依赖IServiceProvider解析 Handler,而 Handler 通常要注入DbContext这类 Scoped 服务,生命周期不匹配会炸
IRequest 和 IRequest 怎么选
选错会导致编译失败、运行时报 Cannot convert lambda expression,或者 Send 返回 void 却被当成有返回值用。
- 纯触发动作、不关心结果(如发邮件、记录日志):用
IRequest,Handler 签名是Task Handle(...) - 需要返回数据(如创建后返回 ID、查询后返回 DTO):必须用
IRequest、IRequest,Handler 签名是TaskHandle(...) - 绝对不要用
void作泛型参数——IRequest不合法,编译不过 - 命令类里别塞
[Required]这类验证属性;验证逻辑该交给IRequestPreProcessor或 FluentValidation
Handler 里为什么不能混读写逻辑
一个 GetUserQueryHandler 里顺手调了 _context.SaveChangesAsync(),表面能跑,实际会破坏 CQRS 的隔离前提,引发缓存失效、事务锁表、读接口变慢等连锁问题。
- Query Handler 只能读:用
IUserReadRepository或只读DbSet.AsNoTracking(),禁止任何SaveChangesAsync - Command Handler 只负责改状态:它不该返回完整 Entity(易触发 EF 延迟加载或序列化循环引用),应返回 ID、DTO 或
Unit - 别让一个 Handler 同时实现
IRequestHandler和IRequestHandler——类型系统允许,但语义上已违背 CQRS - 共用同一个
DbContext实例没问题(Scoped 生命周期合理),但读写操作要严格分层,不能靠“自觉”隔离
领域事件该用 IPublisher 还是 IMediator
用 IMediator.Publish() 发领域事件,大概率会出事务一致性问题——因为 IMediator 不感知 EF Core 的 Unit of Work,事件可能在事务提交前就发出去了。
- 领域事件必须用
IPublisher接口(MediatR 提供),它和 EF Core 的事务拦截器能配合,在SaveChangesAsync成功后才真正发布 -
IPublisher是 Scoped 生命周期,直接从 DI 获取即可,别 new 实例 - 事件类必须继承
INotification,不是IRequest;Handler 要实现INotificationHandler - 别在 Command Handler 里直接 await
_publisher.Publish(...)后再 return——事件发布失败不该阻塞主流程,建议 fire-and-forget 或用后台队列兜底
最常被忽略的一点:CQRS 不是靠命名区分的,而是靠 Handler 的实际行为。哪怕类名叫 GetUserQuery,只要它里面调了 SaveChanges,就不是 Query。











