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

来源:这里教程网 时间:2026-02-21 17:40:38 作者:

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

CQRS(Command Query Responsibility Segregation)本质是把“改数据”和“读数据”彻底拆开——不是靠框架,而是靠接口分离、类型隔离和调用路径隔离。不依赖

MediatR
时,核心就是自己定义
ICommand
/
IQuery<t></t>
接口,再配两个独立的处理器抽象,不共享输入/输出模型,也不共用执行管道。

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

关键不是写得多,而是分得清。命令不返回业务数据(只返回

void
Task
),查询不修改状态(方法体里不能有
SaveChanges
Update
等)。所有类型都应显式声明职责:

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

示例:

public record CreateOrderCommand(string CustomerId, decimal Amount);
public interface ICommandHandler<in TCommand>
{
    Task Handle(TCommand command);
}
public class CreateOrderCommandHandler : ICommandHandler<CreateOrderCommand>
{
    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<type object></type>
静态缓存已注册的处理器实例,首次访问时通过
Activator.CreateInstance
构建并缓存,后续直接
Cast
调用
切忌在调度层做运行时类型判断 + 反射调用——性能差、堆分配多、调试困难

注意:

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

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

很多人只拆了命令,查询仍用 EF 的

DbSet<order></order>
直接暴露给 API,这等于没 CQRS。真正隔离要体现在三处:

查询 DTO 必须和实体类物理分离(不同命名空间、不同程序集更佳),禁止
select new OrderDto()
之外的任何对实体的引用
查询 Handler 内部应使用
AsNoTracking()
,且不复用命令侧的
DbContext
实例(哪怕同一请求周期)
避免在查询中调用
Include()
加载深层导航——那是命令侧或领域服务的事;查询应只投射所需字段,用
SELECT x,y,z
级别控制

一个典型错误是:在

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

相关推荐