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
相关产品推荐
相关产品推荐

