mediatr 不是 cqrs 实现,仅是轻量消息中介者;需手动定义 query/command 类型、拆分读写职责、显式路由处理器,send() 方法不区分语义,仅依赖泛型类型,须靠命名规范、接口标记和管道行为补全约束。

MediatR 本身不是 CQRS 实现,它只是帮你组织消息传递的轻量中介者;真要落地 CQRS,你得自己定义 Query 和 Command 类型、手动拆分读写职责、显式路由到不同处理器——MediatR 不会自动识别“这是查询”或“这是命令”。
为什么 IMediator.Send() 不能区分 CQRS 场景
MediatR 的 Send() 方法只看泛型参数类型,不关心语义。传入 GetUserQuery 还是 UpdateUserCommand,它都走同一套 pipeline(比如 IRequestHandler<trequest tresponse></trequest>)。它不校验命名、不强制接口继承、也不拦截写操作做事务包装。
- 常见错误现象:
Send(new CreateUserCommand())返回void,但你误以为它该返回 ID,结果没处理Unit或自定义响应类型 - 使用场景:所有请求都用
Send(),但你要靠命名规范(如后缀Query/Command)和团队约定来维持 CQRS 意图 - 参数差异:读操作通常实现
IRequest<tresponse></tresponse>,写操作可选IRequest<unit></unit>或IRequest(无返回),但 MediatR 不强制
怎么手动补全 CQRS 的关键约束
MediatR 不提供读写分离的基础设施,你需要在 DI 注册和处理器设计上主动隔离。
- 注册时区分生命周期:
Query处理器用Scoped(依赖 DbContext),Command处理器也用Scoped,但确保它们不共享仓储实例(避免读缓存污染写状态) - 显式声明意图:定义空标记接口
IQuery<t></t>和ICommand,让GetUserQuery : IQuery<userdto></userdto>、UpdateUserCommand : ICommand,再通过自定义IPipelineBehavior拦截并验证——比如禁止ICommand返回非Unit - 性能影响:加 pipeline 行为会引入额外反射调用,简单项目可省略;高并发下建议用 source generator 预生成 handler 查找逻辑(MediatR 12+ 支持)
INotification 和 CQRS 的关系容易被高估
很多人想用 INotification 做“命令执行后的事件通知”,比如 UserCreatedNotification,但这不属于 CQRS 的核心读写分离范畴,而是领域事件(Domain Events)模式。
- 常见错误现象:在
IRequestHandler<createusercommand unit></createusercommand>里直接await _mediator.Publish(new UserCreatedNotification()),导致事务未提交就发事件,下游可能查到脏数据 - 正确做法:把通知推迟到事务提交后,用 EF Core 的
DbContext.SaveChangesAsync()后再 publish,或引入IDomainEvent+ 本地事件总线(如 MediatR 的IPublisher配合IServiceScopeFactory延迟解析) - 兼容性注意:.NET 8 默认关闭 scoped service 在 background thread 中解析,
Publish若在非 scope 上下文调用会抛InvalidOperationException
真正难的不是注册几个 handler,而是让团队所有人严格遵守“Query 不改库、Command 不返业务数据、Notification 只在 commit 后触发”这些边界——这些规则 MediatR 一行代码都不会帮你 enforce。











