You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于MediatR实现CQRS模式时,如何保持.NET API代码DRY?

CQRS + MediatR + EF 中消除代码重复的方案探讨

我正在开发遵循CQRS模式的.NET API,使用MediatR与Entity Framework,目前遇到了代码重复的问题。

场景复现

初始创建Customer的处理器

最初只有创建Customer的需求,处理器代码如下:

public class CreateCustomerHandler(DbContext context): IRequestHandler<CreateCustomerCommand, Customer>
{
    public async Task<Customer> Handle(CreateCustomerCommand request, CancellationToken cancellationToken)
    {
        var c = new Customer()
        {
            Name = request.Name
        };
        await context.Customer.AddAsync(c, cancellationToken);
        await context.SaveChangesAsync(cancellationToken);
        return c;
    }
}

新增关联User后的处理器

后来需求变更:创建Customer时必须关联一个User实体,且后续支持为已有Customer添加User。此时CreateCustomerHandler修改为:

public class CreateCustomerHandler(DbContext context): IRequestHandler<CreateCustomerCommand, Customer>
{
    public async Task<Customer> Handle(CreateCustomerCommand request, CancellationToken cancellationToken)
    {
        var c = new Customer()
        {
            Name = request.Name
        };
        await context.Customer.AddAsync(c, cancellationToken);

        var u = new User()
        {
            CustomerId = c.Guid
        };
        await context.User.AddAsync(u, cancellationToken);
        
        await context.SaveChangesAsync(cancellationToken);
        return c;
    }
}

同时新增了为已有Customer添加User的处理器:

public class CreateUserHandler(DbContext context): IRequestHandler<CreateUserCommand, User>
{
    public async Task<User> Handle(CreateUserCommand request, CancellationToken cancellationToken)
    {
        var u = new User()
        {
            CustomerId = request.CustomerId
        };
        await context.User.AddAsync(u, cancellationToken);
        
        await context.SaveChangesAsync(cancellationToken);
        return u;
    }
}

现在的问题是:如果创建User的逻辑需要修改,必须同时修改两个处理器,违反了DRY原则。虽然示例简单,但肯定有办法消除重复。

已尝试的方案

1. 使用MediatR事件

将创建User的逻辑放到CustomerCreatedNotification的处理器中:

public class CreateCustomerHandler(DbContext context, IMediator mediator): IRequestHandler<CreateCustomerCommand, Customer>
{
    public async Task<Customer> Handle(CreateCustomerCommand request, CancellationToken cancellationToken)
    {
        var c = new Customer()
        {
            Name = request.Name
        };
        await context.Customer.AddAsync(c, cancellationToken);

        await mediator.Publish(
            new CustomerCreatedNotification{ 
                Customer = c
            }, cancellationToken
        );

        await context.SaveChangesAsync(cancellationToken);
        return c;
    }
}

问题:无法保证Customer与User创建的事务性,因为事件处理是异步执行的,可能出现Customer创建成功但User创建失败的情况。

2. 在处理器内部调用其他Command

在CreateCustomerHandler中直接发送CreateUserCommand:

public class CreateCustomerHandler(DbContext context, IMediator mediator): IRequestHandler<CreateCustomerCommand, Customer>
{
    public async Task<Customer> Handle(CreateCustomerCommand request, CancellationToken cancellationToken)
    {
        var c = new Customer()
        {
            Name = request.Name
        };
        
        await context.Customer.AddAsync(c, cancellationToken);

        var createUserCommand = new CreateUserCommand()
        {
            CustomerId = c.Guid
        };
        await mediator.Send(createUserCommand, cancellationToken);

        await context.SaveChangesAsync(cancellationToken);

        return c;
    }
}

问题:多篇文章指出这是代码异味,因为Command不再具备自包含性,调用方需要了解内部依赖的其他Command,破坏了CQRS中Command的单一职责原则。

3. 在控制器中执行多个Command

将创建Customer和User的逻辑放到控制器动作中:

[HttpPost]
public async Task<IActionResult> CreateCustomer(CreateCustomerRequest request, CancellationToken cancellationToken)
{
    var command = request.Adapt<CreateCustomerCommand>();
    
    var customer = await mediator.Send(command, cancellationToken);
    
    var createUserCommand = new CreateUserCommand
    {
        CustomerId = customer.Guid,
    };
    
    var user = await mediator.Send(createUserCommand, cancellationToken);

    var response = customer.Adapt<CreateCustomerResponse>();
    
    return Ok(response);
}

问题:控制器中混入了业务逻辑,且两个Command的执行不在同一个事务中,同样存在数据不一致的风险,显然不合理。

疑问

是否有合理的方法避免上述代码重复?这是使用CQRS模式的必然代价,还是我的思路存在偏差?


内容的提问来源于stack exchange,提问作者BetaByte

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 15:05:19