ASP.NET MVC - не удалось прикрепить объект типа MODELNAME, поскольку другой объект того же типа уже имеет такое же значение первичного ключа

122

В двух словах, исключение выдается во время POSTing модели оболочки и изменения состояния одной записи на «Изменено». Перед изменением состояния устанавливается состояние «Отсоединено», но вызов Attach () выдает ту же ошибку. Я использую EF6.

Пожалуйста, найдите мой код ниже (названия моделей были изменены, чтобы их было легче читать)

Модель

// Wrapper classes
        public class AViewModel
        {
            public A a { get; set; }
            public List<B> b { get; set; }
            public C c { get; set; }
        }   

контроллер

        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }

            if (!canUserAccessA(id.Value))
                return new HttpStatusCodeResult(HttpStatusCode.Forbidden);

            var aViewModel = new AViewModel();
            aViewModel.A = db.As.Find(id);

            if (aViewModel.Receipt == null)
            {
                return HttpNotFound();
            }

            aViewModel.b = db.Bs.Where(x => x.aID == id.Value).ToList();
            aViewModel.Vendor = db.Cs.Where(x => x.cID == aViewModel.a.cID).FirstOrDefault();

            return View(aViewModel);
        }

[HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit(AViewModel aViewModel)
        {
            if (!canUserAccessA(aViewModel.a.aID) || aViewModel.a.UserID != WebSecurity.GetUserId(User.Identity.Name))
                return new HttpStatusCodeResult(HttpStatusCode.Forbidden);

            if (ModelState.IsValid)
            {
                db.Entry(aViewModel.a).State = EntityState.Modified; //THIS IS WHERE THE ERROR IS BEING THROWN
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(aViewModel);
        }

Как показано в строке выше

db.Entry(aViewModel.a).State = EntityState.Modified;

выдает исключение:

Прикрепить объект типа «A» не удалось, поскольку другой объект того же типа уже имеет такое же значение первичного ключа. Это может произойти при использовании метода «Присоединить» или установке состояния объекта на «Неизменено» или «Изменено», если какие-либо объекты на графике имеют конфликтующие значения ключей. Это может быть связано с тем, что некоторые объекты являются новыми и еще не получили значения ключей, созданные базой данных. В этом случае используйте метод «Добавить» или «Добавленное» состояние объекта для отслеживания графа, а затем установите для состояния не новых объектов значение «Без изменений» или «Изменено» в зависимости от ситуации.

Кто-нибудь видит что-то не так в моем коде или понимает, при каких обстоятельствах он может выдать такую ​​ошибку при редактировании модели?

Крис Чизак
источник
Вы пробовали прикрепить свою сущность перед установкой EntityState? Поскольку ваша сущность поступает из почтового запроса, она не должна отслеживаться текущим контекстом, я думаю, она считает, что вы пытаетесь добавить элемент с существующим идентификатором
Реда Маттар
Я пробовал это, и результат точно такой же :( По какой-то причине контекст думает, что я создаю новый элемент, но я просто обновляю существующий ...
Крис Чизак,
Я проверяю состояние 'a' до того, как будет выдана ошибка, и состояние этого объекта 'Отсоединено', но при вызове db.As.Attach (aViewModel.a) выдается точно такое же сообщение? Любые идеи?
Chris Ciszak
5
Я только что увидел ваше обновление, как вы настроили область действия контекста? Это по запросу? Если dbэкземпляр одинаков для двух ваших действий, это может объяснить вашу проблему, поскольку ваш элемент загружается методом GET (затем отслеживается контекстом), и он может не распознать тот, который в вашем методе POST, как объект, полученный ранее ,
Réda Mattar
1
canUserAccessA()Загружает ли объект напрямую или как отношение другого объекта?
CodeCaster

Ответы:

155

Задача решена!

Attachпотенциально может помочь кому-то, но в данной ситуации он не поможет, поскольку документ уже отслеживался при загрузке в функции контроллера Edit GET. Attach выдаст точно такую ​​же ошибку.

Проблема, с которой я столкнулся, была вызвана функцией, canUserAccessA()которая загружает объект A перед обновлением состояния объекта a. Это приводило к сбоям отслеживаемого объекта и меняло состояние объекта на Detached.

Решением было внести поправки, canUserAccessA()чтобы объект, который я загружал, не отслеживался. Функция AsNoTracking()должна вызываться при запросе контекста.

// User -> Receipt validation
private bool canUserAccessA(int aID)
{
    int userID = WebSecurity.GetUserId(User.Identity.Name);
    int aFound = db.Model.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();

    return (aFound > 0); //if aFound > 0, then return true, else return false.
}

По какой-то причине я не мог использовать .Find(aID)with, AsNoTracking()но это не имеет особого значения, поскольку я мог бы добиться того же, изменив запрос.

Надеюсь, это поможет кому-нибудь с подобной проблемой!

Крис Чизак
источник
10
немного аккуратнее и производительнее: if (db.As.AsNoTracking (). Any (x => x.aID == aID && x.UserID == userID))
Brent
11
Примечание: вам нужно using System.Data.Entity;использовать AsNoTracking().
Maxime
В моем случае обновление только полей, кроме идентификатора объекта, работало нормально: var entity = context.Find (entity_id); entity.someProperty = newValue; context.Entry (entity) .Property (x => x.someProperty) .IsModified = true; context.SaveChanges ();
Антон Лыхин
3
Огромная помощь. Я добавил .AsNoTracking () перед моим FirstOrDefault (), и это сработало.
coggicc 01
110

Что интересно:

_dbContext.Set<T>().AddOrUpdate(entityToBeUpdatedWithId);

Или, если вы все еще не универсальный:

_dbContext.Set<UserEntity>().AddOrUpdate(entityToBeUpdatedWithId);

похоже, решил мою проблему гладко.

guneysus
источник
1
Удивительно, это отлично сработало в моем сценарии, когда мне нужно было обновить записи во многих ко многим с помощью настраиваемой таблицы соединения в отключенном приложении. Даже при извлечении объекта из базы данных я получал ссылочные ошибки и т. Д. Я использовал «context.Entry (score) .State = System.Data.Entity.EntityState.Modified;» но это наконец сработало! Спасибо!!
firecape
5
Это работает. Все остальные предложения о подключении и использовании функции notracking не удались, поскольку я уже выполнял noTracking. Спасибо за решение.
Khainestar
3
Это сработало для меня при обновлении родительских и дочерних сущностей в одной единице работы . большое спасибо
Ian
55
Для всех, кто ищет, AddOrUpdateэто метод расширения в System.Data.Entity.Migrationsпространстве имен.
Ник
1
@Artyomska К сожалению, не знаю.
guneysus
15

Кажется, что объект, который вы пытаетесь изменить, не отслеживается правильно и поэтому не распознается как отредактированный, а вместо этого добавляется.

Вместо того, чтобы напрямую устанавливать состояние, попробуйте сделать следующее:

//db.Entry(aViewModel.a).State = EntityState.Modified;
db.As.Attach(aViewModel.a); 
db.SaveChanges();

Также я хотел бы предупредить вас, что ваш код содержит потенциальную уязвимость безопасности. Если вы используете сущность непосредственно в своей модели представления, вы рискуете, что кто-то может изменить содержимое сущности, добавив правильно названные поля в отправленную форму. Например, если пользователь добавил поле ввода с именем «A.FirstName» и сущность содержала такое поле, то значение будет привязано к модели просмотра и сохранено в базе данных, даже если пользователю не разрешено изменить это при нормальной работе приложения. ,

Обновить:

Чтобы преодолеть упомянутую ранее уязвимость системы безопасности, никогда не следует раскрывать модель предметной области в качестве модели представления, а вместо этого следует использовать отдельную модель представления. Тогда ваше действие получит модель представления, которую вы можете сопоставить с моделью предметной области с помощью какого-либо инструмента сопоставления, такого как AutoMapper. Это защитит вас от изменения конфиденциальных данных пользователем.

Вот расширенное объяснение:

http://www.stevefenton.co.uk/Content/Blog/Date/201303/Blog/Why-You-Never-Expose-Your-Domain-Model-As-Your-MVC-Model/

Каспарс Озолс
источник
3
Привет, Каспарс, спасибо за вклад. Метод Attach выдает те же ошибки, что и в моем вопросе. Проблема в том, что функция canUserAccessA () загружает сущность, а также упомянутый выше CodeCaster. Но при этом я очень заинтересован в вашем предложении по поводу безопасности. Не могли бы вы подсказать, что мне делать, чтобы предотвратить такое поведение?
Chris Ciszak
Обновил свой ответ дополнительной информацией о том, как предотвратить уязвимость системы безопасности.
Kaspars Ozols
13

Попробуй это:

var local = yourDbContext.Set<YourModel>()
                         .Local
                         .FirstOrDefault(f => f.Id == yourModel.Id);
if (local != null)
{
  yourDbContext.Entry(local).State = EntityState.Detached;
}
yourDbContext.Entry(applicationModel).State = EntityState.Modified;
Касио Батиста Перейра
источник
11

для меня локальная копия была источником проблемы. это решило это

var local = context.Set<Contact>().Local.FirstOrDefault(c => c.ContactId == contact.ContactId);
                if (local != null)
                {
                    context.Entry(local).State = EntityState.Detached;
                }
надстройку Naan
источник
10

В моем случае у меня не было прямого доступа к контексту EF из моего приложения MVC.

Поэтому, если вы используете какой-то репозиторий для сохранения сущности, можно просто отсоединить явно загруженную сущность, а затем установить для привязанного EntityState значение Modified.

Пример (аннотации) кода:

MVC

public ActionResult(A a)
{
  A aa = repo.Find(...);
  // some logic
  repo.Detach(aa);
  repo.Update(a);
}

вместилище

void Update(A a)
{
   context.Entry(a).EntityState = EntityState.Modified;
   context.SaveChanges();
}

void Detach(A a)
{
   context.Entry(a).EntityState = EntityState.Detached;
}
Сфирот
источник
У меня это сработало, хотя я не стал использовать репозиторий для ссылки на состояния сущностей контекста.
Eckert
3

Я думал, что поделюсь своим опытом по этому поводу, хотя я чувствую себя немного глупо из-за того, что не понял раньше.

Я использую шаблон репозитория с экземплярами репо, введенными в мои контроллеры. Конкретные репозитории создают экземпляр моего ModelContext (DbContext), который длится время жизни репозитория, который IDisposableуправляется и удаляется контроллером.

Проблема для меня заключалась в том, что у меня есть измененная версия штампа и строки на моих объектах, поэтому я сначала получал их, чтобы сравнить с входящими заголовками. Конечно, это загружало и отслеживало объект, который впоследствии обновлялся.

Исправление заключалось в том, чтобы просто изменить репозиторий с создания контекста один раз в конструкторе на наличие следующих методов:

    private DbContext GetDbContext()
    {
        return this.GetDbContext(false);
    }


    protected virtual DbContext GetDbContext(bool canUseCachedContext)
    {
        if (_dbContext != null)
        {
            if (canUseCachedContext)
            {
                return _dbContext;
            }
            else
            {
                _dbContext.Dispose();
            }
        }

        _dbContext = new ModelContext();

        return _dbContext;
    }

    #region IDisposable Members

    public void Dispose()
    {
        this.Dispose(true);
    }

    protected virtual void Dispose(bool isDisposing)
    {
        if (!_isDisposed)
        {
            if (isDisposing)
            {
                // Clear down managed resources.

                if (_dbContext != null)
                    _dbContext.Dispose();
            }

            _isDisposed = true;
        }
    }

    #endregion

Это позволяет методам репозитория заново обновлять свой экземпляр контекста при каждом использовании путем вызова GetDbContextили использовать предыдущий экземпляр, если они того пожелают, указав true.

Люк Пуплетт
источник
2

Я добавил этот ответ только потому, что проблема объясняется на основе более сложного шаблона данных, и мне было трудно понять здесь.

Я создал довольно простое приложение. Эта ошибка произошла в действии Edit POST. Действие приняло ViewModel в качестве входного параметра. Причина использования ViewModel заключалась в том, чтобы произвести некоторые вычисления перед сохранением записи.

После того, как действие прошло проверку, например if(ModelState.IsValid), я совершил ошибку, проецируя значения из ViewModel в совершенно новый экземпляр Entity. Я думал, что мне придется создать новый экземпляр для хранения обновленных данных, а затем сохранить такой экземпляр.

Позже я понял, что мне нужно прочитать запись из базы данных:

Student student = db.Students.Find(s => s.StudentID == ViewModel.StudentID);

и обновил этот объект. Теперь все работает.

Celdor
источник
2

У меня была проблема с local var, и я просто отсоединил ее вот так:

if (ModelState.IsValid)
{
    var old = db.Channel.Find(channel.Id);
    if (Request.Files.Count > 0)
    {
        HttpPostedFileBase objFiles = Request.Files[0];
        using (var binaryReader = new BinaryReader(objFiles.InputStream))
        {
            channel.GateImage = binaryReader.ReadBytes(objFiles.ContentLength);
        }

    }
    else
        channel.GateImage = old.GateImage;
    var cat = db.Category.Find(CatID);
    if (cat != null)
        channel.Category = cat;
    db.Entry(old).State = EntityState.Detached; // just added this line
    db.Entry(channel).State = EntityState.Modified;
    await db.SaveChangesAsync();
    return RedirectToAction("Index");
}
return View(channel);

Причины проблем загруженных объектов с одним и тем же ключом, поэтому сначала мы отсоединим этот объект и выполним обновление, чтобы избежать конфликта между двумя объектами с одним и тем же ключом.

lvl4fi4
источник
@Artjom B Причины проблем с загруженными объектами с одним и тем же ключом, поэтому сначала мы отсоединим этот объект и выполним обновление, чтобы избежать конфликта между двумя объектами с одним и тем же ключом
lvl4fi4
2

У меня была аналогичная проблема, после исследования в течение 2-3 дней обнаружил, что ".AsNoTracking" должен быть удален, поскольку EF не отслеживает изменения и предполагает, что изменений нет, если объект не прикреплен. Также, если мы не используем .AsNoTracking, EF автоматически знает, какой объект нужно сохранить / обновить, поэтому нет необходимости использовать Attach / Added.

Prem
источник
2

Используйте то, AsNoTracking()где вы получаете свой запрос.

  var result = dbcontext.YourModel.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();
Абдус Салам Азад
источник
2

Я столкнулся с этой ошибкой, когда

  • два метода, A и B, в одном контроллере использовали один и тот же экземпляр ApplicationDbContext и
  • метод A вызвал метод B
    private ApplicationDbContext db;
    // api methods
    public JsonResult methodA(string id){
        Resource resource = db.Resources.Find(id);
        db.Entry(resource).State = EntityState.Modified;
        db.SaveChanges();
        return methodB()
    }

    public JsonResult methodB(string id){
        Resource resource = db.Resources.Find(id);
        db.Entry(resource).State = EntityState.Modified;
        db.SaveChanges();
        return new JsonResult();
    }

Я изменил метод B, чтобы он имел оператор using и полагался только на локальный db2 . После:

    private ApplicationDbContext db;    
    // api methods    
    public JsonResult methodA(string id){
        Resource resource = db.Resources.Find(id);
        db.Entry(resource).State = EntityState.Modified;
        db.SaveChanges();
        return methodB()
    }

    public JsonResult methodB(string id){
        using (var db2 = new ApplicationDbContext())
        {
            Resource resource = db2.Resources.Find(id);
            db2.Entry(resource).State = EntityState.Modified;
            db2.SaveChanges();
        }
        return new JsonResult();
    }
colbybhearn
источник
1

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

В моем случае у меня был класс, который принимал контекст под названием ContextService:

public class ContextService : IDisposable
{
    private Context _context;

    public void Dispose()
    {
        _context.Dispose();
    }
    public ContextService(Context context)
    {
        _context = context;
    }
//... do stuff with the context

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

        public void UpdateEntity(MyEntity myEntity, ICollection<int> ids)
        {
            var item = _context.Entry(myEntity);
            item.State = EntityState.Modified;
            item.Collection(x => x.RelatedEntities).Load();
            myEntity.RelatedEntities.Clear();
            foreach (var id in ids)
            {
                myEntity.RelatedEntities.Add(_context.RelatedEntities.Find(id));
            }
            _context.SaveChanges();
        }

Все было в порядке, проблема была в моем контроллере, на котором я инициализировал службу. Изначально мой контроллер выглядел так:

    private static NotificationService _service = 
        new NotificationService(new NotificationContext());
    public void Dispose()
    {
    }

Я изменил его на это, и ошибка исчезла:

    private static NotificationService _service;
    public TemplateController()
    {
        _service = new NotificationService(new NotificationContext());
    }
    public void Dispose()
    {
        _service.Dispose();
    }
Джаред Бич
источник
1

Эта проблема также может рассматриваться во время ViewModelдля EntityModelкартирования (с помощью AutoMapper, и т.д.) и пытается включать в себя context.Entry().Stateи context.SaveChanges()такой блок с использованием , как показано ниже будет решить эту проблему. Имейте в виду, что context.SaveChanges()метод используется два раза, а не сразу после, if-blockпоскольку он также должен быть в блоке using.

public void Save(YourEntity entity)
{
    if (entity.Id == 0)
    {
        context.YourEntity.Add(entity);
        context.SaveChanges();
    }
    else
    {
        using (var context = new YourDbContext())
        {
            context.Entry(entity).State = EntityState.Modified;
            context.SaveChanges(); //Must be in using block
        }
    }            
}

Надеюсь это поможет...

Мурат Йылдыз
источник
1

Вот что я сделал в аналогичном случае.

Эта ситуация означает, что такая же сущность уже существовала в контексте, поэтому следующее может помочь

Сначала проверьте из ChangeTracker, находится ли объект в контексте

var trackedEntries=GetContext().ChangeTracker.Entries<YourEntityType>().ToList();

var isAlreadyTracked =
                    trackedEntries.Any(trackedItem => trackedItem.Entity.Id ==myEntityToSave.Id);

Если он существует

  if (isAlreadyTracked)
            {
                myEntityToSave= trackedEntries.First(trackedItem => trackedItem.Entity.Id == myEntityToSave.Id).Entity;
            } 

else
{
//Attach or Modify depending on your needs
}
erhan355
источник
1

Мне удается исправить проблему, обновив состояние. когда вы запускаете поиск или любую другую операцию запроса в той же записи, состояние было обновлено с измененным, поэтому нам нужно установить статус на Отключено, тогда вы можете запустить изменение обновления

     ActivityEntity activity = new ActivityEntity();
      activity.name="vv";
    activity.ID = 22 ; //sample id
   var savedActivity = context.Activities.Find(22);

            if (savedActivity!=null)
            {
                context.Entry(savedActivity).State = EntityState.Detached;
                context.SaveChanges();

                activity.age= savedActivity.age;
                activity.marks= savedActivity.marks; 

                context.Entry(activity).State = EntityState.Modified;
                context.SaveChanges();
                return activity.ID;
            }
Вира Индуваси
источник
1

Решаю эту проблему блоком "использующий"

using (SqlConnection conn = new SqlConnection(connectionString))

    {

       // stuff to do with data base
    }

    // or if you are using entity framework 
    using (DataBaseEntity data = new DataBaseEntity)
{

    }

Вот откуда я понял https://social.msdn.microsoft.com/Forums/sqlserver/es-ES/b4b350ba-b0d5-464d-8656-8c117d55b2af/problema-al-modificar-en-entity-framework?forum = vcses на испанском (ищите второй ответ)

Сузуме
источник
просто быть осторожными и использовать только 1 экземпляр Conexion базы данных, особенно , если вы используете рамки сущности , если вы не сделаете это , вы получите ошибку Entity Framework Объект объекта не можете ссылаться несколькими экземплярами IEntityChangeTracker
Сузум
1

вы можете использовать добавленный метод, например;

_dbContext.Entry(modelclassname).State = EntityState.Added;

но во многих случаях, если вы хотите использовать более одной модели в то время, это не сработает, потому что объект уже прикреплен к другому объекту. Итак, в это время вы можете использовать метод ADDOrUpdate Entity Migration, который просто переносит объект из одного в другой, и в результате вы не получите никаких ошибок.

_dbContext.Set<modelclassname>().AddOrUpdate(yourmodel);
Михир доши
источник
0

Очистить все состояние

dbContextGlobalERP.ChangeTracker.Entries (). Где (e => e.Entity! = null) .ToList (). ForEach (e => e.State = EntityState.Detached);

xxxsenatorxxx
источник