Hubungan tidak dapat diubah karena satu atau lebih properti kunci asing tidak dapat dibatalkan

192

Saya mendapatkan kesalahan ini ketika saya GetById () pada suatu entitas dan kemudian mengatur koleksi entitas anak ke daftar baru saya yang berasal dari tampilan MVC.

Operasi gagal: Hubungan tidak dapat diubah karena satu atau lebih properti kunci asing tidak dapat dibatalkan. Ketika perubahan dibuat untuk suatu hubungan, properti kunci asing terkait diatur ke nilai nol. Jika kunci asing tidak mendukung nilai nol, hubungan baru harus ditentukan, properti kunci asing harus diberikan nilai bukan nol lainnya, atau objek yang tidak terkait harus dihapus.

Saya tidak begitu mengerti baris ini:

Hubungan tidak dapat diubah karena satu atau lebih properti kunci asing tidak dapat dibatalkan.

Mengapa saya harus mengubah hubungan antara 2 entitas? Itu harus tetap sama sepanjang masa aplikasi secara keseluruhan.

Kode pengecualian terjadi adalah sederhana menetapkan kelas anak yang dimodifikasi dalam koleksi ke kelas induk yang ada. Ini diharapkan akan melayani untuk menghapus kelas anak, penambahan yang baru dan modifikasi. Saya akan berpikir Entity Framework menangani ini.

Baris kode dapat didistilasi ke:

var thisParent = _repo.GetById(1);
thisParent.ChildItems = modifiedParent.ChildItems();
_repo.Save();
jaffa
sumber
Saya menemukan jawaban saya beli menggunakan solusi # 2 dalam artikel di bawah ini, pada dasarnya saya buat menambahkan kunci primer ke tabel anak untuk referensi ke tabel induk (sehingga memiliki 2 kunci utama (kunci asing untuk tabel induk dan ID untuk tabel anak) c-sharpcorner.com/UploadFile/ff2f08/…
yougotiger
@jaffa, saya menemukan jawaban saya di sini stackoverflow.com/questions/22858491/…
antonio

Jawaban:

159

Anda harus menghapus item anak lama thisParent.ChildItemssatu per satu secara manual. Entity Framework tidak melakukan itu untuk Anda. Akhirnya tidak dapat memutuskan apa yang ingin Anda lakukan dengan item anak lama - jika Anda ingin membuangnya atau jika Anda ingin menyimpan dan menugaskannya ke entitas induk lainnya. Anda harus memberi tahu Kerangka Kerja keputusan Anda. Tetapi salah satu dari dua keputusan ini harus Anda buat karena entitas anak tidak dapat hidup sendiri tanpa referensi ke orang tua mana pun dalam basis data (karena batasan kunci asing). Pada dasarnya itulah yang dikatakan pengecualian.

Edit

Apa yang akan saya lakukan jika item anak dapat ditambahkan, diperbarui, dan dihapus:

public void UpdateEntity(ParentItem parent)
{
    // Load original parent including the child item collection
    var originalParent = _dbContext.ParentItems
        .Where(p => p.ID == parent.ID)
        .Include(p => p.ChildItems)
        .SingleOrDefault();
    // We assume that the parent is still in the DB and don't check for null

    // Update scalar properties of parent,
    // can be omitted if we don't expect changes of the scalar properties
    var parentEntry = _dbContext.Entry(originalParent);
    parentEntry.CurrentValues.SetValues(parent);

    foreach (var childItem in parent.ChildItems)
    {
        var originalChildItem = originalParent.ChildItems
            .Where(c => c.ID == childItem.ID && c.ID != 0)
            .SingleOrDefault();
        // Is original child item with same ID in DB?
        if (originalChildItem != null)
        {
            // Yes -> Update scalar properties of child item
            var childEntry = _dbContext.Entry(originalChildItem);
            childEntry.CurrentValues.SetValues(childItem);
        }
        else
        {
            // No -> It's a new child item -> Insert
            childItem.ID = 0;
            originalParent.ChildItems.Add(childItem);
        }
    }

    // Don't consider the child items we have just added above.
    // (We need to make a copy of the list by using .ToList() because
    // _dbContext.ChildItems.Remove in this loop does not only delete
    // from the context but also from the child collection. Without making
    // the copy we would modify the collection we are just interating
    // through - which is forbidden and would lead to an exception.)
    foreach (var originalChildItem in
                 originalParent.ChildItems.Where(c => c.ID != 0).ToList())
    {
        // Are there child items in the DB which are NOT in the
        // new child item collection anymore?
        if (!parent.ChildItems.Any(c => c.ID == originalChildItem.ID))
            // Yes -> It's a deleted child item -> Delete
            _dbContext.ChildItems.Remove(originalChildItem);
    }

    _dbContext.SaveChanges();
}

