【问题标题】:Entity Framework Core - Add and Update same entity with single SaveChangesAsync()Entity Framework Core - 使用单个 SaveChangesAsync() 添加和更新相同的实体
【发布时间】:2021-07-14 12:36:25
【问题描述】:

我有一张这样的桌子:

[Id] [INT] IDENTITY(1,1) NOT NULL,
..
..
[ParentId] [INT] NULL,
[CreatedOn] [DATETIME] NOT NULL,
[UpdatedOn] [DATETIME] NOT NULL

在某些情况下,我想用表的 ID 更新 ParentId,如下所示:

_dbContext.Add(data);

if (true)
{
   data.ParentId = data.Id;    
}

_dbContext.Update(data); 

await _dbContext.SaveChangesAsync();

这样做时,我收到此错误:

实体类型“数据”上的属性“Id”具有临时值,而 试图将实体的状态更改为“已修改”。要么设置一个 明确的永久值或确保数据库已配置 为该属性生成值。

是否有可能我正在尝试做什么,或者我需要在更新之前先调用 SaveChangesAsync()?

【问题讨论】:

    标签: asp.net entity-framework entity-framework-core entity-framework-6


    【解决方案1】:

    您遇到的错误

    Entity Framework 有一个叫做 change tracker 的东西。这执行了一些职责,我将提到与此答案相关的职责:

    • 当您致电SaveChanges() 时,它会跟踪需要发生的事情
    • 它会密切关注所有连接的实体

    当您调用SaveChanges() 时,EF 可能需要INSERT 一些实体,它需要UPDATE 其他一些(我忽略了其他操作,因为它们在这里无关紧要。为了跟踪这一点,EF 的更改跟踪器将一个名为 EntityState 的特定枚举附加到您的实体。根据您调用的方法,设置枚举值。

    _dbContext.Add(data);
    
    var the_enum_value = _dbContext.Entry(data).State;
    

    您会看到the_enum_value 等于EntityState.Added。这意味着当你调用SaveChanges()时,会生成一个INSERT语句。

    _dbContext.Update(data); //let's assume this is an existing entity you fetched
    
    var the_enum_value = _dbContext.Entry(data).State;
    

    在这里,您将看到 the_enum_value 等于 EntityState.Modified。这意味着当你调用SaveChanges()时,会生成一条UPDATE语句。

    您添加了一个新实体,因此您显然希望将其插入到数据库中。但是通过在该实体上调用 Update,您会将枚举值更改为 EntityState.Modified,从而导致生成 UPDATE 语句,即使数据库中还没有您需要更新的现有行。

    在过去,EF 只是更改了枚举,无论如何都尝试过,然后你会摸不着头脑,为什么你的更改没有进入数据库。

    如今,EF 已经足够聪明地意识到,如果实体还没有实际的 ID 值,那么将枚举设置为 EntityState.Modified 将是一个坏主意,因此它会提醒您您正在尝试做一些不起作用的事情。

    这里的一般解决方案是您根本不需要致电Update。你可以这样做:

    var data = new Data() { Name = "Foo" };
    
    _dbContext.Add(data);
    
    data.Name = "Bar";
    
    await _dbContext.SaveChangesAsync();
    

    您将看到访问数据库的名称是“Bar”,而不是“Foo”。因为EF只在你调用SaveChanges的时候生成INSERT语句,而不是在你调用Add的时候;因此,在调用 SaveChanges 之前所做的任何更改仍会被正在生成的 INSERT 语句“看到”。


    你正在尝试什么

    但是,您处于一种特别特殊的情况,因为您正在尝试访问和使用要创建的实体的 ID 属性。该值尚不存在。

    EF 尽最大努力忽略这一点,并使其在幕后为您服务。当您调用 SaveChanges() 时,EF 会找出生成的 ID 是什么,并默默地为您填写,这样您就可以继续使用您的 data 变量而无需担心。

    但我非常怀疑 EF 是否能够意识到data.ParentId 需要同样的处理。

    更新前需要先调用 SaveChangesAsync() 吗?

    在这种非常具体的情况下,很可能是的。但是,这是因为您尝试使用 data.Id 值,这与您报告的错误消息无关。

    【讨论】:

    • 感谢您的精彩解释,我担心的是我不想进行两次数据库调用。所以,试着在这里提交一个数据库
    • @ReyanChougle:正如错误中提到的那样,您将不得不手动设置 ID,但这会带来其自身的挑战(冲突、竞争条件等)。在这里执行第二个非常简单直接的调用会更好。
    猜你喜欢
    • 2013-07-21
    • 1970-01-01
    • 2020-02-06
    • 2017-05-05
    • 1970-01-01
    • 2016-09-13
    • 2020-02-25
    • 2018-02-27
    • 1970-01-01
    相关资源
    最近更新 更多