Объект с таким же ключом уже существует в 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.Modified, его рвет с ошибкой:

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

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

Спасибо!


person Juan    schedule 25.09.2012    source источник


Ответы (10)


Изменить: в исходном ответе использовалось Find вместо Local.SingleOrDefault. Он работал в сочетании с методом Save @Juan, но мог вызвать ненужные запросы к базе данных, а часть 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) и реализовать его во всех ваших объектах, которые вы хотите обрабатывать таким образом.

person Ladislav Mrnka    schedule 25.09.2012
comment
Спасибо. Итак, я создал интерфейс IEntity с int Id {get; набор; } затем попытался выполнить публичное переопределение void Update(T entity), где T: IEntity, но ему не понравилось, где T: IEntity. Это класс репозитория, т. е. открытый класс DbContextRepository‹T› : BaseRepository‹T›, где T : класс, если это имеет значение. Спасибо! - person Juan; 25.09.2012
comment
В таком случае поместите ограничение непосредственно в определение класса - person Ladislav Mrnka; 25.09.2012
comment
хм.. все еще не повезло. Интересно, это потому, что я использую модель edmx. Но я не могу наложить ограничение непосредственно на класс, поскольку он реализует BaseRepository и IRepository. Кроме того, в edmx сущности поступают из представлений, а первичные ключи — это что-то вроде address_pk. - person Juan; 26.09.2012
comment
У меня была похожая проблема (все еще не решена). Проблема заключается в виртуальных свойствах переменных ссылочного типа, которые не обновляются. - person patryk.beza; 13.11.2012
comment
@LadislavMrnka: Когда вы используете set.Find(), если он еще не находится в диспетчере состояний объектов, он будет загружен из базы данных, верно? Итак, в приведенном выше коде attachedEntity всегда будет не нулевым, и вы никогда не будете присоединять переданный объект? (т.е. вы никогда не дойдете до оператора else {) Возможно, я неправильно понимаю документацию для DbSet<>.Find(). Не следует ли нам использовать DbSet‹›.Local? - person BenSwayne; 23.10.2013
comment
@BenSwayne: Вы правы. Приведенный выше код не достигает части else, если объект существует в базе данных, а если нет, произойдет сбой, потому что в таком случае часть else должна установить состояние Added или просто добавить объект в набор. Использование Local исправит это - я изменю ответ. - person Ladislav Mrnka; 24.10.2013
comment
@LadislavMrnka: когда attachEntry имеет значение null, должно ли это вызывать _context.Set‹T›().Attach(entity); сначала прикрепить объект перед установкой состояния? или это то, о чем комментарий? - person Stay Foolish; 03.01.2014
comment
Стоит знать, что копирование значений между сущностями не приведет к копированию полей, помеченных как [NotMapped]. Это ломает то, что я хочу сделать. - person Alan Macdonald; 05.02.2014
comment
Что делать, если вы не можете угадать, как называется первичный ключ объекта? здесь e => e.Id == entity.Id - person mhesabi; 31.03.2014
comment
@mhesabi Возможно, вам придется добавить предикат в качестве второго параметра public virtual void Update(T entity,Func<T, bool> predicate)where T : IEntity { //---hidden T attachedEntity = set.Local.SingleOrDefault(predicate); // So You don't need to have access to key if (attachedEntity != null) {} } } - person Bellash; 07.08.2014
comment
@LadislavMrnka обратите внимание, что тестирование Set‹T›().Local не найдет объекты, которые были удалены с помощью Set‹T›().Remove(), хотя они все еще будут прикреплены - person Leon van der Walt; 02.04.2015

Я не хотел загрязнять свои автоматически сгенерированные классы EF, добавляя интерфейсы или атрибуты. так что это действительно немного из некоторых из приведенных выше ответов (так что кредит принадлежит Ладиславу Мрнке). Это дало мне простое решение.

Я добавил функцию в метод обновления, которая нашла целочисленный ключ объекта.

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);
person Geoff Wells    schedule 28.02.2014
comment
Не следует ли использовать set.Local.Find вместо set.Find? Я считаю, что ваш код всегда будет попадать в базу данных, поэтому переменная attachedEntity никогда не станет нулевой. msdn.microsoft.com/en-us/library /jj592872(v=vs.113).aspx - person Reuel Ribeiro; 08.05.2017

На самом деле вы можете получить идентификатор через отражение, см. Пример ниже:

        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
            }
        }
person Toffee    schedule 20.02.2013
comment
@SerjSagan Вы можете просто сделать _dbContext.Set<T>().Create().GetTy.. - person Joseph Woodward; 03.02.2014

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

}

person Toffee    schedule 31.10.2013

Другим решением (на основе ответа @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)
person Andrés S.    schedule 24.04.2014

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

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))
person Sergey    schedule 15.12.2013

Если вы установите для своего контекста значение AsNoTracking(), это остановит отслеживание aspmvc изменений объекта в памяти (что вам и нужно в Интернете).

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

Я бы порекомендовал вам больше узнать об этом по адресу http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/advanced-entity-framework-scenarios-for-веб-приложениеmvc

person Yashvit    schedule 23.04.2014

Отсоединение найденного объекта (см. 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>().

person Alexander Christov    schedule 13.07.2017

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

    }
person thomas gathings    schedule 28.12.2012
comment
Кажется, вы полностью упускаете из виду тот факт, что ОП хочет сделать это с помощью универсального репозитория. - person M.Stramm; 07.03.2013

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

person Junior Ramoty    schedule 24.08.2016