Catatan: Ini belum diuji. Diasumsikan bahwa koleksi item anak adalah tipe ICollection. (Saya biasanya memilikiIList dan kemudian kode terlihat sedikit berbeda.) Saya juga telah menghapus semua abstraksi repositori agar tetap sederhana.

Saya tidak tahu apakah itu solusi yang baik, tetapi saya percaya bahwa beberapa kerja keras di sepanjang garis ini harus dilakukan untuk mengurus semua jenis perubahan dalam koleksi navigasi. Saya juga akan senang melihat cara yang lebih mudah untuk melakukannya.

Slauma
sumber
Jadi bagaimana jika beberapa hanya diubah? Apakah itu berarti saya masih harus menghapusnya dan menambahkannya lagi?
jaffa
@ Jon: Tidak, Anda juga dapat memperbarui item yang ada tentunya. Saya telah menambahkan contoh bagaimana saya mungkin akan memperbarui koleksi anak, lihat Edit bagian di atas.
Slauma
@Slauma: Lol, jika saya tahu Anda akan mengubah jawaban Anda, saya tidak akan menulis jawaban saya ...
Ladislav Mrnka
@Ladislav: Tidak, tidak, saya senang Anda menulis jawaban Anda sendiri. Sekarang setidaknya saya tahu itu tidak sepenuhnya omong kosong dan terlalu rumit apa yang saya lakukan di atas.
Slauma
1
Saya akan menambahkan kondisi ketika mengambil originalChildItem di foreach: ... Di mana (c => c.ID == childItem.ID && c.ID! = 0) jika tidak maka akan mengembalikan anak-anak yang baru ditambahkan jika childItem.ID == 0.
perfect_element
116

Alasan Anda menghadapi ini adalah karena perbedaan antara komposisi dan agregasi .

Dalam komposisi, objek anak dibuat ketika induk dibuat dan dihancurkan ketika induknya dihancurkan . Jadi masa hidupnya dikendalikan oleh orang tuanya. misalnya posting blog dan komentarnya. Jika sebuah posting dihapus, komentarnya harus dihapus. Tidak masuk akal memiliki komentar untuk kiriman yang tidak ada. Sama untuk pesanan dan barang pesanan.

Secara agregasi, objek anak dapat ada terlepas dari induknya . Jika induk dihancurkan, objek anak masih dapat ada, karena dapat ditambahkan ke orangtua yang berbeda nanti. misalnya: hubungan antara daftar putar dan lagu-lagu dalam daftar putar itu. Jika daftar putar dihapus, lagu-lagu tidak boleh dihapus. Mereka dapat ditambahkan ke daftar putar yang berbeda.

Cara Entity Framework membedakan hubungan agregasi dan komposisi adalah sebagai berikut:

  • Untuk komposisi: ia mengharapkan objek anak memiliki kunci primer komposit (ParentID, ChildID). Ini adalah desain karena ID anak-anak harus berada dalam ruang lingkup orang tua mereka.

  • Untuk agregasi: ia mengharapkan properti kunci asing di objek anak menjadi nullable.

Jadi, alasan Anda mengalami masalah ini adalah karena cara Anda menetapkan kunci utama di tabel anak Anda. Itu harus komposit, tetapi tidak. Jadi, Entity Framework melihat asosiasi ini sebagai agregasi, yang berarti, ketika Anda menghapus atau menghapus objek anak, itu tidak akan menghapus catatan anak. Itu hanya akan menghapus asosiasi dan menetapkan kolom kunci asing terkait ke NULL (sehingga catatan anak tersebut kemudian dapat dikaitkan dengan orangtua yang berbeda). Karena kolom Anda tidak mengizinkan NULL, Anda mendapatkan pengecualian yang Anda sebutkan.

Solusi:

1- Jika Anda memiliki alasan kuat untuk tidak ingin menggunakan kunci komposit, Anda perlu menghapus objek anak secara eksplisit. Dan ini bisa dilakukan lebih sederhana daripada solusi yang disarankan sebelumnya:

context.Children.RemoveRange(parent.Children);

2- Jika tidak, dengan menetapkan kunci utama yang tepat di tabel anak Anda, kode Anda akan terlihat lebih bermakna:

parent.Children.Clear();
Mosh
sumber
9
Saya menemukan penjelasan ini sangat membantu.
Booji Boy
7
Penjelasan yang bagus untuk komposisi vs agregasi dan bagaimana kerangka entitas terkait dengannya.
Chrysalis
# 1 adalah jumlah kode paling sedikit yang diperlukan untuk memperbaiki masalah ini. Terima kasih!
ryanulit
73

