在数据库中更新时未保存嵌套的拥有者类型

mad*_*dev 5 c# ef-core-2.2

我有以下课程:

订购:

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

预付款:

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

    public Money AmountPrepaid { get; private set; }
    public bool HasPrepaymentBeenTaken => AmountPrepaid.Amount > 0;
}
Run Code Online (Sandbox Code Playgroud)

钱:

public class Money {
        private Money()
        {
        }
        private Money(decimal amount)
        : this()
        {
            Amount = amount;
        }
        public decimal Amount { get; private set; }
}
Run Code Online (Sandbox Code Playgroud)

然后,我将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)");
                            });
                    });
Run Code Online (Sandbox Code Playgroud)

存储库代码到SaveChanges:

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

    return await _context.SaveChangesAsync();
}
Run Code Online (Sandbox Code Playgroud)

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

上面的代码适用于INSERT预付款->金钱->金额正确保存到数据库的情况。UPDATE尽管似乎没有反映在数据库中。

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

Order传递给存储库的class对象,首先从数据库中拉出,然后将更改应用于该实例,最后保存回数据库。其他属性(如Customer示例中未提及)也为,OwnedType并且UPDATE可以正常工作。

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

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();
}
Run Code Online (Sandbox Code Playgroud)

我正在使用的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;
}
Run Code Online (Sandbox Code Playgroud)

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

private void SetPrepayment(Prepayment prepayment)
{
    Prepayment = prepayment ?? throw new ArgumentNullException(nameof(prepayment));
}
Run Code Online (Sandbox Code Playgroud)

Iva*_*oev 5

该问题与嵌套拥有的实体类型有一些共同之处。

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

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

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

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

例如,如果GetOrderByIdAsync实现使用AsNoTracking查询,因此和实例都order不会被 跟踪,那么order.Prepaymentorder.Prepayment.AmountPrepaid_context_context.Update(order)将起作用。

如果您在调用之前手动分离它们,它也将起作用ApplyChanges(需要访问 db 上下文):

_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);
Run Code Online (Sandbox Code Playgroud)

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

  • 那确实奏效了。谢谢你。Github 上有一个问题,可能会提出更好的方法。我确信 nHibernate 正在更好地处理那些值对象和 DDD 概念。谢谢大家的解说! (2认同)