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

4

我有一个这样定义的聚合:

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 方法。问题是,我遇到了一个异常,提示如下:

预期影响1行记录,但实际上影响0行记录的数据库操作。数据可能已经被修改或删除,因为实体已经被加载。请参阅http://go.microsoft.com/fwlink/?LinkId=527962,了解乐观并发异常的理解和处理信息。

问题是我没有在其他任何位置更新同一产品,那是唯一接触它的地方。另外,我使用 AsNoTracking 作为默认设置。如果我将带有 _context.Update(prop); 的行注释掉,则不会引发异常,但价格也不会更新。另外,如果我不更新价格集合而是更新 Something 属性,则一切都很顺利。简直是闹鬼。


嗨,不太确定:_context.Attach(product).State = EntityState.Modified; 用于通知 EF 应该重新跟踪它。(对于非核心的 EF:_context.Entry(product).State = EntityState.Modified; - nilsK
我也遇到了同样的错误 :( - Marek M.
2个回答

1

我发布这个答案是为了那些将来迷失的旅行者,尽管我很乐意听到比我更了解EF Core的人的解释。我也认为我理解了那种行为的原因,但我不确定。当我在Price对象上添加了一个Id字段时,我的代码开始正常工作。我怀疑如果没有明确可见的id属性,无论附加或更新多少次,EF都看不到该对象。尽管如此,我仍然希望有关它的文档部分...


虽然我无法快速找到说明在不提供键时会发生什么的文档,但是这里有关于的文档。按照惯例,当您添加Price.Id时,应该同时添加一个键。 - Kirk Horton

1

拥有类型集合的EF Core文档明确指出,您必须定义拥有实体的主键(与OwnsOne不同,后者通常使用影子外键作为主键)。

因此,您需要定义它自己的主键(比如您所做的Id),或者复合主键 - 例如,如果Price.Type在所有者内是唯一的,则可以使用类似以下的内容:

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

并避免额外添加 Id 列。

问题在于EF框架已经在数据库中创建了主键。这有点违反您提供的链接中关于拥有类型需要一个主键的说法。既然它是自动生成的,为什么我需要在架构定义中明确提及它呢?它应该全部都显式声明并发出异常警告,告诉我不要在没有明确命名主键的情况下定义拥有集合类型,或者完全隐含,因此自动生成的主键就足够了。现在它只是令人困惑... - Marek M.
同时,获取一个类型为DbUpdateConcurrencyException的异常也无法帮助调试问题 :( - Marek M.
当你在Price对象上添加了一个Id字段时,你写道“我的代码开始表现正常了”。所以在那之前它并没有自动创建。无论如何,链接是官方的EF Core文档,所以如果它是最新的,那么它应该是它所说的。顺便说一下,你没有指定EF Core版本,这也可能很重要,因为即使是次要版本之间也会改变/破坏许多东西。至于异常,他们基本上用于每个可能的错误 - 例如尝试删除或更新不存在的记录。所以,是的,这个消息是误导性的 :-( - Ivan Stoev
好的,那么我错过了一个重要的信息 - 数据库表有一个由ORM创建的主键列名叫做id。无论如何,感谢您的解释! - Marek M.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接