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


71

尝试将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;
    }
}

从我的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;
}

当它遇到Attach和EntityState.Modified时,会吐出错误:

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

我浏览了堆栈中和Internet上的许多建议,但没有提出任何解决方案。任何变通办法将不胜感激。

谢谢!

Answers:


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
        }
    }
}  

如您所见,主要问题是SingleOrDefault方法需要知道找到实体的键。您可以创建暴露密钥的简单界面(IEntity在我的示例中),并在要处理的所有实体中实现它。


谢谢。因此,我使用int ID {get;创建了一个接口IEntity。组; 然后尝试做公共重写void Update(T entity)where T:IEntity但不喜欢where T:IEntity。这在存储库类中,即公共类DbContextRepository <T>:BaseRepository <T>中,如果有区别,则在T:类中。谢谢!
胡安

1
在这种情况下,将约束直接放在类定义上
Ladislav Mrnka 2012年

嗯..仍然运气不佳。我想知道是否是因为我使用的是edmx模型。但是我无法将约束直接放在类上,因为它实现了BaseRepository和IRepository。另外,在edmx中,实体来自视图,主键类似于address_pk。
胡安

我有类似的问题(仍然没有解决)。问题是虚拟引用类型变量的属性不会更新。
patryk.beza 2012年

2
@LadislavMrnka:当您使用set.Find()时(如果它尚未在对象状态管理器中),它将从数据库中加载,对吗?所以在上面的代码attachedEntity中将始终不为null,并且您将永远不会附加传入的实体?(即:您将永远不会else {发表声明)也许我对的文档有误解DbSet<>.Find()。我们不应该使用DbSet <>。Local吗?
BenSwayne 2013年

8

我不想通过添加接口或属性来污染自动生成的EF类。因此,从上述一些答案中确实可以得出一点点(因此,Ladislav Mrnka功劳很大)。这为我提供了一个简单的解决方案。

我向发现实体整数键的更新方法添加了一个功能。

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
        }
    }
}  

然后,当您调用代码时,就可以使用。

repository.Update(entity, key => key.myId);

您不应该使用set.Local.Find而不是set.Find吗?我相信您的代码将始终访问数据库,因此永远不会使attachedEntity变量为null。msdn.microsoft.com/zh-CN/library/jj592872(v=vs.113).aspx
Reuel Ribeiro

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
            }
        }

@SerjSagan您可以轻松地做_dbContext.Set<T>().Create().GetTy..
约瑟夫·伍德沃德

2

@ serj-sagan你应该这样:

**请注意,YourDb应该是从DbContext派生的类。

public abstract class YourRepoBase<T> where T : class
{
    private YourDb _dbContext;
    private readonly DbSet<T> _dbset;

    public virtual void Update(T entity)
    {
        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
           }
       }
    }

}


1

另一个解决方案(基于@Sergey的答案)可能是:

private void Update<T>(T entity, Func<T, bool> predicate) where T : class
{
    var entry = Context.Entry(entity);
    if (entry.State == EntityState.Detached)
    {
        var set = Context.Set<T>();
        T attachedEntity = set.Local.SingleOrDefault(predicate); 
        if (attachedEntity != null)
        {
            var attachedEntry = Context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
        }
        else
        {
            entry.State = EntityState.Modified; // This should attach entity
        }
    }
}

然后您将这样称呼它:

Update(EntitytoUpdate, key => key.Id == id)

0

没有反射,如果您不想使用接口,则可以使用函数委托在数据库中查找实体。这是上面的更新示例。

private void Update<T>(T entity, Func<ObservableCollection<T>, T> locatorMap) where T : class
{
    var entry = Context.Entry(entity);
    if (entry.State == EntityState.Detached)
    {
        var set = Context.Set<T>();
        T attachedEntity = locatorMap(set.Local); 

        if (attachedEntity != null)
        {
            var attachedEntry = Context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(entity);
        }
        else
        {
            entry.State = EntityState.Modified; // This should attach entity
        }
    }
}

您可以这样称呼它:

Update(EntitytoUpdate, p => p.SingleOrDefault(a => a.Id == id))


0

卸下实体发现(见attachedEntity拉吉斯拉夫的解决方案),并重新安装修改后的一个工作对我来说只是罚款。

这背后的原因很简单:如果某个事物是不可变的,则用所需的事物从其所属位置替换它(作为一个整体)。

这是如何执行此操作的示例:

var set = this.Set<T>();
if (this.Entry(entity).State == EntityState.Detached)
{
    var attached = set.Find(id);
    if (attached != null) { this.Entry(attached).State = EntityState.Detached; }
    this.Attach(entity);
}

set.Update(entity);

当然,可以很容易地弄清楚此代码段是通用方法的一部分,因此使用T,它是模板参数,和Set<T>()


-2

上面的答案可能是EF 4.1+。对于4.0上的用户,请尝试这种简单的方法...尚未经过实际测试,但确实附加并保存了我的更改。

    public void UpdateRiskInsight(RiskInsight item)
    {
        if (item == null)
        {
            throw new ArgumentException("Cannot add a null entity.");
        }

        if (item.RiskInsightID == Guid.Empty)
        {
            _db.RiskInsights.AddObject(item);
        }
        else
        {
            item.EntityKey = new System.Data.EntityKey("GRC9Entities.RiskInsights", "RiskInsightID", item.RiskInsightID);
            var entry = _db.GetObjectByKey(item.EntityKey) as RiskInsight;
            if (entry != null)
            {
                _db.ApplyCurrentValues<RiskInsight>("GRC9Entities.RiskInsights", item);
            }

        }

        _db.SaveChanges();

    }

1
您似乎完全错过了OP想要使用通用存储库执行此操作的事实。
M.Stramm 2013年

-3

您可能已经忘记fBLL = new FornecedorBLL();在algun位置插入物体

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.