Saya telah membaca banyak artikel yang menjelaskan cara mengatur Entity Framework DbContext
sehingga hanya satu yang dibuat dan digunakan per permintaan web HTTP menggunakan berbagai kerangka kerja DI.
Mengapa ini ide yang bagus? Apa keuntungan yang Anda dapatkan dengan menggunakan pendekatan ini? Apakah ada situasi tertentu di mana ini akan menjadi ide yang bagus? Adakah hal-hal yang dapat Anda lakukan dengan menggunakan teknik ini yang tidak dapat Anda lakukan saat instantiasi DbContext
s per panggilan metode repositori?
Jawaban:
Mari kita mulai dengan menggemakan Ian: Memiliki satu
DbContext
untuk seluruh aplikasi adalah Ide Buruk. Satu-satunya situasi di mana ini masuk akal adalah ketika Anda memiliki aplikasi single-threaded dan database yang hanya digunakan oleh instance aplikasi tunggal itu. ItuDbContext
bukan thread-safe dan dan karenaDbContext
data cache, itu akan segera basi. Ini akan membuat Anda dalam segala macam masalah ketika banyak pengguna / aplikasi bekerja pada basis data itu secara bersamaan (yang tentu saja sangat umum). Tapi saya berharap Anda sudah tahu itu dan hanya ingin tahu mengapa tidak hanya menyuntikkan contoh baru (yaitu dengan gaya hidup sementara) dariDbContext
siapa pun yang membutuhkannya. (untuk informasi lebih lanjut tentang mengapa satuDbContext
-atau bahkan pada konteks per utas- buruk, baca jawaban ini ).Mari saya mulai dengan mengatakan bahwa mendaftar
DbContext
sebagai transien dapat bekerja, tetapi biasanya Anda ingin memiliki satu contoh unit kerja dalam lingkup tertentu. Dalam aplikasi web, bisa jadi praktis untuk mendefinisikan cakupan seperti itu pada batas-batas permintaan web; dengan demikian gaya hidup Per Web Request. Ini memungkinkan Anda untuk membiarkan seluruh rangkaian objek beroperasi dalam konteks yang sama. Dengan kata lain, mereka beroperasi dalam transaksi bisnis yang sama.Jika Anda tidak memiliki tujuan untuk memiliki serangkaian operasi yang beroperasi dalam konteks yang sama, dalam hal ini gaya hidup sementara baik-baik saja, tetapi ada beberapa hal yang perlu diperhatikan:
_context.SaveChanges()
(jika tidak, perubahan akan hilang). Ini dapat menyulitkan kode Anda, dan menambahkan tanggung jawab kedua pada kode (tanggung jawab mengendalikan konteks), dan merupakan pelanggaran terhadap Prinsip Tanggung Jawab Tunggal .DbContext
] tidak pernah meninggalkan ruang lingkup kelas seperti itu, karena mereka tidak dapat digunakan dalam instance konteks kelas lain. Ini dapat sangat menyulitkan kode Anda, karena ketika Anda membutuhkan entitas itu, Anda perlu memuatnya lagi dengan id, yang juga dapat menyebabkan masalah kinerja.DbContext
diterapkanIDisposable
, Anda mungkin masih ingin Buang semua instance yang dibuat. Jika Anda ingin melakukan ini, pada dasarnya Anda memiliki dua opsi. Anda harus membuangnya dalam metode yang sama setelah meneleponcontext.SaveChanges()
, tetapi dalam hal itu logika bisnis mengambil kepemilikan atas objek yang diteruskan dari luar. Opsi kedua adalah Buang semua mesin virtual yang dibuat pada batas Permintaan Http, tetapi dalam hal ini Anda masih memerlukan semacam pelingkupan untuk memberi tahu wadah kapan mesin tersebut perlu dibuang.Pilihan lain adalah tidak menyuntikkan
DbContext
sama sekali. Sebaliknya, Anda menyuntikkanDbContextFactory
yang dapat membuat contoh baru (saya dulu menggunakan pendekatan ini di masa lalu). Dengan cara ini logika bisnis mengontrol konteks secara eksplisit. Jika mungkin terlihat seperti ini:Sisi positifnya adalah Anda mengelola kehidupan
DbContext
secara eksplisit dan mudah untuk mengaturnya. Ini juga memungkinkan Anda untuk menggunakan konteks tunggal dalam ruang lingkup tertentu, yang memiliki keuntungan jelas, seperti menjalankan kode dalam satu transaksi bisnis, dan dapat melewati entitas, karena mereka berasal dari samaDbContext
.The downside adalah bahwa Anda harus melewati
DbContext
dari metode ke metode (yang disebut Metode Injeksi). Perhatikan bahwa dalam beberapa hal solusi ini sama dengan pendekatan 'cakupan', tetapi sekarang ruang lingkup dikontrol dalam kode aplikasi itu sendiri (dan mungkin diulang berkali-kali). Ini adalah aplikasi yang bertanggung jawab untuk membuat dan membuang unit kerja. KarenaDbContext
ini dibuat setelah grafik dependensi dibangun, Injeksi Konstruktor keluar dari gambar dan Anda perlu menunda untuk Metode Injeksi ketika Anda perlu meneruskan konteks dari satu kelas ke yang lain.Metode Injeksi tidak terlalu buruk, tetapi ketika logika bisnis menjadi lebih kompleks, dan lebih banyak kelas terlibat, Anda harus meneruskannya dari metode ke metode dan kelas ke kelas, yang dapat menyulitkan banyak kode (saya telah melihat ini di masa lalu). Untuk aplikasi sederhana, pendekatan ini akan baik-baik saja.
Karena kelemahannya, pendekatan pabrik ini memiliki sistem yang lebih besar, pendekatan lain dapat berguna dan itu adalah di mana Anda membiarkan wadah atau kode infrastruktur / Komposisi Root mengelola unit kerja. Ini adalah gaya pertanyaan Anda.
Dengan membiarkan wadah dan / atau infrastruktur menangani ini, kode aplikasi Anda tidak tercemar dengan harus membuat, (secara opsional) komit dan Buang instance UoW, yang menjaga logika bisnis tetap sederhana dan bersih (hanya Satu Tanggung Jawab). Ada beberapa kesulitan dengan pendekatan ini. Misalnya, apakah Anda Berkomitmen dan Buang contoh tersebut?
Membuang unit kerja dapat dilakukan di akhir permintaan web. Namun banyak orang, secara keliru menganggap bahwa ini juga merupakan tempat untuk Komit unit kerja. Namun, pada saat itu dalam aplikasi, Anda tidak dapat menentukan dengan pasti bahwa unit kerja harus benar-benar berkomitmen. mis. Jika kode lapisan bisnis melempar pengecualian yang tertangkap lebih tinggi di callstack, Anda pasti tidak ingin Berkomitmen.
Solusi sebenarnya adalah lagi untuk secara eksplisit mengelola semacam ruang lingkup, tetapi kali ini lakukan di dalam Root Komposisi. Mengabstraksi semua logika bisnis di balik pola perintah / penangan , Anda akan dapat menulis dekorator yang dapat melilit setiap penangan perintah yang memungkinkan untuk melakukan ini. Contoh:
Ini memastikan bahwa Anda hanya perlu menulis kode infrastruktur ini sekali saja. Setiap wadah DI padat memungkinkan Anda untuk mengatur dekorator seperti itu untuk melilit semua
ICommandHandler<T>
implementasi secara konsisten.sumber
CreateCommand<TEnity>
dan generikCreateCommandHandler<TEntity> : ICommandHandler<CreateCommand<TEntity>>
(dan melakukan hal yang sama untuk Pembaruan, dan Hapus, dan memiliki satuGetByIdQuery<TEntity>
permintaan). Namun, Anda harus bertanya pada diri sendiri apakah model ini adalah abstraksi yang berguna untuk operasi CRUD, atau apakah hanya menambah kompleksitas. Namun, Anda mungkin mendapat manfaat dari kemungkinan untuk dengan mudah menambahkan masalah lintas sektoral (melalui dekorator) menggunakan model ini. Anda harus mempertimbangkan pro dan kontra.TransactionCommandHandlerDecorator
? misalnya jika kelas yang didekorasi adalahInsertCommandHandler
kelas, bagaimana ia dapat mendaftarkan operasi penyisipan ke konteks (DbContext dalam EF)?Ada dua rekomendasi yang bertentangan oleh microsoft dan banyak orang menggunakan DbContexts dengan cara yang sangat berbeda.
Mereka bertentangan satu sama lain karena jika Permintaan Anda melakukan banyak hal yang tidak terkait dengan hal-hal Db, maka DbContext Anda disimpan tanpa alasan. Dengan demikian, membuang-buang DbContext Anda tetap hidup selagi permintaan Anda hanya menunggu hal-hal acak diselesaikan ...
Begitu banyak orang yang mengikuti aturan 1 memiliki DbContext mereka di dalam "pola Repositori" mereka dan membuat Instance baru per Database Query jadi X * DbContext per Request
Mereka hanya mendapatkan data dan membuang konteksnya secepat mungkin. Ini dianggap oleh banyak orang sebagai praktik yang dapat diterima. Meskipun ini memiliki manfaat menduduki sumber daya db Anda untuk waktu minimum, ini jelas mengorbankan semua permen UnitOfWork dan Caching yang ditawarkan EF.
Menjaga agar tetap hidup satu contoh multiguna DbContext memaksimalkan manfaat Caching tetapi karena DbContext tidak aman utas dan setiap permintaan Web berjalan di utas itu sendiri, DbContext per Permintaan adalah yang terpanjang yang dapat Anda pertahankan.
Jadi rekomendasi tim EF tentang penggunaan 1 Db Konteks per permintaan, itu jelas didasarkan pada kenyataan bahwa dalam Aplikasi Web, UnitOfWork kemungkinan besar akan berada dalam satu permintaan dan permintaan itu memiliki satu utas. Jadi satu DbContext per permintaan seperti manfaat ideal UnitOfWork dan Caching.
Tetapi dalam banyak kasus ini tidak benar. Saya mempertimbangkan untuk Logging UnitOfWork yang terpisah sehingga memiliki DbContext baru untuk Post-Request Logging di async threads sepenuhnya dapat diterima
Jadi Akhirnya ternyata masa hidup DbContext terbatas pada dua parameter ini. UnitOfWork and Thread
sumber
Tidak ada satu jawaban pun di sini yang sebenarnya menjawab pertanyaan itu. OP tidak bertanya tentang desain DbContext tunggal / per-aplikasi, ia bertanya tentang desain permintaan per-web dan apa manfaat potensial yang bisa ada.
Saya akan merujuk http://mehdi.me/ambient-dbcontext-in-ef6/ karena Mehdi adalah sumber yang fantastis:
Perlu diingat ada kontra juga. Tautan itu berisi banyak sumber daya lain untuk dibaca tentang masalah ini.
Hanya memposting ini kalau-kalau orang lain tersandung pada pertanyaan ini dan tidak terserap dalam jawaban yang tidak benar-benar menjawab pertanyaan.
sumber
Saya cukup yakin itu karena DbContext sama sekali tidak aman. Jadi berbagi hal itu bukan ide yang bagus.
sumber
Satu hal yang tidak benar-benar dibahas dalam pertanyaan atau diskusi adalah kenyataan bahwa DbContext tidak dapat membatalkan perubahan. Anda dapat mengirim perubahan, tetapi Anda tidak dapat menghapus pohon perubahan, jadi jika Anda menggunakan konteks per permintaan, Anda kurang beruntung jika Anda perlu membuang perubahan untuk alasan apa pun.
Secara pribadi saya membuat instance DbContext ketika diperlukan - biasanya melekat pada komponen bisnis yang memiliki kemampuan untuk membuat ulang konteks jika diperlukan. Dengan cara itu saya memiliki kontrol atas proses, daripada memiliki satu contoh yang memaksa saya. Saya juga tidak harus membuat DbContext di setiap startup controller terlepas dari apakah itu benar-benar digunakan. Kemudian jika saya masih ingin memiliki per permintaan contoh, saya dapat membuatnya di CTOR (melalui DI atau secara manual) atau membuatnya sesuai kebutuhan dalam setiap metode pengontrol. Secara pribadi saya biasanya mengambil pendekatan yang terakhir untuk menghindari membuat contoh DbContext ketika mereka sebenarnya tidak diperlukan.
Itu tergantung dari sudut mana Anda melihatnya juga. Bagi saya contoh per permintaan tidak pernah masuk akal. Apakah DbContext benar-benar termasuk dalam Permintaan Http? Dari segi perilaku itulah tempat yang salah. Komponen bisnis Anda harus menciptakan konteks Anda, bukan permintaan Http. Kemudian Anda dapat membuat atau membuang komponen bisnis Anda sesuai kebutuhan dan tidak pernah khawatir tentang masa pakai konteks.
sumber
Saya setuju dengan pendapat sebelumnya. Adalah baik untuk mengatakan, bahwa jika Anda akan membagikan DbContext dalam aplikasi utas tunggal, Anda akan membutuhkan lebih banyak memori. Misalnya aplikasi web saya di Azure (satu contoh ekstra kecil) membutuhkan 150 MB memori lain dan saya memiliki sekitar 30 pengguna per jam.
Berikut ini adalah contoh nyata gambar: aplikasi telah digunakan pada jam 12 siang
sumber
Apa yang saya suka tentang itu adalah menyelaraskan unit-of-work (seperti yang dilihat pengguna - yaitu pengiriman halaman) dengan unit-of-work dalam arti ORM.
Oleh karena itu, Anda dapat membuat transaksional pengiriman seluruh halaman, yang tidak dapat Anda lakukan jika Anda mengekspos metode CRUD dengan masing-masing menciptakan konteks baru.
sumber
Alasan lain yang tidak masuk akal untuk tidak menggunakan DbContext singleton, bahkan dalam aplikasi pengguna tunggal berulir tunggal, adalah karena pola peta identitas yang digunakannya. Ini berarti bahwa setiap kali Anda mengambil data menggunakan kueri atau oleh id, itu akan membuat instance entitas yang diambil dalam cache. Lain kali Anda mengambil entitas yang sama, itu akan memberi Anda contoh cache entitas, jika tersedia, dengan modifikasi apa pun yang telah Anda lakukan dalam sesi yang sama. Ini diperlukan agar metode SaveChanges tidak berakhir dengan beberapa instance entitas yang berbeda dari catatan database yang sama; jika tidak, konteksnya harus menggabungkan data dari semua instance entitas tersebut.
Alasan yang menjadi masalah adalah DbContext singleton dapat menjadi bom waktu yang akhirnya bisa men-cache seluruh database + overhead dari objek .NET dalam memori.
Ada beberapa cara untuk mengatasi perilaku ini dengan hanya menggunakan kueri Linq dengan
.NoTracking()
metode ekstensi. Juga hari ini PC memiliki banyak RAM. Tapi biasanya itu bukan perilaku yang diinginkan.sumber
Masalah lain yang harus diperhatikan dengan Entity Framework secara spesifik adalah ketika menggunakan kombinasi membuat entitas baru, pemuatan malas, dan kemudian menggunakan entitas baru tersebut (dari konteks yang sama). Jika Anda tidak menggunakan IDbSet.Create (vs hanya baru), pemuatan malas pada entitas itu tidak berfungsi saat diambil dari konteks tempat ia dibuat. Contoh:
sumber