Ini masalah yang sangat besar. Apa yang sebenarnya terjadi dalam kode Anda adalah ini:

  • Anda memuat Parentdari database dan mendapatkan entitas terlampir
  • Anda mengganti koleksi anaknya dengan koleksi baru anak-anak yang terpisah
  • Anda menyimpan perubahan tetapi selama operasi ini semua anak dianggap sebagai tambahan karena EF sampai saat ini tidak mengetahuinya. Jadi EF mencoba untuk menetapkan null ke kunci asing anak-anak tua dan memasukkan semua anak baru => baris duplikat.

Sekarang solusinya benar-benar tergantung pada apa yang ingin Anda lakukan dan bagaimana Anda ingin melakukannya?

Jika Anda menggunakan ASP.NET MVC Anda dapat mencoba menggunakan UpdateModel atau TryUpdateModel .

Jika Anda hanya ingin memperbarui anak-anak yang ada secara manual, Anda dapat melakukan sesuatu seperti:

foreach (var child in modifiedParent.ChildItems)
{
    context.Childs.Attach(child); 
    context.Entry(child).State = EntityState.Modified;
}

context.SaveChanges();

Melampirkan sebenarnya tidak diperlukan (mengatur keadaan untuk Modifiedjuga akan melampirkan entitas) tapi saya suka karena membuat proses lebih jelas.

Jika Anda ingin memodifikasi yang sudah ada, hapus yang ada dan masukkan anak baru, Anda harus melakukan sesuatu seperti:

var parent = context.Parents.GetById(1); // Make sure that childs are loaded as well
foreach(var child in modifiedParent.ChildItems)
{
    var attachedChild = FindChild(parent, child.Id);
    if (attachedChild != null)
    {
        // Existing child - apply new values
        context.Entry(attachedChild).CurrentValues.SetValues(child);
    }
    else
    {
        // New child
        // Don't insert original object. It will attach whole detached graph
        parent.ChildItems.Add(child.Clone());
    }
}

// Now you must delete all entities present in parent.ChildItems but missing
// in modifiedParent.ChildItems
// ToList should make copy of the collection because we can't modify collection
// iterated by foreach
foreach(var child in parent.ChildItems.ToList())
{
    var detachedChild = FindChild(modifiedParent, child.Id);
    if (detachedChild == null)
    {
        parent.ChildItems.Remove(child);
        context.Childs.Remove(child); 
    }
}

context.SaveChanges();
Ladislav Mrnka
sumber
1
Tetapi ada komentar menarik tentang penggunaan .Clone(). Apakah Anda memiliki kasus dalam pikiran bahwa ChildItemmemiliki properti navigasi sub-anak lainnya? Tetapi dalam kasus itu, bukankah kita ingin agar seluruh sub-grafik melekat pada konteks karena kita akan berharap bahwa semua sub-anak adalah objek baru jika anak itu sendiri adalah baru? (Yah, mungkin berbeda dari model ke model, tetapi mari kita asumsikan kasus bahwa anak-anak "tergantung" dari anak seperti anak-anak tergantung dari orang tua.)
Slauma
Mungkin akan membutuhkan klon "pintar".
Ladislav Mrnka
1
Bagaimana jika Anda tidak ingin memiliki koleksi Anak dalam konteks Anda? http://stackoverflow.com/questions/20233994/do-i-need-to-create-a-dbset-for-every-table-so-that-i-can-persist-child-entitie
Kirsten Greed
1
parent.ChildItems.Remove (anak); context.Childs.Remove (anak); Penghapusan ganda ini diperbaiki dapat mengeluarkan, TERIMA KASIH. Mengapa kita perlu keduanya dihapus? Mengapa menghapus hanya dari orangtua. Anak-anak tidak menyukai karena anak hanya hidup sebagai anak?
Fernando Torres
40

Saya menemukan jawaban ini jauh lebih bermanfaat untuk kesalahan yang sama. Tampaknya EF tidak suka ketika Anda Hapus, itu lebih suka Hapus.

Anda dapat menghapus koleksi catatan yang dilampirkan ke catatan seperti ini.

order.OrderDetails.ToList().ForEach(s => db.Entry(s).State = EntityState.Deleted);

Dalam contoh tersebut, semua catatan Detail yang dilampirkan pada Pesanan memiliki Statusnya diatur ke Hapus. (Dalam persiapan untuk Menambahkan kembali Rincian yang diperbarui, sebagai bagian dari pembaruan Pesanan)

Greg Little
sumber
Saya percaya itu jawaban yang tepat.
desmati
solusi logis dan mudah.
sairfan
19

Saya tidak tahu mengapa dua jawaban lainnya begitu populer!

