商城首页欢迎来到中国正版软件门户

您的位置:首页 >c#如何使用MediatR_c#MediatR的正确用法与注意事项

c#如何使用MediatR_c#MediatR的正确用法与注意事项

  发布于2026-05-03 阅读(0)

扫一扫,手机访问

C#中MediatR的正确用法与核心注意事项

c#如何使用MediatR_c#MediatR的正确用法与注意事项

在C#项目里引入MediatR,本意是让代码更清晰、更解耦。但实际操作中,不少开发者第一步就卡住了——如果没正确注册IMediator,或者注册方式有偏差,那么后续所有的Send操作都会直接抛出InvalidOperationException,让你寸步难行。

Program.cs 里怎么注册才不报错

问题的根源往往在服务注册这一步。必须调用AddMediatR,但仅仅写builder.Services.AddMediatR()是远远不够的(尤其是在.NET 6+的默认参数下,很容易漏掉跨程序集的Handler)。一个常见的坑是只传入了当前入口程序集,而Handler实际却定义在另一个类库中。

  • 场景一:Handler和请求类都在主项目(比如一个Web API)。这时应该用AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly)),明确指定从当前程序集加载。
  • 场景二:Handler单独放在ApplicationHandlers类库中。这就必须显式传入那个类库的程序集,例如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。那就必须用IRequestIRequest这类泛型接口,Handler签名相应是Task Handle(...)
  • 有一个绝对要避免的写法:不要用void作为泛型参数IRequest是不合法的,编译直接通不过。
  • 另外,命令类(Command)里最好别塞[Required]这类数据注解属性来做验证。验证逻辑应该交给IRequestPreProcessor或者专门的验证库(如FluentValidation)来处理,这样职责更清晰。

Handler 里为什么不能混读写逻辑

想象一下,在一个GetUserQueryHandler里,你顺手调用了_context.Sa veChangesAsync()。代码可能能跑起来,但这实际上破坏了CQRS(命令查询职责分离)的隔离前提,会引发一系列连锁问题:缓存失效、事务锁表,甚至导致本该快速的读接口变得异常缓慢。

  • Query Handler 只负责读:应该使用IUserReadRepository这类只读仓储,或者至少用DbSet.AsNoTracking()来确保不跟踪变更。严格禁止任何形式的Sa veChangesAsync调用。
  • Command Handler 只负责改状态:它的职责是改变领域状态,通常不应该返回完整的Entity对象(这容易意外触发EF Core的延迟加载或序列化时的循环引用问题)。更合适的做法是返回ID、精简的DTO,或者直接返回Unit.Value
  • 从设计上就避免混淆:不要让一个Handler类同时实现IRequestHandlerIRequestHandler。类型系统虽然允许,但这在语义上已经违背了CQRS的核心思想。
  • 当然,读写操作可以共用同一个DbContext实例(只要Scoped生命周期配置合理)。但关键在于,读写逻辑必须在代码层面严格分层,不能仅仅依靠开发者的“自觉”来隔离。

领域事件该用 IPublisher 还是 IMediator

如果你用IMediator.Publish()来发布领域事件,很可能会遇到事务一致性问题。因为IMediator并不感知EF Core的Unit of Work(工作单元),事件可能在数据库事务提交之前就被发出去了,导致数据状态不一致。

  • 正确的选择是IPublisher:这是MediatR专门提供的接口。它与EF Core的事务拦截器可以协同工作,确保事件只在Sa veChangesAsync成功执行后才真正发布出去。
  • IPublisher本身也是Scoped生命周期,直接从依赖注入容器中获取即可,千万不要自己去new实例。
  • 注意事件类的定义:它必须继承INotification,而不是IRequest。对应的Handler则需要实现INotificationHandler
  • 还有一个实践细节:不建议在Command Handler里直接await _publisher.Publish(...)之后再return。因为事件发布的失败不应该阻塞主业务流程。更稳健的做法是采用fire-and-forget(触发后不管)模式,或者用后台队列来兜底。

最后,需要时刻牢记一点:CQRS的区分,靠的不是类名,而是Handler的实际行为。哪怕你给类起名叫GetUserQuery,只要它在里面调用了Sa veChanges,那它本质上就已经不是一个Query了。这才是理解MediatR和CQRS模式的关键所在。

本文转载于:https://www.php.cn/faq/2315573.html 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注