拥有的类型集合在 EF Core 中永远不会更新
Owned types collection is never updated in EF Core
我有一个这样定义的聚合:
public class Product {
public int LocalId { get; private set; }
public Something Something { get; private set; }
public ICollection<Price> Prices { get; private set; }
}
public class Something {
public string Name { get; set; }
}
public class Price {
public int Type { get; set; }
public decimal Value { get; set; }
}
和这样定义的架构:
private void DefineProduct(ModelBuilder builder) =>
builder
.Entity<Product>(builder =>
{
builder.HasKey(p => p.LocalId);
builder
.OwnsOne(p => p.Something, smth =>
{
smth.ToTable("somethings");
})
.OwnsMany(p => p.Prices, pp =>
{
pp.ToTable("prices");
});
});
当要求更改价格时,我会这样做(在此处为简洁起见未包含在产品方法内):
Prices.First(p => p.Type == type).Value = newValue;
然后我尝试这样保存产品:
public async Task UpdateProperties(Product product, IEnumerable<object> props)
{
_context.Attach(product);
_context.Update(product);
foreach (var prop in props)
{
_context.Update(prop);
}
try
{
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
Console.WriteLine("Who the hell allowed such a bug to go into a production release?");
}
}
现在我应该提到产品来自初始查询,其结果未被跟踪(通过 AsNoTracking()
调用),这就是我在第一行调用 Attach
方法的原因方法体。问题是我用一条异常消息说 catch
语句:
Database operation expected to affect 1 row(s) but actually affected 0
row(s). Data may have been modified or deleted since entities were
loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for
information on understanding and handling optimistic concurrency
exceptions."}
问题是我不会在其他任何地方更新相同的产品,这是唯一接触它的地方。我也使用 AsNoTracking
作为默认值。如果我用 _context.Update(prop);
注释掉该行,则不会引发异常,但不会更新价格。此外,如果我不更新价格集合而是更新 Something
属性,一切都会顺利进行。什么。这。见鬼。
我正在为未来迷路的旅行者发布这个答案,尽管我很乐意听到比我更了解 EF Core 的人的解释。我也认为我理解这种行为的原因,但我不确定。当我在 Price
对象上添加一个 Id 字段时,我的代码开始正常运行。我怀疑如果没有明确可见的 id 属性,任何附加或更新都无法使 EF 看到该对象。不过,我会欢迎关于它的文档部分...
Collections of owned types 的 EF Core 文档明确指出您必须定义拥有的实体 PK(与 OwnsOne
相反,影子 FK 通常用作 PK)。
因此您需要定义其自己的 PK(就像您所做的 Id
)或复合 PK - 例如,如果 Price.Type
在所有者内部是唯一的,那么您可以使用类似
pp.HasKey("LocalId", "Type");
并避免额外的 Id
列。
我有一个这样定义的聚合:
public class Product {
public int LocalId { get; private set; }
public Something Something { get; private set; }
public ICollection<Price> Prices { get; private set; }
}
public class Something {
public string Name { get; set; }
}
public class Price {
public int Type { get; set; }
public decimal Value { get; set; }
}
和这样定义的架构:
private void DefineProduct(ModelBuilder builder) =>
builder
.Entity<Product>(builder =>
{
builder.HasKey(p => p.LocalId);
builder
.OwnsOne(p => p.Something, smth =>
{
smth.ToTable("somethings");
})
.OwnsMany(p => p.Prices, pp =>
{
pp.ToTable("prices");
});
});
当要求更改价格时,我会这样做(在此处为简洁起见未包含在产品方法内):
Prices.First(p => p.Type == type).Value = newValue;
然后我尝试这样保存产品:
public async Task UpdateProperties(Product product, IEnumerable<object> props)
{
_context.Attach(product);
_context.Update(product);
foreach (var prop in props)
{
_context.Update(prop);
}
try
{
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
Console.WriteLine("Who the hell allowed such a bug to go into a production release?");
}
}
现在我应该提到产品来自初始查询,其结果未被跟踪(通过 AsNoTracking()
调用),这就是我在第一行调用 Attach
方法的原因方法体。问题是我用一条异常消息说 catch
语句:
Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions."}
问题是我不会在其他任何地方更新相同的产品,这是唯一接触它的地方。我也使用 AsNoTracking
作为默认值。如果我用 _context.Update(prop);
注释掉该行,则不会引发异常,但不会更新价格。此外,如果我不更新价格集合而是更新 Something
属性,一切都会顺利进行。什么。这。见鬼。
我正在为未来迷路的旅行者发布这个答案,尽管我很乐意听到比我更了解 EF Core 的人的解释。我也认为我理解这种行为的原因,但我不确定。当我在 Price
对象上添加一个 Id 字段时,我的代码开始正常运行。我怀疑如果没有明确可见的 id 属性,任何附加或更新都无法使 EF 看到该对象。不过,我会欢迎关于它的文档部分...
Collections of owned types 的 EF Core 文档明确指出您必须定义拥有的实体 PK(与 OwnsOne
相反,影子 FK 通常用作 PK)。
因此您需要定义其自己的 PK(就像您所做的 Id
)或复合 PK - 例如,如果 Price.Type
在所有者内部是唯一的,那么您可以使用类似
pp.HasKey("LocalId", "Type");
并避免额外的 Id
列。