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

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

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

Model

// 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" не удалось, поскольку другой объект тот же тип уже имеет такое же значение первичного ключа. Это может произойти, когда используя метод "Прикрепить" или устанавливая состояние объекта для "Без изменений" или "Модифицировано" , если любые объекты на графике имеют противоречивые значения ключа. Это может быть связано с тем, что некоторые объекты являются новыми и еще не получили значения ключей базы данных. В этом случае используйте метод "Добавить" или "Добавлено" состояние объекта для отслеживания графика и затем установите состояние не новых объектов в "Без изменений" или "Модифицировано" как необходимо.

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

Ответ 1

Проблема SOLVED!

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

Проблема, с которой я столкнулся, вызвана функцией 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) с AsNoTracking(), но это не имеет большого значения, поскольку я мог бы добиться того же, изменив запрос.

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

Ответ 2

Интересно:

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

Или если вы все еще не являетесь общим:

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

похоже, решительно решает мою проблему.

Ответ 3

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

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

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

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

Update:

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

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

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

Ответ 4

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

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

Пример (абстрактный) код:

MVC

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

Repository

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

void Detach(A a)
{
   context.Entry(a).EntityState = EntityState.Detached;
}

Ответ 5

Попробуйте следующее:

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;

Ответ 6

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

var local = context.Set<Contact>().Local.FirstOrDefault(c => c.ContactId == contact.ContactId);
                if (local != null)
                {
                    context.Entry(local).State = EntityState.Detached;
                }

Ответ 7

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

Я использую шаблон репозитория с экземплярами repo, введенными в мои контроллеры. Конкретные репозитории создают экземпляр моего 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.

Ответ 8

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

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

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

То, что я понял позже, заключалось в том, что мне пришлось прочитать запись из базы данных:

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

и обновил этот объект. Все работает сейчас.

Ответ 9

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

Ответ 10

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

  var result = dbcontext.YourModel.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();

Ответ 11

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

В моем случае у меня был класс, который принял контекст под названием 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();
    }

Ответ 12

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

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

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

Ответ 13

Вот что я сделал в подобном случае.

Это sitatuation означает, что тот же объект уже существует в контексте. Таким образом, помощь может помочь

Первая проверка из 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
}

Ответ 14

У меня была эта проблема с локальным 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);

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

Ответ 15

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

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

Ответ 16

Я решаю эту проблему с помощью блока "using"

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 на испанском (ищите второй ответ)

Ответ 17

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

  • два метода, A & B, в одном контроллере оба использовали один и тот же экземпляр ApplicationDbContext, и
  • метод А называется методом Б
    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();
    }

Ответ 18

using (var dbE = new HomologacionContext())
            {
                Empresa empNombre = dbE.Empresas.Where(e => e.Nombre.ToLower() == empresa.Nombre.ToLower()).FirstOrDefault();
                if (empNombre != null)
                {
                    if (empNombre.NitEmpresa != empresa.NitEmpresa)
                    {
                        ViewBag.Existe = "Existe una empresa con ese nombre";
                        return View(empresa);
                    }
                }
            }
db.Entry(empresa).State = EntityState.Modified;
                db.SaveChanges();
                return RedirectToAction("Index");`

Ответ 19

возможно, объект, который читается с db, затем попробуйте сохранить в db несколько раз одним и тем же контекстом db. использовать EntityState.Added вместо

попробовать:

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

Ответ 20

У меня было такое же сообщение исключения, указанное в вопросе, работающее с отключенным контекстом под EF6.

Следуя инструкции сообщения, я буквально устанавливаю состояние сущности дважды:

if (entityWithState.EntityState == EntityState.Modified)
{
    context.Entry(item).State = EntityState.Added;
    context.Entry(item).State = EntityState.Modified;
}

Исправлена ​​проблема для меня и очень противоречива, поэтому, возможно, это поможет кому-то.