Perbarui hubungan saat menyimpan perubahan objek EF4 POCO

107

Entity Framework 4, POCO object dan ASP.Net MVC2. Saya memiliki hubungan banyak ke banyak, katakanlah antara BlogPost dan entitas Tag. Ini berarti bahwa di kelas BlogPost POCO yang dihasilkan T4 saya, saya memiliki:

public virtual ICollection<Tag> Tags {
    // getter and setter with the magic FixupCollection
}
private ICollection<Tag> _tags;

Saya meminta BlogPost dan Tag terkait dari instance ObjectContext dan mengirimkannya ke lapisan lain (Lihat di aplikasi MVC). Kemudian saya mendapatkan kembali BlogPost yang diperbarui dengan properti yang diubah dan hubungan yang diubah. Misalnya memiliki tag "A" "B" dan "C", dan tag baru adalah "C" dan "D". Dalam contoh khusus saya, tidak ada Tag baru dan properti Tag tidak pernah berubah, jadi satu-satunya hal yang harus disimpan adalah hubungan yang diubah. Sekarang saya perlu menyimpan ini di ObjectContext lain. (Pembaruan: Sekarang saya mencoba melakukan dalam contoh konteks yang sama dan juga gagal.)

Masalahnya: Saya tidak bisa membuatnya menyelamatkan hubungan dengan benar. Saya mencoba semua yang saya temukan:

  • Controller.UpdateModel dan Controller.TryUpdateModel tidak berfungsi.
  • Mendapatkan BlogPost lama dari konteks lalu mengubah koleksi tidak berfungsi. (dengan metode berbeda dari poin berikutnya)
  • Ini mungkin akan berhasil, tapi saya harap ini hanya solusi, bukan solusinya :(.
  • Mencoba fungsi Attach / Add / ChangeObjectState untuk BlogPost dan / atau Tag di setiap kemungkinan kombinasi. Gagal.
  • Ini terlihat seperti yang saya butuhkan, tetapi tidak berhasil (saya mencoba memperbaikinya, tetapi tidak bisa untuk masalah saya).
  • Mencoba ChangeState / Add / Attach / ... objek hubungan konteks. Gagal.

"Tidak berfungsi" berarti dalam banyak kasus bahwa saya mengerjakan "solusi" yang diberikan hingga tidak menghasilkan kesalahan dan setidaknya menyimpan properti BlogPost. Apa yang terjadi dengan hubungan bervariasi: biasanya Tag ditambahkan lagi ke tabel Tag dengan PK baru dan BlogPost yang disimpan mereferensikannya dan bukan yang asli. Tentu saja Tag yang dikembalikan memiliki PK, dan sebelum metode simpan / perbarui saya memeriksa PK dan mereka sama dengan yang ada di database sehingga mungkin EF berpikir bahwa mereka adalah objek baru dan PK tersebut adalah temp sementara.

Masalah yang saya ketahui dan mungkin tidak memungkinkan untuk menemukan solusi sederhana otomatis: Ketika koleksi objek POCO diubah, itu seharusnya terjadi oleh properti koleksi virtual yang disebutkan di atas, karena trik FixupCollection akan memperbarui referensi terbalik di ujung lain dari hubungan banyak-ke-banyak. Namun, saat Tampilan "mengembalikan" objek BlogPost yang diperbarui, hal itu tidak terjadi. Ini berarti mungkin tidak ada solusi sederhana untuk masalah saya, tetapi itu akan membuat saya sangat sedih dan saya akan membenci kemenangan EF4-POCO-MVC :(. Itu juga berarti EF tidak dapat melakukan ini di lingkungan MVC mana pun Jenis objek EF4 digunakan :(. Saya pikir pelacakan perubahan berbasis snapshot seharusnya mengetahui bahwa BlogPost yang diubah memiliki hubungan ke Tag dengan PK yang ada.

Btw: Saya pikir masalah yang sama terjadi dengan hubungan satu-ke-banyak (google dan kolega saya bilang begitu). Saya akan mencobanya di rumah, tetapi meskipun berhasil, itu tidak membantu saya dalam enam hubungan banyak-ke-banyak di aplikasi saya :(.

peterfoldi
sumber
Silakan posting kode Anda. Ini adalah skenario yang umum.
John Farrell
1
Saya memiliki solusi otomatis untuk masalah ini, itu tersembunyi dalam jawaban di bawah sehingga banyak yang akan melewatkannya tetapi tolong lihat karena ini akan menyelamatkan Anda dari pekerjaan yang luar biasa lihat posting di sini
brentmckendrick
@brentckendrick Saya pikir pendekatan lain lebih baik. Alih-alih mengirim seluruh grafik objek yang dimodifikasi melalui kabel, mengapa tidak mengirim delta saja? Anda bahkan tidak perlu membuat kelas DTO dalam kasus itu. Jika Anda memiliki pendapat tentang ini, mari kita bahas di stackoverflow.com/questions/1344066/calculate-object-delta .
HappyNomad

Jawaban:

145

Mari kita coba dengan cara ini:

  • Lampirkan BlogPost ke konteks. Setelah melampirkan objek ke konteks status objek, semua objek terkait dan semua relasi disetel ke Tidak Berubah.
  • Gunakan context.ObjectStateManager.ChangeObjectState untuk menyetel BlogPost Anda ke Modified
  • Iterasi melalui pengumpulan Tag
  • Gunakan context.ObjectStateManager.ChangeRelationshipState untuk menyetel status hubungan antara Tag saat ini dan BlogPost.
  • Simpan perubahan

Edit:

Saya kira salah satu komentar saya memberi Anda harapan palsu bahwa EF akan melakukan penggabungan untuk Anda. Saya banyak bermain dengan masalah ini dan kesimpulan saya mengatakan EF tidak akan melakukan ini untuk Anda. Saya rasa Anda juga telah menemukan pertanyaan saya di MSDN . Pada kenyataannya ada banyak pertanyaan seperti itu di Internet. Masalahnya adalah tidak dijelaskan secara jelas bagaimana menangani skenario ini. Jadi mari kita lihat masalahnya:

Latar belakang masalah

EF perlu melacak perubahan pada entitas sehingga persistensi mengetahui rekaman mana yang harus diperbarui, disisipkan atau dihapus. Masalahnya adalah bahwa ObjectContext bertanggung jawab untuk melacak perubahan. ObjectContext dapat melacak perubahan hanya untuk entitas terlampir. Entitas yang dibuat di luar ObjectContext tidak dilacak sama sekali.

Deskripsi masalah

Berdasarkan uraian di atas, kami dapat dengan jelas menyatakan bahwa EF lebih cocok untuk skenario terhubung di mana entitas selalu dilampirkan ke konteks - khas untuk aplikasi WinForm. Aplikasi web memerlukan skenario terputus di mana konteks ditutup setelah pemrosesan permintaan dan konten entitas diteruskan sebagai respons HTTP ke klien. Permintaan HTTP berikutnya menyediakan konten entitas yang dimodifikasi yang harus dibuat ulang, dilampirkan ke konteks baru dan dipertahankan. Rekreasi biasanya terjadi di luar cakupan konteks (arsitektur berlapis dengan ketidaktahuan ketekunan).

Larutan

Jadi bagaimana menangani skenario terputus seperti itu? Saat menggunakan kelas POCO, kami memiliki 3 cara untuk menangani pelacakan perubahan:

  • Snapshot - membutuhkan konteks yang sama = tidak berguna untuk skenario terputus
  • Proksi pelacakan dinamis - membutuhkan konteks yang sama = tidak berguna untuk skenario terputus
  • Sinkronisasi manual.

Sinkronisasi manual pada entitas tunggal adalah tugas yang mudah. Anda hanya perlu melampirkan entitas dan memanggil AddObject untuk memasukkan, DeleteObject untuk menghapus atau menyetel status di ObjectStateManager ke Modified untuk memperbarui. Rasa sakit yang sebenarnya datang ketika Anda harus berurusan dengan grafik objek, bukan entitas tunggal. Rasa sakit ini bahkan lebih buruk ketika Anda harus berurusan dengan asosiasi independen (yang tidak menggunakan properti Foreign Key) dan banyak ke banyak relasi. Dalam hal ini Anda harus menyinkronkan setiap entitas secara manual dalam grafik objek tetapi juga setiap relasi dalam grafik objek.

Sinkronisasi manual diusulkan sebagai solusi oleh dokumentasi MSDN: Memasang dan Melepaskan objek mengatakan:

Objek dilampirkan ke konteks objek dalam keadaan tidak berubah. Jika Anda perlu mengubah status objek atau hubungan karena Anda tahu bahwa objek Anda telah diubah dalam status terlepas, gunakan salah satu dari metode berikut ini.

Metode yang disebutkan adalah ChangeObjectState dan ChangeRelationshipState dari ObjectStateManager = pelacakan perubahan manual. Proposal serupa ada di artikel dokumentasi MSDN lainnya: Mendefinisikan dan Mengelola Hubungan mengatakan:

Jika Anda bekerja dengan objek yang terputus, Anda harus mengelola sinkronisasi secara manual.

Selain itu ada posting blog terkait EF v1 yang mengkritik perilaku EF ini.

Alasan solusi

EF memiliki banyak "membantu" operasi dan pengaturan seperti Segarkan , beban , ApplyCurrentValues , ApplyOriginalValues , MergeOption dll Tapi oleh penyelidikan saya semua fitur ini bekerja hanya untuk entitas tunggal dan hanya mempengaruhi preperties skalar (= tidak sifat navigasi dan hubungan). Saya lebih suka tidak menguji metode ini dengan tipe kompleks yang bersarang di entitas.

Solusi lain yang diusulkan

Alih-alih fungsi Merge yang sebenarnya, tim EF menyediakan sesuatu yang disebut Self Tracking Entities (STE) yang tidak memecahkan masalah. Pertama-tama STE hanya berfungsi jika instance yang sama digunakan untuk seluruh pemrosesan. Dalam aplikasi web, hal ini tidak terjadi kecuali Anda menyimpan instance dalam status tampilan atau sesi. Karena itu saya sangat tidak senang menggunakan EF dan saya akan memeriksa fitur-fitur NHibernate. Pengamatan pertama mengatakan bahwa NHibernate mungkin memiliki fungsi seperti itu .

Kesimpulan

Saya akan mengakhiri asumsi ini dengan tautan tunggal ke pertanyaan terkait lainnya di forum MSDN. Periksa jawaban Zeeshan Hirani. Dia adalah penulis dari Entity Framework 4.0 Recipes . Jika dia mengatakan bahwa penggabungan otomatis grafik objek tidak didukung, saya percaya padanya.

Tetapi masih ada kemungkinan bahwa saya sepenuhnya salah dan beberapa fungsi penggabungan otomatis ada di EF.

Edit 2:

Seperti yang Anda lihat, ini telah ditambahkan ke MS Connect sebagai saran pada tahun 2007. MS telah menutupnya sebagai sesuatu yang harus dilakukan di versi berikutnya tetapi sebenarnya tidak ada yang dilakukan untuk memperbaiki celah ini kecuali STE.

Ladislav Mrnka
sumber
7
Ini adalah salah satu jawaban terbaik yang pernah saya baca di SO. Anda telah dengan jelas menyatakan apa yang gagal disampaikan oleh begitu banyak artikel MSDN, dokumentasi, dan posting blog tentang topik tersebut. EF4 tidak secara inheren mendukung pemutakhiran hubungan dari entitas "terpisah". Ini hanya menyediakan alat bagi Anda untuk menerapkannya sendiri. Terima kasih!
tyriker
1
Jadi setelah beberapa bulan lalu, bagaimana dengan NHibernate terkait masalah ini dibandingkan dengan EF4?
CallMeLaNN
1
Ini didukung dengan sangat baik di NHibernate :-) tidak perlu menggabungkan secara manual, dalam contoh saya ini adalah grafik objek dalam 3 tingkat, pertanyaan memiliki jawaban, setiap jawaban memiliki komentar, dan pertanyaan memiliki komentar juga. NHibernate dapat mempertahankan / menggabungkan grafik objek Anda, tidak peduli seberapa rumitnya ienablemuch.com/2011/01/nhibernate-saves-your-whole-object.html Pengguna NHibernate lain yang puas: codinginstinct.com/2009/11/…
Michael Buen
2
Salah satu penjelasan terbaik yang pernah saya baca !! Terima kasih banyak
marvelTracker
2
Tim EF berencana untuk membahas pasca-EF6 ini. Anda mungkin ingin memberikan suara untuk entityframework.codeplex.com/workitem/864
Eric J.
19

Saya memiliki solusi untuk masalah yang dijelaskan di atas oleh Ladislav. Saya telah membuat metode ekstensi untuk DbContext yang secara otomatis akan melakukan penambahan / perbarui / hapus berdasarkan perbedaan grafik yang disediakan dan grafik tetap.

Saat ini menggunakan Entity Framework Anda perlu melakukan pembaruan kontak secara manual, memeriksa apakah setiap kontak baru dan menambah, memeriksa apakah diperbarui dan mengedit, memeriksa jika dihapus, lalu menghapusnya dari database. Begitu Anda harus melakukan ini untuk beberapa agregat berbeda dalam sistem besar, Anda mulai menyadari pasti ada cara yang lebih baik dan lebih umum.

Silakan lihat dan lihat apakah itu dapat membantu http://refactorthis.wordpress.com/2012/12/11/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a- grafik-entitas-terpisah /

Anda dapat langsung membuka kodenya di sini https://github.com/refactorthis/GraphDiff

brentmckendrick
sumber
Saya yakin Anda dapat menyelesaikan pertanyaan ini dengan mudah, saya mengalami saat yang buruk dengannya.
Shimmy Weitzhandler
1
Hai Shimmy, maaf akhirnya punya waktu untuk melihat-lihat. Saya akan memeriksanya malam ini.
brentmckendrick
Perpustakaan ini luar biasa dan telah menghemat banyak waktu saya! Terima kasih!
lordjeb
9

Saya tahu ini terlambat untuk OP tetapi karena ini adalah masalah yang sangat umum, saya memposting ini jika melayani orang lain. Saya telah bermain-main dengan masalah ini dan saya pikir saya mendapat solusi yang cukup sederhana, yang saya lakukan adalah:

  1. Simpan objek utama (Blog misalnya) dengan menyetel statusnya ke Dimodifikasi.
  2. Buat kueri database untuk objek yang diperbarui termasuk koleksi yang perlu saya perbarui.
  3. Buat kueri dan konversikan .ToList () entitas yang ingin saya sertakan dalam koleksi saya.
  4. Perbarui koleksi objek utama ke Daftar yang saya dapatkan dari langkah 3.
  5. Simpan perubahan();

Dalam contoh berikut "dataobj" dan "_categories" adalah parameter yang diterima oleh pengontrol saya "dataobj" adalah objek utama saya, dan "_categories" adalah IEnumerable yang berisi ID kategori yang dipilih pengguna dalam tampilan.

    db.Entry(dataobj).State = EntityState.Modified;
    db.SaveChanges();
    dataobj = db.ServiceTypes.Include(x => x.Categories).Single(x => x.Id == dataobj.Id);
    var it = _categories != null ? db.Categories.Where(x => _categories.Contains(x.Id)).ToList() : null;
    dataobj.Categories = it;
    db.SaveChanges();

Ia bahkan bekerja untuk banyak relasi

c0y0teX
sumber
7

Tim Entity Framework menyadari bahwa ini adalah masalah kegunaan dan berencana untuk mengatasinya setelah EF6.

Dari tim Entity Framework:

Ini adalah masalah kegunaan yang kami sadari dan merupakan sesuatu yang telah kami pikirkan dan rencanakan untuk melakukan lebih banyak pekerjaan setelah EF6. Saya telah membuat item pekerjaan ini untuk melacak masalah: http://entityframework.codeplex.com/workitem/864 Item pekerjaan juga berisi tautan ke item suara pengguna untuk ini - Saya mendorong Anda untuk memilihnya jika Anda punya belum melakukannya.

Jika hal ini memengaruhi Anda, berikan suara untuk fitur di

http://entityframework.codeplex.com/workitem/864

Eric J.
sumber
pasca-EF6? Kalau begitu tahun berapa dalam kasus optimis?
quetzalcoatl
@quetzalcoatl: Setidaknya ada di radar mereka :-) EF telah berkembang pesat sejak EF 1 tetapi masih memiliki cara untuk pergi.
Eric J.
1

Semua jawaban bagus untuk menjelaskan masalah, tetapi tidak ada yang benar-benar menyelesaikan masalah untuk saya.

Saya menemukan bahwa jika saya tidak menggunakan relasi di entitas induk tetapi hanya menambahkan dan menghapus entitas anak, semuanya berfungsi dengan baik.

Maaf untuk VB, tapi di situlah proyek yang saya kerjakan ditulis.

Entitas induk "Report" memiliki hubungan satu ke banyak dengan "ReportRole" dan memiliki properti "ReportRoles". Peran baru diteruskan oleh string yang dipisahkan koma dari panggilan Ajax.

Baris pertama akan menghapus semua entitas anak, dan jika saya menggunakan "report.ReportRoles.Remove (f)" bukan "db.ReportRoles.Remove (f)" Saya akan mendapatkan kesalahan.

report.ReportRoles.ToList.ForEach(Function(f) db.ReportRoles.Remove(f))
Dim newRoles = If(String.IsNullOrEmpty(model.RolesString), New String() {}, model.RolesString.Split(","))
newRoles.ToList.ForEach(Function(f) db.ReportRoles.Add(New ReportRole With {.ReportId = report.Id, .AspNetRoleId = f}))
Alan Bridges
sumber