69

將EF5與通用存儲庫模式配合使用,並將ninject用於依賴關係禁止,並在嘗試使用存儲過程與我的edmx一起更新實體到數據庫時遇到問題。ObjectStateManager中已存在具有相同鍵的對象。 ObjectStateManager無法使用相同的密鑰跟蹤多個對象

我在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; 
} 

當它擊中的安裝和EntityState.Modified它有錯誤的pukes:

ObjectStateManager中已存在具有相同鍵的對象。 ObjectStateManager不能使用同一個鍵跟蹤多個對象。

我已經瀏覽了堆棧和Internet上的許多建議,而不是提出任何可以解決它的問題。任何周圍的工作將不勝感激。

謝謝!

回答

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),並在你想以這種方式處理的所有實體中實現它。

+0

謝謝。所以我創建了一個接口IEntity int ID {get;組; }然後試圖做公共覆蓋無效更新(T實體)其中T:IEntity但它不喜歡T:IEntity。這是在一個存儲庫類,即公共類DbContextRepository :BaseRepository 其中T:類 如果這有所不同。謝謝! – Juan

+1

在這種情況下直接把約束放在類定義 –

+0

.hmm ..仍然沒有太多的運氣。我想知道它是否因爲我正在使用edmx模型。但是我無法將約束直接放在類上,因爲它實現了BaseRepository和IRepository。另外在edmx中,實體來自視圖,主鍵就像address_pk。 – Juan

-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想要使用通用存儲庫執行此操作的事實。 –

7

實際上,你可以通過反射retreive的標識,見下面的例子:

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

不知道如何以通用的方式獲得'_dbset' ... –

+0

@SerjSagan您可以簡單地執行'_dbContext.Set ().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 
      } 
     } 
    } 

}

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)) 
8

我不想褻瀆我自動加入的接口,或屬性產生的EF類。所以從上面的一些答案中可以看出這一點(所以請留意Ladislav Mrnka)。這爲我提供了一個簡單的解決方案。

我添加了一個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 
     } 
    } 
} 

然後當你打電話給你的代碼,你可以使用..

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

如果您不使用'set。 Local.Find'而不是'set.Find'?我相信你的代碼總是會碰到數據庫,因此永遠不會讓'attachedEntity'變量爲空。 https://msdn.microsoft.com/en-us/library/jj592872(v=vs.113).aspx –

1

另一種解決方案(基於@謝爾蓋的回答)可能是:

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) 
-3

您可能已經忘記instaçia對象fBLL = new FornecedorBLL(); i n algun的地方

0

分離找到的實體(請參閱attachedEntity in Ladislav's解決方案)並重新附加修改後的工作對我來說很好。

這背後的推理很簡單:如果某件東西是不可變的,那麼用它所屬的地方替換它(作爲一個整體,實體)。

下面是如何做到這一點的例子:

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>()

相關問題