【问题标题】:Is this an EF Core bug or an I doing it wrong to update one entity?这是一个 EF Core 错误还是我在更新一个实体时做错了?
【发布时间】:2016-12-26 14:24:30
【问题描述】:

我将 Entity Framework Core 与存储库模式一起使用。为了帮助我,我用基本的 CRUD 方法编写了一个基础存储库。更新方法如下:

public void Update(TEntity entity)
{
    var contextEntry = _context.Entry<TEntity>(entity);
    if (contextEntry.State == EntityState.Dettached)
    {
        _context.Attach(entity);
    }
    contextEntry.State = EntityState.Modified;
    _context.SaveChanges();
}

鉴于包含此方法的 BaseRepository 类,我创建了一个继承自此的 User 存储库

public class UserRepository : BaseRepository<User>, IUserRepository
{
}

我已经在一个使用 ASP.NET Core 编码的 Web API 的 PUT 方法中使用了它

[HttpPut("~/api/users/{id}")]
public IActionResult Put(int id, [FromBody] User user)
{
    if (user == null || user.UserId != id)
    {
        return BadRequest();
    }

    userRepository.Update(user);
    return new NoContentResult();
}

现在,在发出请求时,_context.Attach(entity) 行出现一个错误。异常表示它无法添加要跟踪的实体,因为已经有另一个具有相同键的实体正在跟踪。

调试时我看到contextEntry.State 被设置为Unchanged。因此,它显然不等于EntityState.Dettached。尽管如此,执行还是进入了if 语句并尝试附加实体。

这里有些不对劲。这是一个错误吗?还是我做错了什么?我相信我是这个更新策略做错了什么的人,但我不确定。那么,我的方法有什么问题?

编辑:我将Update 方法更新为仅使用_context.Update(entity)_context.SaveChanges() 之后。尽管如此,_context.Update(entity) 还是会抛出一个 InvalidOperationException 并带有此消息:

附加信息:无法跟踪实体类型“用户”的实例,因为已经在跟踪具有相同键的该类型的另一个实例。添加新实体时,对于大多数键类型,如果没有设置键(即,如果键属性为其类型分配了默认值),则会创建一个唯一的临时键值。如果您为新实体显式设置键值,请确保它们不会与现有实体或为其他新实体生成的临时值发生冲突。附加现有实体时,请确保只有一个具有给定键值的实体实例附加到上下文。

【问题讨论】:

  • 似乎在同一上下文中的某处您正在获取相同的实体。
  • 但是在这种情况下,if 语句中的条件应该返回 false,因为实体已经被提取并且在上下文中可用,对吧? if 可以防止这种可能性,我不明白为什么它没有按预期工作。
  • 你不直接使用DbContext.Update有什么原因吗?
  • “仍然执行在 if 语句中” – 你确定错误是由你正在调试的那一行引起的吗?也许Update 方法也是从另一个地方调用的?你检查了完整的堆栈跟踪吗?
  • @poke,我尝试了DbContext 中的Update 方法,但它仍然发出同样的错误。我添加了一个带有完整错误消息的编辑

标签: c# entity-framework asp.net-core .net-core entity-framework-core


【解决方案1】:

您在项目中的某个位置从数据库中获取相同的实体,这就是它给您错误的原因。

Update 方法中,您只需在上下文中添加实体,这就是您得到contextEntry.State Unchanged 的​​原因。

您可以通过两种方式解决此问题。

  1. 从数据库中获取同一实体时,需要在同一实体上调用Detach方法。

  2. 将您在Update 方法中收到的实体中的值复制到现有的上下文实体中,并将​​该实体保存在数据库中。

【讨论】:

    【解决方案2】:

    所有信息都在异常消息中...您已经拥有附加了该主键的实体的另一个副本。

    我会推荐以下之一(首选):

    1. 为每个操作使用新的上下文,没有长期存在的存储库/上下文
    2. 使用主键在您的上下文中使用.Set&lt;TEntity&gt;.Find(object[] key),以便检索您已有的任何实体。
    3. 在您当前的更新方法中,使用Set&lt;TEntity&gt;.Local.Find(..) 检查它是否已经存在

    【讨论】:

    • 使用不同的上下文将意味着您无法查看对相关实体所做的更改。在 EF Core 不应该执行的连接方案中。必须有一种方法可以将更新的实体发送到上下文,以便它和它的子实体具有正确的状态集。
    猜你喜欢
    • 1970-01-01
    • 2010-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多