CATATAN: Jawaban ini berbicara tentang Kerangka Entitas DbContext
, tetapi ini berlaku untuk segala jenis implementasi Unit Kerja, seperti LINQ untuk SQL DataContext
, dan NHibernate ISession
.
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. Itu DbContext
bukan thread-safe dan dan karena DbContext
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) dari DbContext
siapa pun yang membutuhkannya. (untuk informasi lebih lanjut tentang mengapa satu DbContext
-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:
- Karena setiap objek mendapatkan turunannya sendiri, setiap kelas yang mengubah keadaan sistem, perlu memanggil
_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 .
- Anda perlu memastikan bahwa entitas [dimuat dan disimpan oleh a
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.
- Sejak
DbContext
diterapkan IDisposable
, 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 menelepon context.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 menyuntikkan DbContextFactory
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:
public void SomeOperation()
{
using (var context = this.contextFactory.CreateNew())
{
var entities = this.otherDependency.Operate(
context, "some value");
context.Entities.InsertOnSubmit(entities);
context.SaveChanges();
}
}
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 sama DbContext
.
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. Karena DbContext
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:
class TransactionalCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand>
{
readonly DbContext context;
readonly ICommandHandler<TCommand> decorated;
public TransactionCommandHandlerDecorator(
DbContext context,
ICommandHandler<TCommand> decorated)
{
this.context = context;
this.decorated = decorated;
}
public void Handle(TCommand command)
{
this.decorated.Handle(command);
context.SaveChanges();
}
}
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.