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

ASP.NET Clean Architecture+MediatR疑问:多实体查询与增改实现

关于Clean Architecture结合MediatR的两个问题解答

一、查询OrderDetail关联多实体的合理性判断

查询OrderDetail时关联多个相关实体(比如Order、Customer)本身是合理的,但要把握两个核心原则:

  • 按需加载,避免过度关联:只关联当前业务场景需要的实体,比如如果只需要Order的编号和Customer的名称,就不要加载整个Customer实体的所有字段。推荐用EF Core的Include/ThenInclude配合AutoMapper的ProjectTo,直接投影到DTO,减少不必要的数据查询和内存占用。
  • 遵循Clean Architecture分层原则:Repository层应返回IQueryable<T>而非已执行的查询结果,让Application层的Query Handler来决定需要关联哪些实体。这样既保持Repository的通用性,又让上层业务逻辑灵活控制查询范围。

反例:在Repository层硬编码关联所有可能的实体,导致每个查询都加载冗余数据,这就不合理。

二、一次性创建/编辑Order及关联数据的MediatR Command实现

1. 创建Order及关联OrderDetail的Command示例

定义CreateOrderCommand和DTO

// DTO:接收前端传入的订单明细
public class OrderDetailDto
{
    public int ProductId { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
}

// CreateOrderCommand:MediatR命令
public class CreateOrderCommand : IRequest<int>
{
    public int CustomerId { get; set; }
    public DateTime OrderDate { get; set; }
    public List<OrderDetailDto> OrderDetails { get; set; } = new();
}

Command Handler实现

public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, int>
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly IMapper _mapper;
    private readonly IRepository<Order> _orderRepo;

    public CreateOrderCommandHandler(IUnitOfWork unitOfWork, IMapper mapper, IRepository<Order> orderRepo)
    {
        _unitOfWork = unitOfWork;
        _mapper = mapper;
        _orderRepo = orderRepo;
    }

    public async Task<int> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
    {
        // 映射订单主实体
        var order = _mapper.Map<Order>(request);
        
        // 映射订单明细并关联到订单
        order.OrderDetails = _mapper.Map<List<OrderDetail>>(request.OrderDetails);
        
        await _orderRepo.AddAsync(order);
        await _unitOfWork.SaveChangesAsync(cancellationToken);
        
        return order.Id;
    }
}

2. 编辑Order及关联OrderDetail的Command示例

编辑的核心是处理关联数据的增删改,需要对比现有明细和传入的DTO:

定义UpdateOrderCommand

public class UpdateOrderCommand : IRequest<bool>
{
    public int Id { get; set; }
    public int CustomerId { get; set; }
    public DateTime OrderDate { get; set; }
    public List<OrderDetailDto> OrderDetails { get; set; } = new();
}

Command Handler实现

public class UpdateOrderCommandHandler : IRequestHandler<UpdateOrderCommand, bool>
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly IMapper _mapper;
    private readonly IRepository<Order> _orderRepo;
    private readonly IRepository<OrderDetail> _detailRepo;

    public UpdateOrderCommandHandler(IUnitOfWork unitOfWork, IMapper mapper, IRepository<Order> orderRepo, IRepository<OrderDetail> detailRepo)
    {
        _unitOfWork = unitOfWork;
        _mapper = mapper;
        _orderRepo = orderRepo;
        _detailRepo = detailRepo;
    }

    public async Task<bool> Handle(UpdateOrderCommand request, CancellationToken cancellationToken)
    {
        // 加载现有订单及关联明细(用Include确保跟踪)
        var existingOrder = await _orderRepo.GetByIdAsync(request.Id, cancellationToken, o => o.Include(d => d.OrderDetails));
        if (existingOrder == null) return false;

        // 更新订单主数据
        _mapper.Map(request, existingOrder);

        // 处理订单明细:删除不存在的,添加新的,更新修改的
        var existingDetailIds = existingOrder.OrderDetails.Select(d => d.Id).ToList();
        var incomingDetailIds = request.OrderDetails.Where(d => d.Id > 0).Select(d => d.Id).ToList();

        // 删除传入DTO中没有的现有明细
        foreach (var detailToDelete in existingOrder.OrderDetails.Where(d => !incomingDetailIds.Contains(d.Id)))
        {
            _detailRepo.Delete(detailToDelete);
        }

        // 处理新增和更新的明细
        foreach (var detailDto in request.OrderDetails)
        {
            if (detailDto.Id == 0)
            {
                // 新增明细
                var newDetail = _mapper.Map<OrderDetail>(detailDto);
                existingOrder.OrderDetails.Add(newDetail);
            }
            else
            {
                // 更新现有明细
                var existingDetail = existingOrder.OrderDetails.First(d => d.Id == detailDto.Id);
                _mapper.Map(detailDto, existingDetail);
            }
        }

        await _unitOfWork.SaveChangesAsync(cancellationToken);
        return true;
    }
}

关键注意点

  • 确保EF Core的实体关联配置正确(比如Order和OrderDetail的导航属性、外键),避免保存时出现关联错误。
  • 编辑时必须加载现有关联实体并让EF Core跟踪,否则无法正确处理更新和删除。
  • 用UnitOfWork统一管理事务,确保订单主数据和明细的操作原子性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 14:37:53