跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

C#中CQRS架构详解_C#命令查询分离教程【高级】

MediatR 仅是请求路由工具,不实现 CQRS;CQRS 需手动结合 MediatR、读写分离、事务控制与约定达成。 MediatR 本身不实现 CQRS,它只是帮你把
IRequest
路由到唯一
IRequestHandler
的管道工具;CQRS 是你用 MediatR + 约定 + 手动事务 + 读写模型分离搭出来的架构风格。 为什么
IRequest
和
IRequest
必须严格配对 编译器不会帮你校验泛型参数是否一致,运行时才报
Handler was not found for request
。这不是配置遗漏,而是类型系统在拒绝错误匹配。
GetUserQuery : IRequest
→ Handler 必须是
IRequestHandler
,写成
IRequestHandler
(实体类)就失败
DeleteUserCommand : IRequest
→ Handler 必须是
IRequestHandler
,返回
Task
或
Task
;若误写为
IRequestHandler
,Send 调用会静默跳过 Handler 泛型不区分大小写但区分结构:
IRequest
和
IRequest
在 .NET 中等价,但
IRequest
和
IRequest
不匹配 DbContext 生命周期错配导致
Cannot access a disposed object
Handler 默认是 Transient,而
AppDbContext
必须注册为 Scoped —— 这不是“建议”,是强制约束。错配后,Handler 构造函数注入的 DbContext 可能在 Handle 执行中途被释放。 注册必须显式写:
services.AddDbContext(ServiceLifetime.Scoped)
,不能只写
AddDbContext()
(.NET 默认是 Scoped,但显式声明更安全) 查询 Handler 中禁止
new DbContext()
,也禁止从
IDbContextFactory
创建后不
Dispose
;正确做法是构造注入
AppDbContext
并调用
.AsNoTracking()
命令 Handler 若需跨多个操作共享上下文(如“创建订单 + 扣库存”),必须确保它们在同一个 Scoped 容器中执行 —— 即同一个 HTTP 请求生命周期内
Send()
不自动包装事务,别在 Handler 里开事务 很多人以为 Send 是原子操作,其实它只是调度器。事务边界必须由调用方控制,否则会出现部分成功、数据不一致。 C知道 CSDN推出的一款AI技术问答工具 下载 事务应在 Controller 或 Minimal API endpoint 层开启:
using var tx = await context.Database.BeginTransactionAsync();
,再
await mediator.Send(cmd)
,最后
await tx.CommitAsync()
Handler 内部调用
SaveChangesAsync()
不等于事务提交 —— 它只把变更刷进当前 DbContext 缓存,真正落库靠外层
CommitAsync()
若一个请求需多个 Handler 协同(如发命令 + 发通知),且都需事务一致性,它们必须共用同一个
AppDbContext
实例 —— 这依赖 Scoped 生命周期正确传递,而非每个 Handler 自己 resolve 新实例 别把
INotification
当成查询或命令的替代品
Publish(new OrderCreatedNotification())
是 fire-and-forget,不参与主流程、无返回值、不保证顺序、默认不加入事务。把它当“异步命令”用,等于放弃 CQRS 的核心契约。 仅用于非关键路径:发邮件、写审计日志、更新 Elasticsearch 索引 禁止在
INotificationHandler
里调用
mediator.Send(new UpdateInventoryCommand())
—— 这会隐式引入嵌套命令链,破坏职责边界 如果需要“命令执行后同步触发另一操作”,应该在命令 Handler 内部直接调用,而不是 Publish 后再由另一个 Handler 捕获处理 最常被忽略的点:CQRS 不是“多写几个类”,而是读写路径彻底隔离 —— 查询不能改状态,命令不能返回实时聚合数据,DTO 和 Entity 必须物理分离。一旦 Handler 里出现
context.Entry(x).State = EntityState.Modified
或
AsNoTracking().FirstOrDefault()
混用,就已偏离模式本意。

相关文章