Un object con la stessa chiave esiste già in ObjectStateManager. ObjectStateManager non può tracciare più oggetti con la stessa chiave

Utilizzo di EF5 con un modello di repository generico e ninject per la dipendenza da dipendenza e in esecuzione in un problema quando si tenta di aggiornare un’entity framework nel database utilizzando i proc memorizzati con my edmx.

il mio aggiornamento in DbContextRepository.cs è:

public override void Update(T entity) { if (entity == null) throw new ArgumentException("Cannot add a null entity."); var entry = _context.Entry(entity); if (entry.State == EntityState.Detached) { _context.Set().Attach(entity); entry.State = EntityState.Modified; } } 

Dal mio AddressService.cs che risale al mio repository ho:

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

Quando colpisce l’Attach e EntityState.Modified emette l’errore:

Un object con la stessa chiave esiste già in ObjectStateManager. ObjectStateManager non può tracciare più oggetti con la stessa chiave.

Ho esaminato molti dei suggerimenti in stack e su Internet e non ho trovato nulla che lo risolva. Qualsiasi lavoro in giro sarebbe apprezzato.

Grazie!

Modifica : risposta originale utilizzata Find anziché Local.SingleOrDefault . Ha funzionato in combinazione con il metodo Save @ Juan, ma potrebbe causare query non necessarie al database e la parte probabilmente non è mai stata eseguita (l’esecuzione della parte else causerebbe un’eccezione perché Find ha già interrogato il database e non ha trovato l’ quadro in modo che non possa essere aggiornato). Grazie a @BenSwayne per aver trovato il problema.

È necessario verificare se un’entity framework con la stessa chiave è già tracciata dal contesto e modificare quell’ quadro invece di albind quella corrente:

 public override void Update(T entity) where T : IEntity { if (entity == null) { throw new ArgumentException("Cannot add a null entity."); } var entry = _context.Entry(entity); if (entry.State == EntityState.Detached) { var set = _context.Set(); 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 } } } 

Come puoi vedere il problema principale è che il metodo SingleOrDefault deve conoscere la chiave per trovare l’ quadro. È ansible creare un’interfaccia semplice che esponga la chiave ( IEntity nel mio esempio) e implementarla in tutte le quadro che si desidera elaborare in questo modo.

Non volevo inquinare le mie classi EF generate automaticamente aggiungendo interfacce o attributi. quindi questo è davvero un po ‘da alcune delle risposte di cui sopra (quindi il merito va a Ladislav Mrnka). Ciò ha fornito una soluzione semplice per me.

Ho aggiunto una funzione al metodo di aggiornamento che ha trovato la chiave intera dell’ quadro.

 public void Update(TEntity entity, Func getKey) { if (entity == null) { throw new ArgumentException("Cannot add a null entity."); } var entry = _context.Entry(entity); if (entry.State == EntityState.Detached) { var set = _context.Set(); 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 } } } 

Quindi quando chiami il tuo codice, puoi usare ..

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

Puoi effettivamente recuperare l’ID attraverso il riflesso, vedi esempio sotto:

  var entry = _dbContext.Entry(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 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 } } 

@ serj-sagan dovresti farlo in questo modo:

** Nota che YourDb dovrebbe essere una class derivata da DbContext.

 public abstract class YourRepoBase where T : class { private YourDb _dbContext; private readonly DbSet _dbset; public virtual void Update(T entity) { var entry = _dbContext.Entry(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 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 } } } 

}

Un’altra soluzione (basata sulla risposta di @ Sergey) potrebbe essere:

 private void Update(T entity, Func predicate) where T : class { var entry = Context.Entry(entity); if (entry.State == EntityState.Detached) { var set = Context.Set(); 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 } } } 

E allora lo chiameresti in questo modo:

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

Senza riflessioni e se non si desidera utilizzare le interfacce, è ansible utilizzare i delegati funzionali per trovare un’ quadro nel database. Ecco il campione aggiornato di cui sopra.

 private void Update(T entity, Func, T> locatorMap) where T : class { var entry = Context.Entry(entity); if (entry.State == EntityState.Detached) { var set = Context.Set(); 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 } } } 

Lo chiameresti in questo modo:

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

Se si imposta il contesto su AsNoTracking (), ciò impedirà a aspmvc di tenere traccia delle modifiche all’ quadro in memoria (che è ciò che si desidera comunque sul Web).

 _dbContext.Products.AsNoTracking().Find(id); 

Vi raccomando di leggere di più su questo http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/advanced-entity-framework-scenarios-for-an-mvc-web -applicazione

Scolbind l’ quadro trovata (vedi attachedEntity nella soluzione di Ladislav ) e ricolbind quella modificata ha funzionato bene per me.

Il ragionamento alla base di questo è semplice: se qualcosa è immutabile, sostituirlo (nel suo complesso, quadro) da dove appartiene a quello desiderato.

Ecco un esempio di come fare questo:

 var set = this.Set(); 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); 

Naturalmente, si può facilmente capire che questo frammento di codice fa parte di un metodo generico, quindi l’uso di T , che è un parametro di modello, e Set() .

Quella risposta sopra può essere EF 4.1+. Per quelli su 4.0, prova questo semplice metodo … non realmente testato ma ha allegato e salvato le mie modifiche.

  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("GRC9Entities.RiskInsights", item); } } _db.SaveChanges(); } 

Potresti aver dimenticato di instaçia l’object fBLL = new FornecedorBLL(); nel posto di algun