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

时间:2019-02-24 21:08:02

标签: 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
        }

预付款项:

    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)");
                            });
                    });

要保存更改的存储库代码:

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尽管似乎没有反映在数据库中。

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

传递到存储库的

Order类对象,首先从数据库中拉出,然后在该实例上应用更改,最后将其保存回数据库。示例中未提及的其他属性,例如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();
}

我使用的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));
        }

1 个答案:

答案 0 :(得分:3)

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

但是一般的问题是您使用拥有的实体类型的方式违反了EF Core规则,因此行为是不确定的-有时它可能行得通,有时不行,有时甚至抛出异常等。

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

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

由于存在上述违反EF Core规则的问题,因此很难给您好的建议。唯一的解决方法是确保上下文不跟踪所有原始对象引用。

例如,如果GetOrderByIdAsync实现使用AsNoTracking查询,则order既不会跟踪order.Prepayment也不order.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是更好的选择。您可以通过在数据库上下文构造函数内设置ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;并将其设置为所有查询的默认值,并在需要时使用AsTracking()