Объект с таким ключом уже существует в ObjectStateManager. ObjectStateManager не может отслеживать несколько объектов с одним ключом
использование 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.Модифицированный он блюет с ошибкой:
объект с тем же ключом уже существует в ObjectStateManager. ObjectStateManager не может отслеживать несколько объектов с одним и тем же ключом.
Я просмотрел многие предложения в стеке и в интернете и не придумал ничего, что его решает. Любые обходные пути будут оценены по достоинству.
спасибо!
10 ответов:
Edit: оригинальный ответ используется
Find
вместоLocal.SingleOrDefault
. Он работал в сочетании с @JuanSave
метод, но это может вызвать ненужные запросы к базе данных и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
в моем примере) и реализовать его во всех ваших сущностях, которые вы хотите обработать таким образом.
Я не хотел загрязнять мои автоматически созданные классы EF, добавляя интерфейсы или атрибуты. так что это действительно немного из некоторых из приведенных выше ответов (так что кредит идет Ладиславу Мрнке). Это обеспечивает простое решение для меня.
я добавил 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);
вы можете фактически вернуть идентификатор через отражение, см. пример ниже:
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 } }
@Серж-Саган вы должны сделать это таким образом:
* * обратите внимание, что 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 } } }
}
другое решение (на основе ответа @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)
без отражения и если вы не хотите использовать интерфейсы, вы можете использовать функциональные делегаты для поиска сущности в базе данных. Вот обновленный образец сверху.
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))
Если вы установите свой контекст в AsNoTracking (), это остановит aspmvc отслеживание изменений сущности в памяти (что вы хотите в любом случае в интернете).
_dbContext.Products.AsNoTracking().Find(id);
Я бы рекомендовал вам прочитать больше об этом на http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/advanced-entity-framework-scenarios-for-an-mvc-web-application
отсоединение найденного объекта (см.
attachedEntity
in Ладислав тут решение) и повторное присоединение модифицированного работало для меня просто отлично.причина этого проста: если что-то неизменное, то замените его (в целом, сущность), откуда оно принадлежит, желаемым.
вот пример того, как это сделать:
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>()
.
этот ответ выше может быть 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(); }