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


相关推荐: 为什么要用作用域操作符_php中访问类常量与静态属性的优势【解答】  zabbix利用python脚本发送报警邮件的方法  BootStrap整体框架之基础布局组件  网页制作模板网站推荐,网页设计海报之类的素材哪里好?  Laravel中间件如何使用_Laravel自定义中间件实现权限控制  制作公司内部网站有哪些,内网如何建网站?  Laravel如何配置和使用缓存?(Redis代码示例)  原生JS实现图片轮播切换效果  Laravel项目如何进行性能优化_Laravel应用性能分析与优化技巧大全  如何快速搭建自助建站会员专属系统?  如何获取免费开源的自助建站系统源码?  Laravel中间件起什么作用_Laravel Middleware请求生命周期与自定义详解  如何用ChatGPT准备面试 模拟面试问答与职场话术练习教程  米侠浏览器网页背景异常怎么办 米侠显示修复  如何自定义建站之星网站的导航菜单样式?  如何快速辨别茅台真假?关键步骤解析  Laravel路由怎么定义_Laravel核心路由系统完全入门指南  制作电商网页,电商供应链怎么做?  详解Android——蓝牙技术 带你实现终端间数据传输  EditPlus中的正则表达式实战(6)  网站图片在线制作软件,怎么在图片上做链接?  HTML 中动态设置元素 name 属性的正确语法详解  Laravel如何使用Service Provider注册服务_Laravel服务提供者配置与加载  Laravel如何实现RSS订阅源功能_Laravel动态生成网站XML格式订阅内容【教程】  香港服务器租用每月最低只需15元?  如何在万网自助建站中设置域名及备案?  Laravel怎么进行数据库事务处理_Laravel DB Facade事务操作确保数据一致性  LinuxShell函数封装方法_脚本复用设计思路【教程】  Laravel如何优雅地处理服务层_在Laravel中使用Service层和Repository层  JavaScript实现Fly Bird小游戏  Windows Hello人脸识别突然无法使用  *服务器网站为何频现安全漏洞?  Laravel怎么返回JSON格式数据_Laravel API资源Response响应格式化【技巧】  Laravel怎么实现一对多关联查询_Laravel Eloquent模型关系定义与预加载【实战】  做企业网站制作流程,企业网站制作基本流程有哪些?  Laravel事件监听器怎么写_Laravel Event和Listener使用教程  哪家制作企业网站好,开办像阿里巴巴那样的网络公司和网站要怎么做?  Win11搜索不到蓝牙耳机怎么办 Win11蓝牙驱动更新修复【详解】  如何在Tomcat中配置并部署网站项目?  如何快速生成ASP一键建站模板并优化安全性?  悟空识字怎么关闭自动续费_悟空识字取消会员自动扣费步骤  1688铺货到淘宝怎么操作 1688一键铺货到自己店铺详细步骤  如何快速生成专业多端适配建站电话?  Android使用GridView实现日历的简单功能  Laravel怎么生成URL_Laravel路由命名与URL生成函数详解  Win11怎么修改DNS服务器 Win11设置DNS加速网络【指南】  制作网站软件推荐手机版,如何制作属于自己的手机网站app应用?  Laravel如何生成和使用数据填充?(Seeder和Factory示例)  三星网站视频制作教程下载,三星w23网页如何全屏?  东莞专业网站制作公司有哪些,东莞招聘网站哪个好?