领域对象到实体模型,EF丢失跟踪问题(DDD)

问题描述 投票:0回答:1

我正在尝试遵循 DDD,但感觉我错过了一些东西。我的解决方案是这样的:

  • 具有
    Order
    OrderItem
    类的域项目(这些类具有包含一些业务逻辑的实例方法,如
    CanChangeOrder
    等)
  • 应用层
  • 使用 EF Core 的基础设施项目,即数据库表的包装器(如
    Orders
    OrderItems
  • Web 项目 - 控制器

我需要您的建议:

假设客户编辑他的订单(添加 1 件新商品、更改现有商品数量并删除 2 件商品)。

在(基础设施项目)中,我获取

Order
OrderItems
,转换为订单域对象(使用
automapper
)并返回到应用层的调用者方法。

var dbOrder = await _dbContext.Orders.Where(o => o.OrderId == orderId && o.DeletedDateTimeUtc == null).Include(o => o.OrderItems.Where(i => i.DeletedDateTimeUtc == null)).SingleOrDefaultAsync();
    
var domainOrder = _mapper.Map<Domain.Entities.Order>(dbOrder);
                return Result.Ok(domainOrder);
  

应用层)订单域对象有一个实例方法,用于验证订单是否可以编辑。如果为 true,我将添加、编辑、删除 Order 对象中的项目,现在将修改后的 Order 对象(包括 OrderItems)发送到持久层(基础设施项目)。

var orderResult = await _orderRepo.Get(request.OrderId);

if (!orderResult.Value.CanOrderBeUpdated())
  return Result.Fail("Sorry, order can not be changed now.");
else
   update domain model based on the user request

_orderRepo.Update(domainOrderModel)

基础设施层)这里我再次将我的

Order
域模型转换为EF Core实体
Order
模型。但所有
OrderItem
都被视为新项目,因为 EF Core 没有跟踪现有项目,从而在
OrderItems
表中产生新行。为了避免这种情况,我在下面的
Update
方法中进行了检查:

    public void Update(Domain.Entities.Order order)
            {
                var dbOrder = _mapper.Map<Infrastructure.Entities.Order>(order);

                // to instruct ef to insert or update an order item
                foreach (var item in dbOrder.OrderItems)
                {
                    if (item.OrderId > 0)
                       _dbContext.Entry(item).State = EntityState.Modified;
                }
        
                _dbContext.Orders.Update(dbOrder);
            }

我觉得这种方法效率不高(你觉得吗?),请提出改进意见。

谢谢你

c# entity-framework domain-driven-design automapper clean-architecture
1个回答
0
投票

我觉得这种方法效率不高(你觉得吗?),请提出改进意见。

答案取决于您如何看待持久层。

ORM 作为域持久性库

存储库的概念是在 ORM 库推广之前发明的。那时,基础设施层将是开发人员将 OOP (

Order.Update()
) 转换为 SQL (
UPDATE Orders SET ... WHERE id = ...
) 的地方。实际上,ORM 库是在开发人员尝试概括他们在 SQL 基础架构之上实现的对象存储库模式后构建的。

如果基础设施层的目的是无条件存储和恢复域状态,并且您并不真正关心数据库中数据的结构(域优先),那么是的,这是一种非常低效的方法。在这种情况下,使用 EF 实体作为您的域模型,并让 EF 本身(库)作为您的基础设施层。 DbContext 既充当存储库又充当工作单元:

namespace Domain;
public class UpdateOrderUseCase(MyDbContext database) // .NET 8 notation for consistency
{
  public void Execute(UpdateOrderDto payload)
  {
    var order = database.Orders.Find(payload.id) ?? throw new NotFoundException();
    order.CanUpdate || throw new ConflictException();

    // update order from payload

    database.SaveChanges();
  }
}

注意:上面的代码是一个意图示例,请注意您的实际实现。

但这会给您的领域模型带来一些限制:使用 EF 添加业务功能可能会很困难,因为库需要实体在关系世界中进行转换。您最终可能会拥有一个贫乏的领域模型,并将一些业务规则放入服务类中。

ORM 作为业务和基础设施之间的中介层

如果您希望拥有与业务模型结构不同的持久性模式,并且想要一个非贫乏的域模型,那么您需要这些转换。由于业务模型和持久性模型存在结构差异,因此进行此映射将具有更多的感知价值。

愚蠢的代码示例,允许将订单详细信息和商品详细信息存储在单独的数据库中,省略两阶段提交模式和事务以提高可读性:

namespace Infrastructure;
public class OrderRepository(
  OrderDbContext orders,
  OrderItemsDbContext items,
) : IOrderRepository
{
  public void Update(Domain.Entities.Order order)
  {
    var oder = orders.FindById(order.Id) ?? throw NotFoundException();
    // update order details
    orders.SaveChanges();

    items.Items.Where(item => item.OrderId == order.id).ExecuteDelete();
    items.Items.AddRange(order.Items.ProjectTo<ItemsEntity>());
    items.SaveChanges();
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.