C# 手动实现CQRS模式方法 C#如何不依赖MediatR实现CQRS

发布时间 - 2026-02-02 00:00:00    点击率:
CQRS在C#中最简手动实现是通过分离ICommand/IQuery接口及对应处理器,命令只改状态无返回,查询只读数据不修改,类型不可变且职责明确。

什么是CQRS在C#里最简可行的手动实现

CQRS(Command Query Responsibility Segregation)本质是把“改数据”和“读数据”彻底拆开——不是靠框架,而是靠接口分离、类型隔离和调用路径隔离。不依赖 MediatR 时,核心就是自己定义 ICommand / IQuery 接口,再配两个独立的处理器抽象,不共享输入/输出模型,也不共用执行管道。

手动定义命令与查询的接口和处理器

关键不是写得多,而是分得清。命令不返回业务数据(只返回 voidTask),查询不修改状态(方法体里不能有 SaveChangesUpdate 等)。所有类型都应显式声明职责:

  • ICommandHandler:只接受一个 TCommand,无返回值
  • IQueryHandler:接受 TQuery,必须返回 TResult
  • 命令和查询类型本身是 record 或不可变 class,不继承、不带行为

示例:

public record CreateOrderCommand(string CustomerId, decimal Amount);
public interface ICommandHandler
{
    Task Handle(TCommand command);
}

public class CreateOrderCommandHandler : ICommandHandler
{
    private readonly OrderDbContext _db;
    public CreateOrderCommandHandler(OrderDbContext db) => _db = db;

    public async Task Handle(CreateOrderCommand command)
    {
        var order = new Order { CustomerId = command.CustomerId, Amount = command.Amount };
        await _db.Orders.AddAsync(order);
        await _db.SaveChangesAsync();
    }
}

如何避免手写大量 if-else 或 switch 路由逻辑

不用 MediatR 就意味着没有自动泛型解析,但也不必硬写反射调度。推荐两种轻量方案:

  • DI 容器直接注册具体处理器,按需注入——比如在 Controller 里明确构造 CreateOrderCommandHandler,不追求“统一路由”
  • 若真需要统一入口(如 API 层只暴露一个 Dispatch()),可用 Dictionary 静态缓存已注册的处理器实例,首次访问时通过 Activator.CreateInstance 构建并缓存,后续直接 Cast 调用
  • 切忌在调度层做运行时类型判断 + 反射调用——性能差、堆分配多、调试困难

注意:IServiceProvider.GetService(Type) 可用,但必须确保该 Type 已在 DI 中注册为具体实现,否则返回 null 不报错,容易漏测。

查询侧容易忽略的隔离细节

很多人只拆了命令,查询仍用 EF 的 DbSet 直接暴露给 API,这等于没 CQRS。真正隔离要体现在三处:

  • 查询 DTO 必须和实体类物理分离(不同命名空间、不同程序集更佳),禁止 select new OrderDto() 之外的任何对实体的引用
  • 查询 Handler 内部应使用 AsNoTracking(),且不复用命令侧的 DbContext 实例(哪怕同一请求周期)
  • 避免在查询中调用 Include() 加载深层导航——

    那是命令侧或领域服务的事;查询应只投射所需字段,用 SELECT x,y,z 级别控制

一个典型错误是:在 GetOrderSummaryQueryHandler 里调用了 _db.Orders.Include(x => x.Items),结果无意中触发了延迟加载或全表扫描——这不是 CQRS,这是披着查询外衣的命令副作用。


# 处理器  # ai  # switch  # 路由  # c#  # 延迟加载  # Object  # NULL  # if  # 命名空间  # select  # include  # void  # 继承  # 接口  #   # class  # 泛型  # 这是  # 加载  # 也不  # 那是  # 首次  # 两种  # 很多人  # 所需  # 得多  # 这不是 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: 轻松掌握MySQL函数中的last_insert_id()  Windows10电脑怎么查看硬盘通电时间_Win10使用工具检测磁盘健康  JS实现鼠标移上去显示图片或微信二维码  微信推文制作网站有哪些,怎么做微信推文,急?  5种Android数据存储方式汇总  如何用景安虚拟主机手机版绑定域名建站?  nodejs redis 发布订阅机制封装实现方法及实例代码  Laravel如何实现多语言支持_Laravel本地化与国际化(i18n)配置教程  详解jQuery中基本的动画方法  JS去除重复并统计数量的实现方法  Win11搜索栏无法输入_解决Win11开始菜单搜索没反应问题【技巧】  JavaScript常见的五种数组去重的方式  详解Android中Activity的四大启动模式实验简述  极客网站有哪些,DoNews、36氪、爱范儿、虎嗅、雷锋网、极客公园这些互联网媒体网站有什么差异?  Laravel怎么创建自己的包(Package)_Laravel扩展包开发入门到发布  Laravel如何配置和使用缓存?(Redis代码示例)  Laravel如何处理跨站请求伪造(CSRF)保护_Laravel表单安全机制与令牌校验  如何彻底删除建站之星生成的Banner?  如何确保FTP站点访问权限与数据传输安全?  Win11摄像头无法使用怎么办_Win11相机隐私权限开启教程【详解】  Laravel安装步骤详细教程_Laravel环境搭建指南  Python3.6正式版新特性预览  Laravel怎么配置S3云存储驱动_Laravel集成阿里云OSS或AWS S3存储桶【教程】  微信小程序 scroll-view组件实现列表页实例代码  HTML透明颜色代码怎么让下拉菜单透明_下拉菜单透明背景指南【技巧】  免费的流程图制作网站有哪些,2025年教师初级职称申报网上流程?  如何批量查询域名的建站时间记录?  高防服务器租用首荐平台,企业级优惠套餐快速部署  Windows10电脑怎么设置虚拟光驱_Win10右键装载ISO镜像文件  Laravel如何配置.env文件管理环境变量_Laravel环境变量使用与安全管理  EditPlus中的正则表达式 实战(2)  Android实现代码画虚线边框背景效果  Laravel怎么做缓存_Laravel Cache系统提升应用速度的策略与技巧  敲碗10年!Mac系列传将迎来「触控与联网」双革新  详解Android——蓝牙技术 带你实现终端间数据传输  Laravel如何监控和管理失败的队列任务_Laravel失败任务处理与监控  Laravel如何使用Service Container和依赖注入?(代码示例)  Python数据仓库与ETL构建实战_Airflow调度流程详解  图片制作网站免费软件,有没有免费的网站或软件可以将图片批量转为A4大小的pdf?  Laravel用户认证怎么做_Laravel Breeze脚手架快速实现登录注册功能  Linux系统运维自动化项目教程_Ansible批量管理实战  如何在阿里云虚拟机上搭建网站?步骤解析与避坑指南  网易LOFTER官网链接 老福特网页版登录地址  香港服务器网站卡顿?如何解决网络延迟与负载问题?  网站制作报价单模板图片,小松挖机官方网站报价?  如何在橙子建站上传落地页?操作指南详解  如何在云虚拟主机上快速搭建个人网站?  详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)  个人网站制作流程图片大全,个人网站如何注销?  如何在香港免费服务器上快速搭建网站?