c# - 在数据库中更新时未保存嵌套的拥有类型
问题描述
我有以下课程:
命令:
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
property 有另一个嵌套的 Owned 类型 - Money
。
Order
传递到存储库的类对象,首先从数据库中提取,然后在该实例上应用更改,最后保存回数据库。Customer
示例中未提及的其他属性也是 aOwnedType
并且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));
}
解决方案
该问题与嵌套拥有的实体类型有一些共同点。
但一般问题是您使用拥有实体类型的方式违反了 EF Core 规则,因此行为未定义 - 有时它可能有效,有时无效,有时甚至抛出异常等。
拥有的实体类型不能用于实现值对象,因为即使它们是“拥有的”,按照 EF Core 术语它们仍然是“实体”(具有隐藏的影子 PK),因此它们通过引用进行跟踪并遵循与其他实体相同的规则引用 - 最重要的是,每个定义导航 PK 必须只有一个对象实例。
简而言之,它们应该被分配一次,然后通过它们的原始属性进行变异。虽然你正在做的是用不可变对象改变引用。
由于前面提到的 EF Core 规则违规,很难给你好的建议。唯一可行的解决方法是确保上下文不跟踪所有原始对象引用。
例如,如果GetOrderByIdAsync
实现使用AsNoTracking
查询,因此order
nororder.Prepayment
和order.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);
看起来AsNoTracking
是更好的选择。您可以通过ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
在 db 上下文构造函数中设置并AsTracking()
在需要时使用它来将其设为所有查询的默认值。
推荐阅读
- javascript - 从 Vuex 操作向 Vuex getter 传递参数
- python - 从项目中的模块导入的函数的 Pycharm 调用层次结构
- gradle - 升级到 Gradle 5 时的循环依赖
- database - 读取绑定到数据库表的 vb.net 中 Checklistbox 的检查状态
- c++ - 如何用联合初始化结构?
- c# - 在 wpf 中的数据网格行上按下按钮时,数据绑定上下文菜单不会填充项目
- c++ - 如何通过 TWAIN 获取扫描仪当前的 DPI 设置?
- java - 使用 JAVA 从网站页面中刮取“海拔”的值
- microsoft-graph-api - 是否可以存档教育团队
- javascript - AngularJS:启动init()函数中的HTTP响应为空