【问题标题】:nested Owned Type not saved when Updating in the database在数据库中更新时未保存嵌套的拥有类型
【发布时间】:2019-08-29 07:58:06
【问题描述】:

我有以下课程:

订单:

public class Order {
  private Order()
  {
      //some code to initialise object
  }
  //more properties
  public Prepayment Prepayment { get; private set; }
  //more methods and properties
}

预付款:

public class Prepayment:ValueObject<Prepayment>
{
    private Prepayment()
    {   
    }

    public Money AmountPrepaid { get; private set; }
    public bool HasPrepaymentBeenTaken => AmountPrepaid.Amount > 0;
}

金钱:

public class Money {
        private Money()
        {
        }
        private Money(decimal amount)
        : this()
        {
            Amount = amount;
        }
        public decimal Amount { get; private set; }
}

然后我将 Order 类通过以下方式映射到数据库:

modelBuilder.Entity<Order>()
                .OwnsOne(x => x.Prepayment,
                    prepayment =>
                    {
                        prepayment.OwnsOne(x => x.AmountPrepaid,
                            amountPrepaid =>
                            {
                                amountPrepaid.Property(x => x.Amount)
                                    .HasColumnName("PrepaymentAmount")
                                    .HasColumnType("decimal(7,2)");
                            });
                    });

SaveChanges 的存储库代码:

public async Task<int> SaveAsync(Order order)
{
    if (order.Id == 0)
    {
        await _context.AddAsync(order);
    }
    else
    {
        _context.Update(order);
    }

    return await _context.SaveChangesAsync();
}

请理解,我从代码中删除了所有不重要的属性,以使示例更清晰。

以上代码适用于INSERT 场景,其中预付款 -> 货币 -> 金额已正确保存到数据库中。 UPDATE 虽然似乎没有反映在数据库中。

请注意,我在该模型中有很多 Owned Types,并且它们都运行良好。据我所知,唯一的区别是 Prepayment 属性有另一个嵌套的 Owned 类型 - Money

Order 类对象传递到存储库,首先从数据库中提取,然后将更改应用于该实例,最后保存回数据库。示例中未提及的其他属性(例如 Customer)也是 OwnedTypeUPDATE 按预期工作。

以防万一 - 用于在更新之前检索对象的代码:

public async Task<Order> GetOrderByIdAsync(int orderId)
{
    var result = (from order in _context.Orders
                  where order.Id == orderId
                  select order).Include(x => x.OrderLines);

    return await result.FirstOrDefaultAsync();
}

我使用的 Entity Framework Core 的确切版本是:2.2.0

任何帮助将不胜感激。 谢谢。

编辑:

更新数据的代码如下所示:

public async Task<int> Handle(EditOrderCommand request, CancellationToken cancellationToken)
{
    var order = await _orderRepository.GetOrderByIdAsync(request.Id);

    var customer = new Customer(
        request.FirstName,
        request.LastName,
        request.TelephoneNumber);

    var prepayment = new Prepayment(
        Money.SomeMoney(
            request.PrepaymentAmount
            )
        );

    order.ApplyChanges(
            request.UserId, 
            request.AdditionalInformation,
            collectionDate,
            customer,
            prepayment);

    await _orderRepository.SaveAsync(order);

    return order.Id;
}

以及设置预付款的部分 ApplyChanges 方法:

private void SetPrepayment(Prepayment prepayment)
{
    Prepayment = prepayment ?? throw new ArgumentNullException(nameof(prepayment));
}

【问题讨论】:

  • 对象是否从用于Update相同 db 上下文实例中提取?修改了什么以及如何修改?所有这些私有构造函数/属性设置器让我觉得你在做一些不寻常/不被 EF Core 预期/支持的事情。提供一个小而完整的重现 (mcve) 将有助于识别问题。
  • 是的。我也尝试在更新之前执行 _context.Attach ,但根本没有改变。私有设置器也运行良好,Orders 对象上有一个更改预付款状态的方法。我在 Order 类上还有另一种行为正常的自有类型,据我所知,doffference 是嵌套的。预付款在其中嵌套了 Money 对象。
  • 如果它是从同一个数据库上下文实例中提取的,则它已经附加,因此通常不需要AttachUpdate。当然,你有一些变异方法,问题是它们做了什么。简单来说,它们是修改现有对象的属性还是创建新对象? DDD“值对象”看起来很可疑。看起来需要minimal reproducible example
  • 我现在知道了,它只是将新的 Prepayment 对象分配给 Order 值。 Prepayment 或 Money 甚至没有 Id 字段,因为它们是自有类型。作为比较,还有另一个 Property Customer - 具有 ValueObject 的私有设置器,并且每次都设置 Customer 的新实例 - 并且按预期工作。如果有机会,我会在下午晚些时候尝试准备一个示例。我现在放置了处理程序代码 - 保存更改并应用更新
  • 有一个bug in ef core 2.2 阻止自有类型的更新。您找到解决方法了吗?

标签: c# ef-core-2.2


【解决方案1】:

这个问题与嵌套拥有的实体类型有一些共同点。

但普遍的问题是您使用自有实体类型的方式违反了 EF Core 规则,因此行为未定义 - 有时它可能有效,有时无效,有时甚至抛出异常等。

拥有的实体类型不能用于实现值对象,因为即使它们是“拥有的”,根据 EF Core 术语,它们仍然是“实体”(具有隐藏的影子 PK),因此它们被跟踪通过引用 em> 并遵循与其他实体引用相同的规则 - 最重要的是,每个定义导航 PK 必须只有一个对象实例。

简而言之,它们应该被分配一次,然后通过它们的原始属性进行变异。而你正在做的是用不可变对象改变引用。

由于上述 EF Core 规则违规,很难给您提供好的建议。唯一可行的解​​决方法是确保上下文不跟踪所有原始对象引用。

例如,如果GetOrderByIdAsync 实现使用AsNoTracking 查询,则orderorder.Prepaymentorder.Prepayment.AmountPrepaid 实例都不会被_context 跟踪,那么_context.Update(order) 将起作用。

如果您在调用ApplyChanges 之前手动分离它们也可以工作(需要访问数据库上下文):

_context.Entry(order).State = EntityState.Detached;
_context.Entry(order.Prepayment).State = EntityState.Detached;
_context.Entry(order.Prepayment.AmountPrepaid).State = EntityState.Detached;

order.ApplyChanges(...);

await _orderRepository.SaveAsync(order); // _context.Update(order);

看起来AsNoTracking 是更好的选择。您可以通过在 db 上下文构造函数中设置 ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; 并在需要时使用 AsTracking() 将其设为所有查询的默认值。

【讨论】:

  • 这确实有效。谢谢你。 Github 上有一个问题,可能会想出更好的方法。我确信 nHibernate 可以更好地处理这些值对象和 DDD 概念。谢谢大家的解释!
  • 嗯,这似乎是to be a bug,不是不受支持的情况。
猜你喜欢
  • 2020-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-15
  • 2012-10-24
  • 2011-12-14
  • 1970-01-01
  • 2020-12-18
相关资源
最近更新 更多