DbSet.attach() 进行了下一次查询 return 错误的结果

DbSet.attach() made next query return wrong result

假设此User数据已记录在数据库中..

User {
   Id = 1,
   Name = "John",
   Job = "Programmer"
};

稍后,我想更新他的名字。所以,我执行这个命令

var entity = new User { Id = 1 };

_context.Users.Attach(entity);

entity.Name = "Jack";

await _context.SaveChangesAsync();

最后在我再次查询看到update

var order = await _context.Users
                          .FirstOrDefaultAsync(x => x.Id == 1));

但是我得到的数据很奇怪...

User {
   Id = 1,
   Name = "Jack",
   Job = null <----- this should not be null
};

看起来查询结果来自_context调用attach()方法后的记录。

任何想法或我应该怎么做才能防止这种奇怪的结果。

您在作业中看到#null 的原因是因为在最终查询中 DbContext returned 您的实体将是您启动并附加到上下文的实例。当您使用实体调用 SaveChanges 时,它的 ID 为 1,名称为 "Jack",作业为 #null。跟踪告诉 EF 只有名称发生了变化,所以这就是更新语句中进入数据库的全部内容。

EF 在幕后做了一些有点可疑的事情,但是当你获取一个实体时,它会 return 在从数据库加载实体之前缓存中的任何内容(如果可用)。令人困惑的是,如果您针对数据库进行分析,您将看到针对数据库引发的 SELECT,但实体 returned 将反映最后缓存的状态,而不是数据库状态。要刷新实体,您可以:

_context.Entity(user).Reload();

重新加载被跟踪的实体。或者...

_context.Entity(user).EntityState = EntityState.Detached;
user = _context.Users.Single(x => x.Id == 1);

...分离您再次更新的实体以在询问时强制 EF 重新加载或...

user = _context.Users.AsNoTracking().Single(x => x.Id == 1);

...加载新的未跟踪参考。

作为一般规则,在编辑实体时,您应该加载实体、应用更改并保存,而不是 Id + Attach 技巧。 Id + Attach 是删除场景或可能的批量更新的便捷 hack,但仅限于短暂的 DbContexts,以便这些跟踪的 "partial" 实体不会再次被其他查询 returned .