Подтвердить что ты не робот

Объект с тем же ключом уже существует в ObjectStateManager. ObjectStateManager не может отслеживать несколько объектов с одним и тем же ключом

Использование EF5 с общим шаблоном репозитория и ninject для ограничения зависимостей и проблемы при попытке обновления объекта в базе данных с использованием сохраненных procs с моим 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 не может отслеживать несколько объектов с одним и тем же ключом.

Я просмотрел множество предложений в стеке и в Интернете и не придумываю ничего, что разрешает его. Любые работы будут оценены.

Спасибо!

4b9b3361

Ответ 1

Изменить. Исходный ответ использовал 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 в моем примере) и реализовать его во всех ваших сущностях, которые вы хотите обработать таким образом.

Ответ 2

Я не хотел загрязнять мои автогенерированные классы 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);

Ответ 3

Фактически вы можете отследить отражение 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
            }
        }

Ответ 4

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

}

Ответ 5

Другим решением (на основе ответа @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)

Ответ 6

Без отражения, и если вы не хотите использовать интерфейсы, вы можете использовать функциональных делегатов для поиска объекта в базе данных. Вот обновленный пример сверху.

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

Ответ 7

Если вы установите свой контекст в 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

Ответ 8

Отсоединение найденной сущности (см. attachedEntity в решение Ladislav) и повторное присоединение измененного для меня работало отлично.

Обоснование этого просто: если что-то неизменное, то замените его (как целое, сущность) на то, где оно принадлежит с нужным.

Вот пример того, как это сделать:

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

Ответ 9

Этот ответ выше может быть 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();

    }

Ответ 10

Возможно, вы забыли установить объект fBLL = new FornecedorBLL(); в месте algun