Saya percaya Anda benar dalam mengasumsikan kerangka kerja ORM harus menanganinya - setelah semua, itulah yang dijanjikan untuk diberikan. Kalau tidak, model domain Anda akan rusak oleh masalah kegigihan. NHibernate mengelola ini dengan senang hati jika Anda mengatur pengaturan kaskade dengan benar. Dalam Kerangka Entitas juga dimungkinkan, mereka hanya mengharapkan Anda untuk mengikuti standar yang lebih baik ketika menyiapkan model database Anda, terutama ketika mereka harus menyimpulkan apa yang harus dilakukan cascading:

Anda harus mendefinisikan hubungan orangtua - anak dengan benar dengan menggunakan hubungan pengidentifikasi ".

Jika Anda melakukan ini, Entity Framework tahu objek anak diidentifikasi oleh orang tua, dan oleh karena itu objek itu harus berupa situasi "kaskade-hapus-anak yatim".

Selain yang di atas, Anda mungkin perlu (dari pengalaman NHibernate)

thisParent.ChildItems.Clear();
thisParent.ChildItems.AddRange(modifiedParent.ChildItems);

alih-alih mengganti daftar sepenuhnya.

MEMPERBARUI

Komentar @ Slauma mengingatkan saya bahwa entitas yang terpisah adalah bagian lain dari keseluruhan masalah. Untuk mengatasinya, Anda dapat mengambil pendekatan menggunakan pengikat model khusus yang membangun model Anda dengan mencoba memuatnya dari konteks. Posting blog ini menunjukkan contoh apa yang saya maksud.

Andre Luus
sumber
Pengaturan sebagai pengidentifikasi hubungan tidak akan membantu di sini karena skenario dalam pertanyaan harus berurusan dengan entitas yang terpisah ( "daftar baru saya yang berasal dari tampilan MVC" ). Anda masih harus memuat anak-anak asli dari DB, menemukan item yang dihapus dalam koleksi berdasarkan koleksi yang terlepas dan kemudian menghapus dari DB. Satu-satunya perbedaan adalah bahwa dengan hubungan identifikasi Anda dapat meneleponparent.ChildItems.Remove alih-alih _dbContext.ChildItems.Remove. Masih (EF <= 6) tidak ada dukungan bawaan dari EF untuk menghindari kode panjang seperti yang ada di jawaban lain.
Slauma
Saya mengerti maksud Anda. Namun, saya percaya dengan pengikat model khusus yang memuat entitas dari konteks atau mengembalikan contoh baru pendekatan di atas akan berfungsi. Saya akan memperbarui jawaban saya untuk menyarankan solusi itu.
Andre Luus
Ya, Anda bisa menggunakan pengikat model tetapi Anda harus melakukan hal-hal dari jawaban lain di pengikat model sekarang. Itu hanya memindahkan masalah dari repo / lapisan layanan ke binder model. Setidaknya, saya tidak melihat penyederhanaan yang nyata.
Slauma
Penyederhanaan adalah penghapusan otomatis entitas yatim. Yang Anda butuhkan dalam binder model adalah setara dengan generikreturn context.Items.Find(id) ?? new Item()
Andre Luus
Umpan balik yang bagus untuk tim EF, tetapi solusi yang Anda sarankan tidak menyelesaikan apa pun di tanah EF sayangnya.
Chris Moschini
9

Jika Anda menggunakan AutoMapper dengan Entity Framework pada kelas yang sama, Anda mungkin menemukan masalah ini. Misalnya jika kelas Anda

class A
{
    public ClassB ClassB { get; set; }
    public int ClassBId { get; set; }
}

AutoMapper.Map<A, A>(input, destination);

Ini akan mencoba menyalin kedua properti. Dalam hal ini, ClassBId tidak dapat dibatalkan. Karena AutoMapper akan menyalin destination.ClassB = input.ClassB;ini akan menyebabkan masalah.

Setel AutoMapper Anda ke Abaikan ClassBproperti.

 cfg.CreateMap<A, A>()
     .ForMember(m => m.ClassB, opt => opt.Ignore()); // We use the ClassBId
