ObjectStateManager中已存在具有相同键的对象.ObjectStateManager无法使用相同的键跟踪多个对象

Jua*_*uan 71 c# entity-framework ninject repository-pattern entity-framework-5

使用带有通用存储库模式的EF5和ninject用于依赖性损害并在尝试使用存储过程与我的edmx将实体更新到数据库时遇到问题.

我在DbContextRepository.cs中的更新是:

public override void Update(T entity)
{
    if (entity == null)
        throw new ArgumentException("Cannot add a null entity.");

    var entry = _context.Entry<T>(entity);

    if (entry.State == EntityState.Detached)
    {
        _context.Set<T>().Attach(entity);
        entry.State = EntityState.Modified;
    }
}
Run Code Online (Sandbox Code Playgroud)

从我的AddressService.cs回到我的存储库我有:

 public int Save(vw_address address)
{
    if (address.address_pk == 0)
    {
        _repo.Insert(address);
    }
    else
    {
        _repo.Update(address);
    }

    _repo.SaveChanges();

    return address.address_pk;
}
Run Code Online (Sandbox Code Playgroud)

当它命中Attach和EntityState.Modified时,它会发出错误:

ObjectStateManager中已存在具有相同键的对象.ObjectStateManager无法使用相同的键跟踪多个对象.

我已经查看了堆栈和互联网上的许多建议,但没有提出解决它的任何建议.任何工作都将受到赞赏.

谢谢!

Lad*_*nka 125

编辑:使用原始答案Find而不是Local.SingleOrDefault.它与@Juan的Save方法结合使用但它可能导致对数据库进行不必要的查询,并且else部分可能从未执行过(执行else部分会导致异常,因为Find已查询数据库但未找到实体,因此无法更新) .感谢@BenSwayne找到问题.

您必须检查上下文是否已跟踪具有相同密钥的实体,并修改该实体而不是附加当前实体:

public override void Update(T entity) where T : IEntity {
    if (entity == null) {
        throw new ArgumentException("Cannot add a null entity.");
    }

    var entry = _context.Entry<T>(entity);

    if (entry.State == EntityState.Detached) {
        var set = _context.Set<T>();
        T attachedEntity = set.Local.SingleOrDefault(e => e.Id == entity.Id);  // You need to have access to key

        if (attachedEntity != null) {
            var attachedEntry = _context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
        } else {
            entry.State = EntityState.Modified; // This should attach entity
        }
    }
}  
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,主要问题是该SingleOrDefault方法需要知道查找实体的关键.您可以创建公开密钥的简单界面(IEntity在我的示例中),并在您希望以此方式处理的所有实体中实现它.

  • @LadislavMrnka:当你使用set.Find()时它是否已经在对象状态管理器中,它将从db加载,对吧?所以在上面的代码中,`attachedEntity`总是不为null,你永远不会附加传入的实体?(即:你永远不会到达`else {`语句)也许我误解了'DbSet <>的文档.查找()`.我们不应该使用DbSet <>.本地? (2认同)

小智 8

我不想通过添加接口或属性来污染我自动生成的EF类.所以这真的是上面的一些答案(所以归功于Ladislav Mrnka).这为我提供了一个简单的解决方案.

我在update方法中添加了一个func,它找到了实体的整数键.

public void Update(TEntity entity, Func<TEntity, int> getKey)
{
    if (entity == null) {
        throw new ArgumentException("Cannot add a null entity.");
    }

    var entry = _context.Entry<T>(entity);

    if (entry.State == EntityState.Detached) {
        var set = _context.Set<T>();
        T attachedEntity = set.Find.(getKey(entity)); 

        if (attachedEntity != null) {
            var attachedEntry = _context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
        } else {
            entry.State = EntityState.Modified; // This should attach entity
        }
    }
}  
Run Code Online (Sandbox Code Playgroud)

然后当你调用你的代码时,你可以使用..

repository.Update(entity, key => key.myId);
Run Code Online (Sandbox Code Playgroud)


Tof*_*fee 7

您实际上可以通过反射来检索Id,请参阅下面的示例:

        var entry = _dbContext.Entry<T>(entity);

        // Retreive the Id through reflection
        var pkey = _dbset.Create().GetType().GetProperty("Id").GetValue(entity);

        if (entry.State == EntityState.Detached)
        {
            var set = _dbContext.Set<T>();
            T attachedEntity = set.Find(pkey);  // access the key
            if (attachedEntity != null)
            {
                var attachedEntry = _dbContext.Entry(attachedEntity);
                attachedEntry.CurrentValues.SetValues(entity);
            }
            else
            {
                entry.State = EntityState.Modified; // attach the entity
            }
        }
Run Code Online (Sandbox Code Playgroud)