简体   繁体   English

在 EF Core 中永远不会更新拥有的类型集合

[英]Owned types collection is never updated in EF Core

I have an aggregate defined like this:我有一个这样定义的聚合:

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

And a schema defined like this:和这样定义的模式:

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

When a price change is requested, I do this (inside the product method not included here for brevity):当请求更改价格时,我会这样做(为简洁起见,此处未包含产品方法):

Prices.First(p => p.Type == type).Value = newValue;

And then I try to save the product like this:然后我尝试像这样保存产品:

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

Now I should mention that the product comes in from an initial query whose results were not tracked (via AsNoTracking() call), that's why I'm calling the Attach method in the first line of the method body.现在我应该提到产品来自一个初始查询,其结果没有被跟踪(通过AsNoTracking()调用),这就是为什么我在方法主体的第一行调用Attach方法。 The problem is that I'm hitting that catch statement with an exception message saying:问题是我正在使用一条异常消息点击该catch语句:

Database operation expected to affect 1 row(s) but actually affected 0 row(s).数据库操作预计会影响 1 行,但实际上影响了 0 行。 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."}有关理解和处理乐观并发异常的信息,请参阅http://go.microsoft.com/fwlink/?LinkId=527962 。"}

The thing is that I'm not updating the same product anywhere else and that's the only place touching it.问题是我不会在其他任何地方更新相同的产品,这是唯一接触它的地方。 Also I use AsNoTracking as the default.我也使用AsNoTracking作为默认值。 If I comment out the line with _context.Update(prop);如果我用_context.Update(prop);注释掉这一行_context.Update(prop); , then there's no exception raised, but the price is not being updated. ,则没有异常引发,但价格未更新。 Also, if I don't update that prices collection but the Something property, everything goes well.此外,如果我不更新价格集合而是更新Something属性,则一切顺利。 What.什么。 The.这。 Hell.地狱。

I'm posting this answer for the future lost travelers out there, although I would gladly hear an explanation from someone who knows more than me about EF Core.我正在为未来迷路的旅行者发布这个答案,尽管我很乐意听到比我更了解 EF Core 的人的解释。 I also think I understand the reason for that behavior, but I'm not sure.我也认为我理解这种行为的原因,但我不确定。 My code started behaving properly when I added an Id field on the Price object.当我在Price对象上添加 Id 字段时,我的代码开始正常运行。 My suspicion is that without an explicitly visible id property, no amount of attaching or updating would make EF see and that object.我的怀疑是,如果没有明确可见的 id 属性,则没有任何附加或更新会使 EF 看到该对象。 I would welcome a documentation section about it though...不过,我欢迎有关它的文档部分...

EF Core documentation for Collections of owned types explicitly states that you have to define the owned entity PK (as opposed to OwnsOne where the shadow FK is normally used as PK). 拥有类型集合的EF Core 文档明确指出您必须定义拥有的实体 PK(而不是OwnsOne ,其中影子 FK 通常用作 PK)。

Hence you need to either define its own PK (like Id you did), or composite PK - for instance, if Price.Type is unique inside the owner, then you can use something like因此,您需要定义自己的 PK(如您所做的Id )或复合 PK - 例如,如果Price.Type在所有者内部是唯一的,那么您可以使用类似

pp.HasKey("LocalId", "Type");

and avoid the additional Id column.并避免额外的Id列。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM