mediatr不是开箱即用的cqrs框架,仅是进程内请求路由工具;注册失败主因是未调用addmediatr()或生命周期配置错误(imediator必须为scoped),irequest用于无返回命令,irequest用于需返回值查询,tresponse不可为void,应改用unit;send用于一对一命令,publish用于一对多事件通知。

MediatR 不是开箱即用的 CQRS 框架,它只是进程内请求路由工具;没配对生命周期、没分清 IRequest 和 IRequest<tresponse></tresponse>、没管事务边界,代码跑起来也会出逻辑错觉。
IMediator 注册失败或拿不到实例怎么办
最常见报错是 InvalidOperationException: No service for type 'MediatR.IMediator',根本原因只有两个:没注册,或注册了但生命周期不对。
- 必须在
Program.cs里调用AddMediatR(),且不能漏掉using MediatR; - 跨类库时,
AddMediatR()必须显式传入含 Handler 的程序集,比如AddMediatR(typeof(CreateUserCommandHandler).Assembly),否则扫描不到 -
IMediator必须是Scoped生命周期,如果手动写AddSingleton<imediator>()</imediator>或没配 DI 容器作用域,运行时会解析失败 - Minimal API 下容易漏掉
builder.Services.AddMediatR(...)这一行,检查Program.cs开头的 services 配置段
IRequest 和 IRequest 选错导致编译通过但行为异常
MediatR 用泛型参数决定“查”还是“改”,不是靠类名。选错接口不会编译报错,但会让调用方和处理器语义脱节。
- 命令(如删除、发邮件)应继承
IRequest(无泛型),对应 Handler 实现IRequestHandler<trequest></trequest>,返回Task或Task<unit></unit> - 查询(如获取用户、列表分页)必须继承
IRequest<tresponse></tresponse>,Handler 必须实现IRequestHandler<trequest tresponse></trequest>,TResponse不能是void - 别为了“统一”让所有请求都带返回类型——
IRequest<bool></bool>看似方便,但bool不表达意图,Unit或具体 DTO 才符合 CQRS 语义 - 错误示例:
public class DeleteUserCommand : IRequest<bool></bool>+IRequestHandler<deleteusercommand bool></deleteusercommand>→ 编译过,但返回值被忽略,易造成“以为删了其实没删”的错觉
Command Handler 里 DbContext 被 new 出来或 Scoped 失效
事务无法跨 Handler 生效、并发修改出错、连接池耗尽,八成是因为 DbContext 生命周期失控。
-
AddDbContext<appdbcontext>(ServiceLifetime.Scoped)</appdbcontext>是硬性要求,漏掉Scoped会导致每次 Resolve 都是新实例 - Handler 内禁止
new AppDbContext(),也不该从IServiceProvider手动GetRequiredService<appdbcontext>()</appdbcontext>—— 这绕过了 DI 容器的作用域管理 - 事务必须由上层(如 Controller 或 Minimal API endpoint)开启并包裹
mediator.Send(),例如用await using var tx = await context.Database.BeginTransactionAsync() - 多个 Handler(如“创建订单 + 扣库存”)要共享同一 DbContext 实例,唯一前提是它们在同一个 Scoped 请求生命周期内被调用
INotification 当成 CQRS 查询/命令通道用
通知(INotification)天生不参与主流程:无返回、不保证顺序、默认异步 fire-and-forget。把它当查询结果或命令副作用通道,等于把日志系统当数据库用。
-
INotificationHandler<t></t>只适合非关键路径操作:清本地缓存、写审计日志、触发搜索索引更新 - 禁止在
INotificationHandler里调用SaveChangesAsync()或发新命令(如mediator.Send(new SendEmailCommand(...)))—— 这会制造隐式依赖链,破坏事务边界 - 高并发下,某个 handler 卡住(比如第三方邮件 API 延迟),会拖慢整个通知链;真需要可靠投递,必须外接 RabbitMQ/Kafka,而不是给
INotification加Task.Run - 一个典型误用:
OrderCreatedNotification的 handler 里去更新用户积分——这本该是命令的一部分,不该藏在通知里
真正难的从来不是怎么写 IRequestHandler,而是判断哪条逻辑该走命令、哪条该走查询、哪条连 CQRS 都不该进——比如“用户登录后更新最后登录时间”,它既不是纯查询,也不是独立命令,得看它是否影响读模型一致性,以及缓存策略怎么定。











