ASP.NET MVC - Melampirkan entitas jenis 'MODELNAME' gagal karena entitas lain dari jenis yang sama sudah memiliki nilai kunci utama yang sama

122

Singkatnya, pengecualian dilemparkan selama model pembungkus POSTing dan mengubah status satu entri menjadi 'Dimodifikasi'. Sebelum mengubah status, status disetel ke 'Terpisah' tetapi memanggil Attach () tidak memunculkan kesalahan yang sama. Saya menggunakan EF6.

Silakan temukan kode saya di bawah ini (nama model telah diubah agar lebih mudah dibaca)

Model

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

Kontroler

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

Seperti yang ditunjukkan di atas garis

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

melempar pengecualian:

Gagal melampirkan entitas jenis 'A' karena entitas lain dengan jenis yang sama sudah memiliki nilai kunci utama yang sama. Hal ini dapat terjadi saat menggunakan metode 'Lampirkan' atau menyetel status entitas menjadi 'Tidak Berubah' atau 'Dimodifikasi' jika ada entitas dalam grafik yang memiliki nilai kunci yang bertentangan. Ini mungkin karena beberapa entitas baru dan belum menerima nilai kunci yang dihasilkan database. Dalam kasus ini, gunakan metode 'Tambah' atau status entitas 'Ditambahkan' untuk melacak grafik dan kemudian setel status entitas bukan baru ke 'Tidak Berubah' atau 'Dimodifikasi' yang sesuai.

Adakah yang melihat ada yang salah dalam kode saya atau memahami dalam keadaan apa kesalahan tersebut akan terjadi saat mengedit model?