jsgoupil
sumber
Saya menghadapi masalah serupa dengan AutoMapper, tetapi ini tidak berhasil untuk saya :( Lihat stackoverflow.com/q/41430679/613605
J86
4

Saya baru saja mengalami kesalahan yang sama. Saya memiliki dua tabel dengan hubungan anak induk, tetapi saya mengonfigurasi "pada penghapusan kaskade" pada kolom kunci asing di definisi tabel dari tabel anak. Jadi ketika saya secara manual menghapus baris induk (melalui SQL) di dalam basis data, ia akan secara otomatis menghapus baris turunan.

Namun ini tidak berhasil di EF, kesalahan yang dijelaskan dalam utas ini muncul. Alasan untuk ini adalah, bahwa dalam model data entitas saya (file edmx) properti asosiasi antara orangtua dan tabel anak tidak benar. The End1 OnDeletepilihan dikonfigurasi untuk menjadinone ( "End1" dalam model saya adalah akhir yang memiliki keragaman 1).

Saya secara manual mengubah End1 OnDeleteopsi menjadi Cascadedan daripada bekerja. Saya tidak tahu mengapa EF tidak dapat mengambil ini, ketika saya memperbarui model dari basis data (Saya memiliki basis data model pertama).

Untuk kelengkapan, ini adalah bagaimana kode saya untuk menghapus terlihat seperti:

   public void Delete(int id)
    {
        MyType myObject = _context.MyTypes.Find(id);

        _context.MyTypes.Remove(myObject);
        _context.SaveChanges(); 
   }    

Jika saya tidak menetapkan penghapusan kaskade, saya harus menghapus baris anak secara manual sebelum menghapus baris induk.

Martin
sumber
4

Ini terjadi karena Entitas Anak ditandai sebagai Dimodifikasi daripada Dihapus.

Dan modifikasi yang dilakukan EF terhadap Entitas Anak ketika parent.Remove(child)dijalankan, cukup mengatur referensi ke induknya null.

Anda bisa memeriksa EntityState anak dengan mengetikkan kode berikut ke Jendela Langsung Visual Studio saat pengecualian terjadi, setelah menjalankan SaveChanges():

_context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Modified).ElementAt(X).Entity

di mana X harus diganti oleh Entitas yang dihapus.

Jika Anda tidak memiliki akses ke ObjectContexteksekusi _context.ChildEntity.Remove(child), Anda bisa menyelesaikan masalah ini dengan menjadikan kunci asing sebagai bagian dari kunci utama di tabel anak.

Parent
 ________________
| PK    IdParent |
|       Name     |
|________________|

Child
 ________________
| PK    IdChild  |
| PK,FK IdParent |
|       Name     |
|________________|

Dengan cara ini, jika Anda mengeksekusi parent.Remove(child), EF akan dengan benar menandai Entity telah Dihapus.

Mauricio Ramalho
sumber
2

Jenis solusi ini membantu saya:

Parent original = db.Parent.SingleOrDefault<Parent>(t => t.ID == updated.ID);
db.Childs.RemoveRange(original.Childs);
updated.Childs.ToList().ForEach(c => original.Childs.Add(c));
db.Entry<Parent>(original).CurrentValues.SetValues(updated);

Penting untuk mengatakan bahwa ini menghapus semua catatan dan memasukkannya lagi. Tapi untuk kasus saya (kurang dari 10) tidak apa-apa.

Saya harap ini membantu.

Wagner Bertolini Junior
sumber
Apakah penyisipan kembali terjadi dengan ID baru atau itu membuat ID anak yang mereka miliki di tempat pertama?
Pepito Fernandez
2

Saya mengalami masalah ini hari ini dan ingin berbagi solusi. Dalam kasus saya, solusinya adalah menghapus item Anak sebelum mendapatkan Orang Tua dari database.

Sebelumnya saya melakukannya seperti pada kode di bawah ini. Saya kemudian akan mendapatkan kesalahan yang sama yang tercantum dalam pertanyaan ini.

var Parent = GetParent(parentId);
var children = Parent.Children;
foreach (var c in children )
{
     Context.Children.Remove(c);
}
Context.SaveChanges();

Apa yang berhasil bagi saya, adalah untuk mendapatkan item anak-anak terlebih dahulu, menggunakan parentId (kunci asing) dan kemudian hapus item-item itu. Maka saya bisa mendapatkan Orang Tua dari database dan pada saat itu, seharusnya tidak memiliki item anak lagi dan saya dapat menambahkan item anak baru.

var children = GetChildren(parentId);
foreach (var c in children )
{
     Context.Children.Remove(c);
}
Context.SaveChanges();

var Parent = GetParent(parentId);
Parent.Children = //assign new entities/items here
Dino Bansigan
sumber
2

Anda harus secara manual menghapus koleksi ChildItems dan menambahkan item baru ke dalamnya:

thisParent.ChildItems.Clear();
thisParent.ChildItems.AddRange(modifiedParent.ChildItems);

Setelah itu, Anda dapat memanggil metode ekstensi DeleteOrphans yang akan menangani entitas yatim (harus dipanggil antara metode DetectChanges dan SaveChanges).

public static class DbContextExtensions
{
    private static readonly ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>> s_navPropMappings = new ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>>();

