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


121

Коротше кажучи, виняток закидається під час розгортання моделі обгортки та зміни стану одного запису на "Модифікований". Перед тим, як змінити стан, стан встановлено на "Детально", але виклик 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? Оскільки ваша організація надходить із запиту на публікацію, її не слід відслідковувати за поточним контекстом, я думаю, що вона вважає, що ви намагаєтесь додати елемент із наявним ідентифікатором
Réda Mattar

Я спробував це, і результат точно такий же :( З якоїсь причини контекст вважає, що я створюю новий елемент, але я просто оновлюю існуючий ...
Кріс Цізак

Я перевіряю стан "a" перед тим, як помилка буде видалена, і стан цього об'єкта "Detached", але виклик db.As.Attach (aViewModel.a) видає точно таке ж повідомлення? Будь-які ідеї?
Кріс Цішак

5
Я щойно побачив ваше оновлення, як ви налаштували контекстну область життя? Це за запитом? Якщо dbекземпляр однаковий між вашими двома діями, він може пояснити вашу проблему, оскільки ваш елемент завантажується методом GET (потім відстежується контекстом), і він може не розпізнавати той, який використовується у вашому методі POST, як суб'єкт, отриманий раніше .
Реда Маттар

1
Чи canUserAccessA()завантажує суб'єкт господарювання безпосередньо або як відношення іншої організації?
CodeCaster

Відповіді:


154

Проблема вирішена!

Attachметод може потенційно допомогти комусь, але це не допоможе в цій ситуації, оскільки документ вже відстежувався під час завантаження у функції контролера 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.
}

Деякий використання не змогла причину I .Find(aID)з , AsNoTracking()але це на самому справі не має значення , як я міг би досягти того ж, змінивши запит.

Сподіваюся, це допоможе будь-кому із подібною проблемою!


10
трохи акуратніше і ефективніше: якщо (db.As.AsNoTracking (). Будь-який (x => x.aID == aID && x.UserID == userID))
Brent

11
Примітка: вам потрібно using System.Data.Entity;скористатися AsNoTracking().
Максим

У моєму випадку оновлення лише полів, крім ідентифікатора сутності, спрацювало нормально: var entit = context.Find (entit_id); entit.someProperty = newValue; контекст.вступ (сутність) .властивість (x => x.someProperty) .IsModified = вірно; context.SaveChanges ();
Антон Лихін

3
Масова допомога. Я додав .AsNoTracking () перед моїм FirstOrDefault (), і він працював.
coggicc

110

Цікаво:

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

Або якщо ви все ще не є загальним:

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

здається, вирішив мою проблему гладко.


1
Дивовижно, що в моєму сценарії працювало ідеально, коли мені потрібно було оновити записи у багатьох до багатьох за допомогою спеціальної таблиці приєднання у відключеному додатку. Навіть із суттю, схопленим із бази даних, я отримував референтні помилки тощо. Я використовував "контекст. Введення (оцінка). Держава = System.Data.Entity.EntityState.Modified;" але це нарешті спрацювало! Дякую!!
firecape

5
Це працює. Усі інші пропозиції щодо приєднання та використання Notracking не вдалися, оскільки я вже робив noTracking. Дякую за рішення.
Хайнестар

3
Це працювало для мене під час оновлення батьківських та дочірніх організацій в межах однієї одиниці роботи . велике спасибі
Ян

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
Привіт Kaspars, дякую за вклад. Метод Attach видає ті самі помилки, що й згадка в моєму запитанні. Проблема полягає в тому, що функція canUserAccessA () завантажує сутність, а також CodeCaster, помічене вище. Але кажу, що мене дуже цікавить ваша пропозиція щодо безпеки. Чи можете ви підказати, що мені робити, щоб запобігти такій поведінці?
Кріс Цішак

Оновив мою відповідь додатковою інформацією про те, як запобігти вразливості безпеки.
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;
                }

10

Моя справа полягала в тому, що я не мав прямого доступу до контексту EF з мого додатку MVC.

Отже, якщо ви використовуєте якесь сховище для збереження сутності, можливо, буде просто від'єднати явно завантажений об'єкт і потім встановити прив’язаний EntityState до модифікованого.

Зразок (реферат) коду:

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

Це працювало для мене, хоча я не намагався використовувати сховище для посилання на контекстні стани сутності.
Еккерт

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


2

Цю відповідь я додав лише тому, що проблема пояснюється на основі більш складної структури даних, і мені було важко зрозуміти тут.

Я створив досить просту програму. Ця помилка сталася під час дії редагування POST. Дія прийняла ViewModel як вхідний параметр. Причиною використання ViewModel було зробити деякий розрахунок перед тим, як запис був збережений.

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

Пізніше я зрозумів, що мені потрібно було прочитати запис із бази даних:

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

і оновив цей об’єкт. Все працює зараз.


2

У мене була ця проблема з локальним 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);

Проблемні причини завантажених об’єктів з одним ключем, тому спочатку ми від'єднаємо цей об’єкт і зробимо оновлення, щоб уникнути конфлікту між двома об’єктами з одним ключем


@Artjom B Проблема причин завантажених об'єктів з тим самим ключем, тому спочатку ми від'єднаємо цей об’єкт і зробимо оновлення, щоб уникнути конфлікту між двома об’єктами з одним ключем
lvl4fi4

2

У мене була подібна проблема, після зондування протягом 2-3 днів було знайдено ".AsNoTracking", оскільки EF не відстежує зміни та передбачає, що змін немає, якщо об'єкт не доданий. Крім того, якщо ми не використовуємо .AsNoTracking, EF автоматично знає, який об’єкт зберегти / оновити, тому немає необхідності використовувати Attach / Added.


2

Використовуйте, AsNoTracking()де ви отримуєте запит.

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

2

Я зіткнувся з цією помилкою де

  • два методи 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, щоб мати оператор, що використовує, і покладатися тільки на локальний 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();
    }

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як це має бути і при використанні блоку.

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
}

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 екземпляр спільноти баз даних, особливо якщо ви використовуєте структуру сутності, якщо цього не зробите, ви отримаєте помилку Entity Framework. Об'єкт сутності не може бути посилається на кілька примірників IEntityChangeTracker
Suzume

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.