Chris Ciszak
sumber
Sudahkah Anda mencoba melampirkan entitas Anda sebelum menyetel EntityState? Karena entitas Anda berasal dari permintaan posting, itu tidak boleh dilacak oleh konteks saat ini, saya rasa itu menganggap bahwa Anda mencoba menambahkan item dengan ID yang ada
Réda Mattar
Saya sudah mencoba yang ini dan hasilnya persis sama :( Untuk beberapa alasan konteks mengira saya membuat item baru, tetapi saya hanya memperbarui yang sudah ada ...
Chris Ciszak
Saya memeriksa status 'a' sebelum kesalahan dilemparkan dan status objek ini adalah 'Detached' tetapi memanggil db.As.Attach (aViewModel.a) akan memunculkan pesan yang persis sama? Ada ide?
Chris Ciszak
5
Saya baru saja melihat pembaruan Anda, bagaimana Anda mengatur lingkup konteks seumur hidup? Apakah ini sesuai permintaan? Jika dbinstance sama di antara dua tindakan Anda, ini dapat menjelaskan masalah Anda, karena item Anda dimuat dengan metode GET (kemudian dilacak oleh konteksnya), dan mungkin tidak mengenali yang ada di metode POST Anda sebagai entitas yang diambil sebelumnya .
Réda Mattar
1
Apakah canUserAccessA()memuat entitas secara langsung atau sebagai relasi dari entitas lain?
CodeCaster

Jawaban:

155

Masalah TERSELESAIKAN!

AttachMetode berpotensi dapat membantu seseorang tetapi tidak akan membantu dalam situasi ini karena dokumen sudah dilacak saat dimuat dalam fungsi pengontrol Edit GET. Lampirkan akan menampilkan kesalahan yang persis sama.

Masalah yang saya temui di sini disebabkan oleh fungsi canUserAccessA()yang memuat entitas A sebelum memperbarui status objek a. Ini mengacaukan entitas yang dilacak dan itu mengubah status objek menjadi Detached.

Solusinya adalah mengubah canUserAccessA()sehingga objek yang saya muat tidak akan terlacak. Fungsi AsNoTracking()harus dipanggil saat menanyakan konteks.

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

Untuk beberapa alasan saya tidak dapat menggunakan .Find(aID)dengan AsNoTracking()tetapi tidak terlalu penting karena saya dapat mencapai hal yang sama dengan mengubah kueri.

Semoga ini akan membantu siapa pun dengan masalah serupa!

Chris Ciszak
sumber
10
sedikit lebih rapi dan lebih berkinerja: if (db.As.AsNoTracking (). Any (x => x.aID == aID && x.UserID == userID))
Brent
11
Catatan: Anda perlu using System.Data.Entity;menggunakan AsNoTracking().
Maxime
Dalam kasus saya hanya memperbarui bidang kecuali entitas id bekerja dengan baik: var entity = context.Find (entity_id); entity.someProperty = newValue; konteks.Entry (entitas) .Property (x => x.someProperty) .IsModified = true; context.SaveChanges ();
Anton Lyhin
3
Bantuan Besar-besaran. Saya menambahkan .AsNoTracking () sebelum FirstOrDefault () saya dan berhasil.
coggicc
110

Menariknya:

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

Atau jika Anda masih tidak umum:

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

sepertinya menyelesaikan masalah saya dengan lancar.

guneysus
sumber
1
Luar biasa, itu bekerja sempurna dalam skenario saya di mana saya perlu memperbarui catatan dalam banyak ke banyak dengan tabel gabungan khusus di aplikasi terputus. Bahkan dengan entitas yang diambil dari database, saya mendapatkan kesalahan referensial, dll. Saya menggunakan "context.Entry (skor) .State = System.Data.Entity.EntityState.Modified;" tapi ini akhirnya berhasil! Terima kasih!!
firecape
5
Ini bekerja. Semua saran lain tentang melampirkan dan menggunakan notracking gagal karena saya sudah melakukan noTracking. Terima kasih atas solusinya.
Khainestar
3
Ini berfungsi untuk saya saat memperbarui entitas induk dan anak dalam unit kerja yang sama . terima kasih banyak
Ian
55
Bagi siapa pun yang mencari, AddOrUpdateadalah metode ekstensi di System.Data.Entity.Migrationsnamespace.
Nick
1
@Artyomska Sayangnya saya tidak tahu.
guneysus
15

Tampaknya entitas yang Anda coba ubah tidak dilacak dengan benar dan oleh karena itu tidak dikenali sebagai diedit, melainkan ditambahkan.

Alih-alih langsung menyetel status, coba lakukan hal berikut:

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

Selain itu, saya ingin memperingatkan Anda bahwa kode Anda mengandung potensi kerentanan keamanan. Jika Anda menggunakan entitas secara langsung dalam model tampilan Anda, maka Anda berisiko bahwa seseorang dapat mengubah konten entitas dengan menambahkan bidang yang diberi nama dengan benar dalam formulir yang dikirimkan. Misalnya, jika pengguna menambahkan kotak input dengan nama "A.FirstName" dan entitas berisi bidang tersebut, maka nilai akan terikat ke viewmodel dan disimpan ke database bahkan jika pengguna tidak diizinkan untuk mengubahnya dalam operasi normal aplikasi .

Memperbarui:

Untuk mengatasi kerentanan keamanan yang disebutkan sebelumnya, Anda tidak boleh mengekspos model domain Anda sebagai model tampilan, tetapi gunakan model tampilan terpisah sebagai gantinya. Kemudian tindakan Anda akan menerima viewmodel yang dapat Anda petakan kembali ke model domain menggunakan beberapa alat pemetaan seperti AutoMapper. Ini akan membuat Anda aman dari pengguna yang mengubah data sensitif.

Berikut penjelasan lengkapnya:

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

Kaspars Ozols
sumber
3
Halo Kaspars, terima kasih atas masukannya. Metode Lampirkan menghasilkan kesalahan yang sama seperti yang disebutkan dalam pertanyaan saya. Masalahnya adalah bahwa fungsi canUserAccessA () memuat entitas serta CodeCaster yang disebutkan di atas. Tetapi mengatakan bahwa saya sangat tertarik dengan saran Anda tentang keamanan. Bisakah Anda menyarankan apa yang harus saya lakukan untuk mencegah perilaku seperti itu?
Chris Ciszak
Memperbarui jawaban saya dengan info tambahan tentang cara mencegah kerentanan keamanan.
Kaspars Ozols
13

Coba ini:

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;
Cássio Batista Pereira
sumber
11

bagi saya salinan lokal adalah sumber masalahnya. ini menyelesaikannya

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

Kasus saya adalah saya tidak memiliki akses langsung ke konteks EF dari aplikasi MVC saya.

Jadi, jika Anda menggunakan beberapa jenis repositori untuk persistensi entitas, mungkin lebih tepat untuk melepaskan entitas yang dimuat secara eksplisit dan kemudian menyetel EntityState yang terikat ke Dimodifikasi.

Contoh kode (abstrak):

MVC

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

Gudang

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

void Detach(A a)
{
   context.Entry(a).EntityState = EntityState.Detached;
}
sephirot
sumber
Ini berfungsi untuk saya, meskipun saya tidak repot-repot menggunakan repositori untuk mereferensikan status entitas konteks.
Eckert
3

Saya pikir saya akan membagikan pengalaman saya tentang yang satu ini, meskipun saya merasa agak konyol karena tidak menyadarinya lebih awal.

Saya menggunakan pola repositori dengan instance repo yang dimasukkan ke dalam pengontrol saya. Repositori konkret memberi contoh ModelContext saya (DbContext) yang bertahan seumur hidup repositori, yang IDisposabledan dibuang oleh pengontrol.

Masalah bagi saya adalah saya memiliki versi cap dan baris yang dimodifikasi pada entitas saya, jadi saya mendapatkannya terlebih dahulu untuk membandingkan dengan header masuk. Tentu saja, ini memuat dan melacak entitas yang kemudian diperbarui.

Perbaikannya hanya dengan mengubah repositori dari memperbarui konteks sekali dalam konstruktor menjadi memiliki metode berikut:

    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

Hal ini memungkinkan metode repositori untuk memperbarui instance konteksnya pada setiap penggunaan dengan memanggil GetDbContext, atau menggunakan instance sebelumnya jika mereka menginginkannya dengan menetapkan true.

Luke Puplett
sumber
2

Saya telah menambahkan jawaban ini hanya karena masalahnya dijelaskan berdasarkan pola data yang lebih kompleks dan saya merasa sulit untuk memahami di sini.

Saya membuat aplikasi yang cukup sederhana. Kesalahan ini terjadi di dalam tindakan Edit POST. Tindakan tersebut menerima ViewModel sebagai parameter masukan. Alasan menggunakan ViewModel adalah untuk membuat beberapa kalkulasi sebelum record disimpan.

Setelah tindakan melewati validasi seperti if(ModelState.IsValid), kesalahan saya adalah memproyeksikan nilai dari ViewModel ke dalam instance Entitas yang benar-benar baru. Saya pikir saya harus membuat contoh baru untuk menyimpan data yang diperbarui dan kemudian menyimpan contoh tersebut.

Apa yang saya sadari kemudian adalah bahwa saya harus membaca catatan dari database:

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

dan memperbarui objek ini. Semuanya bekerja sekarang.

Celdor
sumber
2

Saya mengalami masalah ini dengan var lokal dan saya hanya melepaskannya seperti ini:

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

Masalah penyebab objek yang dimuat dengan Key yang sama, maka pertama-tama kita akan melepaskan objek tersebut dan melakukan update untuk menghindari konflik antara dua objek dengan Key yang sama.

lvl4fi4
sumber
@Artjom B Masalah penyebab objek yang dimuat dengan Key yang sama, jadi pertama-tama kita akan melepaskan objek tersebut dan melakukan update untuk menghindari konflik antara dua objek dengan Key yang sama
lvl4fi4
2

Saya mengalami masalah serupa, setelah menyelidiki selama 2-3 hari ditemukan ".AsNoTracking" harus dihapus karena EF tidak melacak perubahan dan menganggap tidak ada perubahan kecuali ada objek yang dilampirkan. Juga jika kita tidak menggunakan .AsNoTracking, EF secara otomatis mengetahui objek mana yang akan disimpan / diperbarui sehingga tidak perlu menggunakan Attach / Added.

Prem
sumber
2

Gunakan di AsNoTracking()mana Anda mendapatkan kueri Anda.

  var result = dbcontext.YourModel.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();
Abdus Salam Azad
sumber
2

Saya mengalami kesalahan ini di mana

  • dua metode, A & B, dalam satu pengontrol menggunakan instance yang sama dari ApplicationDbContext, dan
  • metode A disebut metode 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();
    }

