MediatR要求IRequest与IRequestHandler严格一一对应,返回类型必须完全匹配,否则抛出Handler未找到异常;命令需实现IRequest以支持await,处理器应注入IDbContextFactory避免DbContext释放问题,DTO与Entity须分离以防耦合。

MediatR 的 IRequest 和 IRequestHandler 到底怎么配对
MediatR 不会自动扫描泛型类型,IRequest<tresponse></tresponse> 和 IRequestHandler<trequest tresponse></trequest> 必须严格一一对应,否则运行时报 InvalidOperationException: Handler was not found for request。
常见错误是:命令类实现了 IRequest<int></int>,但处理器写成了 IRequestHandler<mycommand void></mycommand>(返回 void 却声明要 int),或者漏了泛型参数。
- 查询命令必须返回非
void类型,且处理器的第二个泛型参数得完全一致(比如Task<userdto></userdto>对应IRequestHandler<getuserquery userdto></getuserquery>) - 命令类本身不需继承基类,但必须实现
IRequest<t></t>;处理器类必须实现对应接口,且只能有一个Handle方法 - ASP.NET Core 6+ 默认启用
Microsoft.Extensions.DependencyInjection的泛型注册,但 MediatR 要求你显式调用services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
为什么 Send 返回 Task 却不能直接 await 普通命令
因为 MediatR 的 Send<tresponse></tresponse> 是泛型方法,编译器无法从 void 命令中推断出 TResponse —— 所以你不能对“无返回值”的命令直接 await mediator.Send(new CreateUserCommand(...))。
这和你写 Task.Run(() => { }) 一样,它返回的是 Task,不是 Task<t></t>。强行 await 会导致编译失败或隐式转换错误。
- 有返回值的命令:用
await mediator.Send(new GetUserQuery(id)),类型安全 - 无返回值的命令:必须显式指定泛型参数为
Unit(MediatR 自带)或Task,推荐写成await mediator.Send(new CreateUserCommand(...))+ 把命令实现为IRequest<unit></unit>,然后return Unit.Value; - 别用
FireAndForget或Publish替代Send来绕过这个问题——它们语义完全不同,Publish是事件,不是命令
Handler 里依赖注入 DbContext 为什么总报 Cannot access a disposed object
这是因为 MediatR 的 handler 默认生命周期是 Transient,而你在 handler 构造函数里注入的 IDbContextFactory<appdbcontext></appdbcontext> 或直接注入 AppDbContext,如果没配对好作用域,就会在 handler 执行完后 DbContext 被提前释放。
最典型场景:你在 Program.cs 里注册了 services.AddDbContext<appdbcontext>(opt => opt.UseSqlServer(...))</appdbcontext>(默认 Scoped),但 handler 是 Transient,.NET 无法保证 DbContext 在 handler 生命周期内有效。
- 正确做法:注入
IDbContextFactory<appdbcontext></appdbcontext>,并在Handle方法里用using var ctx = await _factory.CreateDbContextAsync(); - 或者把 handler 改成
Scoped:在注册时加.AddTransient<irequesthandler unit>, CreateUserHandler>()</irequesthandler>→ 改成.AddScoped<...></...>,但要注意这不是 MediatR 推荐方式,容易引发作用域混乱 - 绝对不要在 handler 里缓存 DbContext 实例、或跨 await 保存引用——EF Core 的上下文不是线程安全的
CQRS 分层时,为什么 DTO 和 Entity 经常被混用
很多人把 UserDto 直接当 User Entity 传进 handler,或者反过来用 Entity 当查询返回值,结果导致序列化循环引用、N+1 查询、敏感字段泄露(比如密码哈希)、甚至 EF Core 跟踪污染。
CQRS 的核心不是多写几个类,而是切断读写模型之间的耦合路径。一旦你让 controller 直接 new 一个 Entity 丢给 command,那 CQRS 就退化成普通三层架构了。
- Command/Query 类只负责传输数据,不包含逻辑,也不继承 Entity;Handler 内部才做映射(推荐用
Mapster或手写AsNoTracking().Select(...)) - 查询返回值必须是 DTO,且禁止包含导航属性(除非明确需要,且已配置
Select加载);写入用的 Command 也应校验后再映射到 Entity - Entity 不该出现在 API 层或 MediatR 请求类里——哪怕只是
typeof(User)出现在泛型参数中,都算破防
真正难的不是写四个 handler,而是每次新增字段时,要同步改 Command、DTO、Entity、Mapper 配置——少一环,运行时才爆错,而且错得毫无提示。


