    public static void DeleteOrphans( this DbContext source )
    {
        var context = ((IObjectContextAdapter)source).ObjectContext;
        foreach (var entry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
        {
            var entityType = entry.EntitySet.ElementType as EntityType;
            if (entityType == null)
                continue;

            var navPropMap = s_navPropMappings.GetOrAdd(entityType, CreateNavigationPropertyMap);
            var props = entry.GetModifiedProperties().ToArray();
            foreach (var prop in props)
            {
                NavigationProperty navProp;
                if (!navPropMap.TryGetValue(prop, out navProp))
                    continue;

                var related = entry.RelationshipManager.GetRelatedEnd(navProp.RelationshipType.FullName, navProp.ToEndMember.Name);
                var enumerator = related.GetEnumerator();
                if (enumerator.MoveNext() && enumerator.Current != null)
                    continue;

                entry.Delete();
                break;
            }
        }
    }

    private static ReadOnlyDictionary<string, NavigationProperty> CreateNavigationPropertyMap( EntityType type )
    {
        var result = type.NavigationProperties
            .Where(v => v.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
            .Where(v => v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One || (v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne && v.FromEndMember.GetEntityType() == v.ToEndMember.GetEntityType()))
            .Select(v => new { NavigationProperty = v, DependentProperties = v.GetDependentProperties().Take(2).ToArray() })
            .Where(v => v.DependentProperties.Length == 1)
            .ToDictionary(v => v.DependentProperties[0].Name, v => v.NavigationProperty);

        return new ReadOnlyDictionary<string, NavigationProperty>(result);
    }
}
Duduk
sumber
Ini bekerja dengan baik untuk saya. Saya hanya perlu menambahkan context.DetectChanges();.
Andy Edinborough
1

Saya sudah mencoba solusi ini dan banyak lainnya, tetapi tidak ada satupun yang berhasil. Karena ini adalah jawaban pertama di google, saya akan menambahkan solusi saya di sini.

Metode yang berhasil bagi saya adalah mengeluarkan hubungan dari gambar selama komit, jadi tidak ada yang bisa dikacaukan EF. Saya melakukan ini dengan menemukan kembali objek induk di DBContext, dan menghapusnya. Karena properti navigasi objek yang ditemukan kembali semuanya nol, hubungan anak-anak diabaikan selama komit.

var toDelete = db.Parents.Find(parentObject.ID);
db.Parents.Remove(toDelete);
db.SaveChanges();

Perhatikan bahwa ini mengasumsikan kunci asing diset dengan ON DELETE CASCADE, jadi ketika baris induk dihapus, anak-anak akan dibersihkan oleh database.

Steve
sumber
1

Saya menggunakan solusi Mosh , tetapi tidak jelas bagi saya bagaimana menerapkan kunci komposisi dengan benar dalam kode terlebih dahulu.

Jadi, inilah solusinya:

public class Holiday
{
    [Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int HolidayId { get; set; }
    [Key, Column(Order = 1), ForeignKey("Location")]
    public LocationEnum LocationId { get; set; }

    public virtual Location Location { get; set; }

    public DateTime Date { get; set; }
    public string Name { get; set; }
}
PeterB
sumber
1

Saya memiliki masalah yang sama, tetapi saya tahu itu bekerja dengan baik dalam kasus lain, jadi saya mengurangi masalahnya menjadi ini:

parent.OtherRelatedItems.Clear();  //this worked OK on SaveChanges() - items were being deleted from DB
parent.ProblematicItems.Clear();   // this was causing the mentioned exception on SaveChanges()
  • OtherRelatedItems memiliki kunci utama gabungan (parentId + beberapa kolom lokal) dan bekerja OK
  • Item-item Problematic memiliki Kunci Utama satu-kolom sendiri, dan parentId hanya FK. Ini menyebabkan pengecualian setelah Hapus ().

Yang harus saya lakukan adalah menjadikan ParentId bagian dari PK komposit untuk menunjukkan bahwa anak-anak tidak dapat hidup tanpa orangtua. Saya menggunakan model DB-first, menambahkan PK dan menandai kolom parentId sebagai EntityKey (jadi, saya harus memperbarui keduanya dalam DB dan EF - tidak yakin apakah EF saja sudah cukup).

Saya menjadikan RequestId bagian dari PK Dan kemudian memperbarui model EF, DAN mengatur properti lainnya sebagai bagian dari Entity Key

Setelah Anda memikirkannya, ini adalah perbedaan yang sangat elegan yang digunakan EF untuk memutuskan apakah anak-anak "masuk akal" tanpa orangtua (dalam hal ini Clear () tidak akan menghapus mereka dan membuang pengecualian kecuali jika Anda mengatur ParentId ke sesuatu yang lain / istimewa ), atau - seperti dalam pertanyaan awal - kami berharap item akan dihapus setelah dihapus dari induknya.

Ekus
sumber
0

Masalah ini muncul karena kami mencoba untuk menghapus tabel induk masih ada data tabel anak. Kami memecahkan masalah dengan bantuan penghapusan kaskade.

Dalam model, buat metode dalam kelas dbcontext.

 modelBuilder.Entity<Job>()
                .HasMany<JobSportsMapping>(C => C.JobSportsMappings)
                .WithRequired(C => C.Job)
                .HasForeignKey(C => C.JobId).WillCascadeOnDelete(true);
            modelBuilder.Entity<Sport>()
                .HasMany<JobSportsMapping>(C => C.JobSportsMappings)
                  .WithRequired(C => C.Sport)
                  .HasForeignKey(C => C.SportId).WillCascadeOnDelete(true);

Setelah itu, In Call API kami

var JobList = Context.Job                       
          .Include(x => x.JobSportsMappings)                                     .ToList();
Context.Job.RemoveRange(JobList);
Context.SaveChanges();

Opsi hapus kaskade menghapus tabel induk juga terkait orangtua dengan kode sederhana ini. Cobalah dengan cara sederhana ini.

Hapus Rentang yang digunakan untuk menghapus daftar catatan dalam database Terima kasih

Sowmiya V
sumber
0

Saya juga memecahkan masalah saya dengan jawaban Mosh dan saya pikir jawaban PeterB sedikit karena menggunakan enum sebagai kunci asing. Ingatlah bahwa Anda perlu menambahkan migrasi baru setelah menambahkan kode ini.

Saya juga dapat merekomendasikan posting blog ini untuk solusi lain:

http://www.kianryan.co.uk/2013/03/orphaned-child/

Kode:

public class Child
{
    [Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public string Heading { get; set; }
    //Add other properties here.

    [Key, Column(Order = 1)]
    public int ParentId { get; set; }

    public virtual Parent Parent { get; set; }
}
Ogglas
sumber
0

Menggunakan solusi Slauma, saya membuat beberapa fungsi umum untuk membantu memperbarui objek anak dan koleksi objek anak.

Semua objek gigih saya mengimplementasikan antarmuka ini

/// <summary>
/// Base interface for all persisted entries
/// </summary>
public interface IBase
{
    /// <summary>
    /// The Id
    /// </summary>
    int Id { get; set; }
}

Dengan ini saya menerapkan dua fungsi ini di Repositori saya

    /// <summary>
    /// Check if orgEntry is set update it's values, otherwise add it
    /// </summary>
    /// <param name="set">The collection</param>
    /// <param name="entry">The entry</param>
    /// <param name="orgEntry">The original entry found in the database (can be <code>null</code> is this is a new entry)</param>
    /// <returns>The added or updated entry</returns>
    public T AddOrUpdateEntry<T>(DbSet<T> set, T entry, T orgEntry) where T : class, IBase
    {
        if (entry.Id == 0 || orgEntry == null)
        {
            entry.Id = 0;
            return set.Add(entry);
        }
        else
        {
            Context.Entry(orgEntry).CurrentValues.SetValues(entry);
            return orgEntry;
        }
    }

    /// <summary>
    /// check if each entry of the new list was in the orginal list, if found, update it, if not found add it
    /// all entries found in the orignal list that are not in the new list are removed
    /// </summary>
    /// <typeparam name="T">The type of entry</typeparam>
    /// <param name="set">The database set</param>
    /// <param name="newList">The new list</param>
    /// <param name="orgList">The original list</param>
    public void AddOrUpdateCollection<T>(DbSet<T> set, ICollection<T> newList, ICollection<T> orgList) where T : class, IBase
    {
        // attach or update all entries in the new list
        foreach (T entry in newList)
        {
            // Find out if we had the entry already in the list
            var orgEntry = orgList.SingleOrDefault(e => e.Id != 0 && e.Id == entry.Id);

            AddOrUpdateEntry(set, entry, orgEntry);
        }

        // Remove all entries from the original list that are no longer in the new list
        foreach (T orgEntry in orgList.Where(e => e.Id != 0).ToList())
        {
            if (!newList.Any(e => e.Id == orgEntry.Id))
            {
                set.Remove(orgEntry);
            }
        }
    }

Untuk menggunakannya saya lakukan hal berikut:

var originalParent = _dbContext.ParentItems
    .Where(p => p.Id == parent.Id)
    .Include(p => p.ChildItems)
    .Include(p => p.ChildItems2)
    .SingleOrDefault();

// Add the parent (including collections) to the context or update it's values (except the collections)
originalParent = AddOrUpdateEntry(_dbContext.ParentItems, parent, originalParent);

// Update each collection
AddOrUpdateCollection(_dbContext.ChildItems, parent.ChildItems, orgiginalParent.ChildItems);
AddOrUpdateCollection(_dbContext.ChildItems2, parent.ChildItems2, orgiginalParent.ChildItems2);

Semoga ini membantu


EXTRA: Anda juga bisa membuat kelas DbContextExtentions (atau inferface konteks Anda sendiri):

public static void DbContextExtentions {
    /// <summary>
    /// Check if orgEntry is set update it's values, otherwise add it
    /// </summary>
    /// <param name="_dbContext">The context object</param>
    /// <param name="set">The collection</param>
    /// <param name="entry">The entry</param>
    /// <param name="orgEntry">The original entry found in the database (can be <code>null</code> is this is a new entry)</param>
    /// <returns>The added or updated entry</returns>
    public static T AddOrUpdateEntry<T>(this DbContext _dbContext, DbSet<T> set, T entry, T orgEntry) where T : class, IBase
    {
        if (entry.IsNew || orgEntry == null) // New or not found in context
        {
            entry.Id = 0;
            return set.Add(entry);
        }
        else
        {
            _dbContext.Entry(orgEntry).CurrentValues.SetValues(entry);
            return orgEntry;
        }
    }

    /// <summary>
    /// check if each entry of the new list was in the orginal list, if found, update it, if not found add it
    /// all entries found in the orignal list that are not in the new list are removed
    /// </summary>
    /// <typeparam name="T">The type of entry</typeparam>
    /// <param name="_dbContext">The context object</param>
    /// <param name="set">The database set</param>
    /// <param name="newList">The new list</param>
    /// <param name="orgList">The original list</param>
    public static void AddOrUpdateCollection<T>(this DbContext _dbContext, DbSet<T> set, ICollection<T> newList, ICollection<T> orgList) where T : class, IBase
    {
        // attach or update all entries in the new list
        foreach (T entry in newList)
        {
            // Find out if we had the entry already in the list
            var orgEntry = orgList.SingleOrDefault(e => e.Id != 0 && e.Id == entry.Id);

            AddOrUpdateEntry(_dbContext, set, entry, orgEntry);
        }

        // Remove all entries from the original list that are no longer in the new list
        foreach (T orgEntry in orgList.Where(e => e.Id != 0).ToList())
        {
            if (!newList.Any(e => e.Id == orgEntry.Id))
            {
                set.Remove(orgEntry);
            }
        }
    }
}

dan gunakan seperti:

var originalParent = _dbContext.ParentItems
    .Where(p => p.Id == parent.Id)
    .Include(p => p.ChildItems)
    .Include(p => p.ChildItems2)
    .SingleOrDefault();

// Add the parent (including collections) to the context or update it's values (except the collections)
originalParent = _dbContext.AddOrUpdateEntry(_dbContext.ParentItems, parent, originalParent);

// Update each collection
_dbContext.AddOrUpdateCollection(_dbContext.ChildItems, parent.ChildItems, orgiginalParent.ChildItems);
_dbContext.AddOrUpdateCollection(_dbContext.ChildItems2, parent.ChildItems2, orgiginalParent.ChildItems2);
Bluemoon74
sumber
Anda juga bisa membuat kelas extention untuk konteks Anda dengan fungsi-fungsi ini:
Bluemoon74
0

Saya menghadapi masalah yang sama ketika saya akan menghapus catatan saya daripada beberapa masalah terjadi, karena solusi masalah ini adalah bahwa ketika Anda akan menghapus catatan Anda daripada Anda kehilangan sesuatu sebelum menghapus catatan kepala / master Anda harus menulis ke kode untuk hapus detailnya sebelum tajuk / Master Saya harap masalah Anda terselesaikan.

Ghazi Hur
sumber
-1

Saya telah menemui masalah ini sebelum beberapa jam dan mencoba semuanya, tetapi dalam kasus saya solusinya berbeda dari yang tercantum di atas.

Jika Anda menggunakan entitas yang sudah diambil dari database dan mencoba untuk memodifikasi itu anak-anak kesalahan akan terjadi, tetapi jika Anda mendapatkan salinan baru entitas dari database seharusnya tidak ada masalah. Jangan gunakan ini:

 public void CheckUsersCount(CompanyProduct companyProduct) 
 {
     companyProduct.Name = "Test";
 }

Gunakan ini:

 public void CheckUsersCount(Guid companyProductId)
 {
      CompanyProduct companyProduct = CompanyProductManager.Get(companyProductId);
      companyProduct.Name = "Test";
 }
Tanyo Ivanov
sumber