Saya mengubah metode B agar memiliki pernyataan penggunaan dan hanya mengandalkan db2 lokal . Setelah:

    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
sumber
1

Mirip dengan apa yang dikatakan Luke Puplett, masalah dapat disebabkan oleh tidak tepat membuang atau menciptakan konteks Anda.

Dalam kasus saya, saya memiliki kelas yang menerima konteks yang disebut ContextService:

public class ContextService : IDisposable
{
    private Context _context;

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

Layanan konteks saya memiliki fungsi yang memperbarui entitas menggunakan objek entitas yang dipakai:

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

Semua ini baik-baik saja, pengontrol saya tempat saya menginisialisasi layanan adalah masalahnya. Pengontrol saya awalnya terlihat seperti ini:

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

Saya mengubahnya menjadi ini dan kesalahannya hilang:

    private static NotificationService _service;
    public TemplateController()
    {
        _service = new NotificationService(new NotificationContext());
    }
    public void Dispose()
    {
        _service.Dispose();
    }
Pantai Jared
sumber
1

Masalah ini juga dapat dilihat selama ViewModeluntuk EntityModelpemetaan (dengan menggunakan AutoMapper, dll) dan mencoba untuk memasukkan context.Entry().Statedan context.SaveChanges()sebuah seperti menggunakan blok seperti yang ditunjukkan di bawah ini akan memecahkan masalah. Harap diingat bahwa context.SaveChanges()metode ini digunakan dua kali daripada menggunakan setelah if-blockkarena harus menggunakan blok juga.

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

Semoga ini membantu...

Murat Yıldız
sumber
1

Inilah yang saya lakukan dalam kasus serupa.

Situasi itu berarti entitas yang sama telah ada dalam konteks. Jadi berikut ini bisa membantu

Pertama, periksa dari ChangeTracker jika entitas ada dalam konteks

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

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

Jika ada

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

else
{
//Attach or Modify depending on your needs
}
erhan355
sumber
1

Saya bisa memperbaiki masalah dengan memperbarui status. ketika Anda memicu pencarian atau operasi kueri lainnya pada catatan yang sama sate telah diperbarui dengan dimodifikasi sehingga kami perlu mengatur status ke Terpisah maka Anda dapat mengaktifkan perubahan pembaruan Anda

     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;
            }
