EntityFramework实体追踪之Attach和Detach

momo314相同方式共享非商业用途署名转载

最近在使用EntityFrameworkCore的时候遇到一个报错:

The instance of entity type 'xxx' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

这个错误是在对数据库上下文(DbContext)进行Attach操作的时候出现的。

什么意思的,大致就是说:要Attach的这个实体,不能被数据库上下文追踪。为啥呢?因为与要Attach的这个实体具有相同主键的另一个同类型的实体已经在上下文中存在了。

我们都知道,EntityFramework会在内存中维护一份数据库上下文,其中会包含一些实体,当这些实体被改变时,比如修改某个属性的值,则会被EF追踪到,这也是我们在EF中常用的更新指定字段的方法:

new Entity() -> Attach -> modify property A -> SaveChanges().

但是,当我们在Attach的时候发现相同Id已经在上下文中被追踪了,无法Attach了,怎么办呢?

当我们十分确定,当前上下文中被追踪的实体已经不再需要,我真的只是要对现在这个新实体做操作的时候,办法就十分的简单粗暴了。我的办法就是,在Attach操作进行之前,手动Detach掉原实体:

public void Attach(TEntity entity)
{
    ClearContextHoldingEntity(entity);
    _context.Attach(entity);
}

/// <summary>
/// 清理上下文中的同key实体
/// </summary>
/// <param name="entity"></param>
private void ClearContextHoldingEntity(TEntity entity)
{
    var target = _context.Set<TEntity>().Local.FirstOrDefault(item => item.Id == entity.Id);
    if (target == null)
        return;
    _context.Entry(target).State = EntityState.Detached;
}

以上代码在EntityFramework 2.1.4中验证通过,使用DbSet方式。

DbSet.Local是一个EntityFrameworkCore在内存中维护的被追踪的实体集合,本质上,只要我们将指定的实体从DbSet.Local中移除,EF对该实体的追踪也就结束了(Detached)。但DbSet.Local并没有提供方法直接移除(Detach)被追踪的实体,所以我们还是通过设置实体Entry为EntityState.Detached的方式来移除。

千万不要被DbSet.Local.Remove(TEntity)方法所迷惑,此方法会将实体Entry设置为EntityState.Deleted

✎﹏ 本文来自于 momo314和他们家的猫,文章原创,转载请注明作者并保留原文链接。