Veera Induvasi
sumber
1

Saya mengatasi masalah ini dengan blok "menggunakan"

using (SqlConnection conn = new SqlConnection(connectionString))

    {

       // stuff to do with data base
    }

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

    }

Di sinilah saya mendapatkan ide https://social.msdn.microsoft.com/Forums/sqlserver/es-ES/b4b350ba-b0d5-464d-8656-8c117d55b2af/problema-al-modificar-en-entity-framework?forum = vcses dalam bahasa spanyol (cari jawaban kedua)

Suzume
sumber
Berhati-hatilah dan hanya gunakan 1 instance dari database conexion, khususnya jika Anda menggunakan framework entitas jika tidak melakukannya, Anda akan mendapatkan error Entity Framework Objek entitas tidak dapat direferensikan oleh beberapa instance IEntityChangeTracker
Suzume
1

Anda dapat menggunakan metode tambahan seperti;

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

tetapi dalam banyak kasus jika Anda ingin menggunakan lebih dari satu model pada saat itu, ini tidak akan berfungsi karena entitas sudah dilampirkan ke entitas lain. Jadi, saat itu Anda bisa menggunakan metode ADDOrUpdate Entity Migration yang hanya memigrasikan objek dari satu objek ke objek lain dan sebagai hasilnya Anda tidak akan mendapatkan error.

_dbContext.Set<modelclassname>().AddOrUpdate(yourmodel);
mihir doshi
sumber
0

Hapus semua Status

dbContextGlobalERP.ChangeTracker.Entries (). Di mana (e => e.Entity! = null) .ToList (). ForEach (e => e.State = EntityState.Detached);

xxxsenatorxxx
sumber