Apakah Ada Manfaat Nyata untuk Repositori Generik?

28

Sedang membaca beberapa artikel tentang keuntungan membuat Gudang Umum untuk aplikasi baru ( contoh ). Idenya tampak bagus karena memungkinkan saya menggunakan repositori yang sama untuk melakukan beberapa hal untuk beberapa tipe entitas yang berbeda sekaligus:

IRepository repo = new EfRepository(); // Would normally pass through IOC into constructor 
var c1 = new Country() { Name = "United States", CountryCode = "US" };
var c2 = new Country() { Name = "Canada", CountryCode = "CA" };
var c3 = new Country() { Name = "Mexico", CountryCode = "MX" };
var p1 = new Province() { Country = c1, Name = "Alabama", Abbreviation = "AL" };
var p2 = new Province() { Country = c1, Name = "Alaska", Abbreviation = "AK" };
var p3 = new Province() { Country = c2, Name = "Alberta", Abbreviation = "AB" };
repo.Add<Country>(c1);
repo.Add<Country>(c2);
repo.Add<Country>(c3);
repo.Add<Province>(p1);
repo.Add<Province>(p2);
repo.Add<Province>(p3);
repo.Save();

Namun, sisa implementasi dari Repository memiliki ketergantungan yang kuat pada Linq:

IQueryable<T> Query();
IList<T> Find(Expression<Func<T,bool>> predicate);
T Get(Expression<Func<T,bool>> predicate);
T First(Expression<Func<T,bool>> predicate);
//... and so on

Pola repositori ini bekerja sangat baik untuk Entity Framework, dan cukup banyak menawarkan pemetaan metode 1 ke 1 yang tersedia di DbContext / DbSet. Tetapi mengingat penyerapan Linq yang lambat pada teknologi akses data lainnya di luar Entity Framework, apa keuntungan yang diberikan ini daripada bekerja langsung dengan DbContext?

Saya mencoba untuk menulis versi PetaPoco dari Repositori, tetapi PetaPoco tidak mendukung Linq Expressions, yang membuat membuat antarmuka IRepository generik sangat tidak berguna kecuali Anda hanya menggunakannya untuk GetAll, GetById, Add, Update, Delete, dan Save dasar metode dan menggunakannya sebagai kelas dasar. Maka Anda harus membuat repositori spesifik dengan metode khusus untuk menangani semua klausa "di mana" yang sebelumnya bisa saya sampaikan sebagai predikat.

Apakah pola Gudang Generik berguna untuk apa pun di luar Kerangka Entitas? Jika tidak, mengapa seseorang menggunakannya sama sekali alih-alih bekerja langsung dengan Entity Framework?


Tautan asli tidak mencerminkan pola yang saya gunakan dalam kode sampel saya. Ini adalah ( tautan yang diperbarui ).

Sam
sumber
1
Apakah pertanyaannya benar-benar tentang "advatange dari repositori generik" atau lebih "bagaimana menyembunyikan pertanyaan kompleks di belakang antarmuka repositori generik"? Apakah mungkin menjadi generik jika antarmuka dan penggunaannya tergantung pada LINQ? Repositoryapi kami memiliki metode QueryByExample yang sepenuhnya independen dari teknik pencarian dan akan memungkinkan untuk menggeser implementasi.
k3b
"Ini memungkinkan saya menggunakan repositori yang sama untuk melakukan beberapa hal untuk beberapa tipe entitas yang berbeda" => Apakah saya atau Anda salah paham apa itu repositori generik (termasuk yang berkaitan dengan artikel yang disebutkan)? Bagi saya, repositori generik selalu berarti templating semua repo dengan kelas tunggal atau antarmuka, tidak memiliki instance repositori tunggal yang mengelola persistensi semua entitas apa pun jenisnya ...
guillaume31

Jawaban:

35

Repositori generik bahkan tidak berguna (dan IMHO juga buruk) untuk Entity Framework. Itu tidak membawa nilai tambahan apa pun yang sudah disediakan oleh IDbSet<T>(yaitu btw. Generic repository).

Seperti yang telah Anda temukan argumen bahwa repositori generik dapat diganti dengan implementasi untuk teknologi akses data lainnya cukup lemah karena dapat menuntut penulisan penyedia Linq Anda sendiri.

Argumen umum kedua tentang pengujian unit yang disederhanakan juga salah karena mengejek repositori / set dengan penyimpanan data dalam memori menggantikan penyedia Linq dengan yang lain yang memiliki kemampuan berbeda. Penyedia Linq-to-entitas hanya mendukung sebagian dari fitur Linq - bahkan tidak mendukung semua metode yang tersedia pada IQueryable<T>antarmuka. Berbagi pohon ekspresi antara lapisan akses data dan lapisan logika bisnis mencegah pemalsuan lapisan akses data - logika kueri harus dipisahkan.

Jika Anda ingin memiliki abstraksi "generik" yang kuat, Anda harus melibatkan pola-pola lain juga. Dalam hal ini Anda perlu menggunakan bahasa permintaan abstrak yang dapat diterjemahkan oleh repositori ke bahasa permintaan tertentu yang didukung oleh lapisan akses data yang digunakan. Ini ditangani oleh pola Spesifikasi. Linq on IQueryableadalah spesifikasi (tetapi terjemahan membutuhkan penyedia - atau pengunjung kustom yang menerjemahkan pohon ekspresi ke dalam kueri) tetapi Anda dapat menentukan versi Anda sendiri yang disederhanakan dan menggunakannya. Misalnya NHibernate menggunakan Kriteria API. Tetap cara paling sederhana adalah menggunakan repositori spesifik dengan metode tertentu. Cara ini adalah yang paling sederhana untuk diterapkan, paling sederhana untuk menguji dan paling sederhana untuk palsu dalam unit test karena logika kueri sepenuhnya tersembunyi dan dipisahkan di belakang abstraksi.

Ladislav Mrnka
sumber
Hanya pertanyaan singkat, NHibernate memiliki ISessionyang mudah dipermainkan untuk tujuan pengujian unit umum, dan saya dengan senang hati akan melepaskan 'repositori' ketika bekerja dengan EF juga - tetapi apakah ada cara yang lebih mudah dan langsung untuk menciptakan kembali itu? Sesuatu yang mirip ISessiondan ISessionFactory, karena tidak ada IDbContextsejauh yang saya tahu ...
Patryk Ćwiek
Tidak ada tidak IDbContext- jika Anda mau, IDbContextAnda bisa membuatnya dan menerapkannya pada konteks turunan Anda.
Ladislav Mrnka
Jadi Anda mengatakan bahwa untuk aplikasi MVC sehari-hari, IDbSet <T> harus menyediakan repositori generik yang cukup baik untuk melupakan pola repositori sepenuhnya. Kecuali untuk situasi khusus misalnya di mana Anda tidak diperbolehkan referensi DAL dalam proyek MVC Anda.
ProfK
1
Jawaban yang bagus. Beberapa pengembang hanya membuat lapisan abstraksi lain tanpa alasan. IMO, EF tidak perlu repositori generik atau unit kerja (itu adalah built-in)
ashraf
1
IDbSet <T> adalah repositori generik dengan ketergantungan pada Entity Framework yang jenisnya mengalahkan tujuan menggunakan repositori generik untuk menghilangkan ketergantungan pada Entity Framework.
Joel McBeth
7

Masalahnya bukan pola repositori. Memiliki abstraksi antara mendapatkan data dan bagaimana Anda mendapatkannya adalah hal yang baik.

Masalahnya di sini adalah implementasinya. Dengan asumsi bahwa ekspresi sewenang-wenang akan berfungsi untuk memfilter sangat tidak pasti.

Membuat repositori berfungsi untuk semua objek Anda secara langsung agak melenceng. Objek data akan jarang jika pernah memetakan langsung ke objek bisnis. Melewati T untuk memfilter sangat tidak masuk akal dalam situasi ini. Dan memberikan banyak fungsionalitas itu cukup banyak jaminan bahwa Anda tidak dapat mendukung semuanya begitu penyedia lain datang.

Telastyn
sumber
Jadi lebih masuk akal untuk memiliki Repositori per objek data, atau grup objek yang terkait erat, dengan metode spesifik seperti GetById (int id), SortedList (), dll? Apakah saya mengembalikan daftar objek data dari repositori, atau mengubahnya menjadi objek bisnis dengan bidang yang diperlukan juga? Agak berpikir itulah yang terjadi di lapisan Layanan / Bisnis.
Sam
1
@ Sam - Saya cenderung menyukai repositori yang lebih spesifik, ya. Apakah mereka melakukan terjemahan atau tidak tergantung pada di mana hal-hal cenderung berubah. Jika domain Anda didefinisikan dengan baik, saya akan mengembalikan sesuatu dalam bentuk domain. Jika data didefinisikan dengan baik, saya akan mengembalikan hal-hal dalam bentuk struktur data. Jika tidak ada, saya akan memiliki entitas yang berfungsi sebagai dasar yang didefinisikan dengan baik untuk membangun dan kemudian beradaptasi dengan / dari itu.
Telastyn
1
Tidak ada alasan Anda tidak dapat memiliki repositori spesifik yang mendelegasikan hal-hal umum ke repositori umum, tetapi juga memiliki metode repositori khusus tambahan untuk panggilan yang lebih kompleks. Saya telah melihatnya dilakukan seperti itu beberapa kali.
Eric King
@EricKing saya juga punya, dan itu bagus di sana, tetapi cenderung bocor abstraksi karena hal-hal umum cenderung hanya ada karena kesamaan tentang bagaimana data disimpan (GetByID misalnya memerlukan tabel dengan ID).
Telastyn
@ Telastyn Ya, saya setuju dengan itu, tetapi itu terjadi dengan repositori tertentu juga. Abstraksi bocor tidak spesifik untuk repositori generik. (Wow, bisakah saya mengutarakannya dengan lebih ceroboh?)
Eric King
2

Nilai lapisan data generik (repositori adalah jenis lapisan data tertentu) memungkinkan kode untuk mengubah mekanisme penyimpanan yang mendasarinya dengan sedikit atau tanpa dampak pada kode panggilan.

Secara teori, ini bekerja dengan baik. Dalam praktiknya, seperti yang telah Anda amati, abstraksi sering bocor. Mekanisme yang digunakan untuk mengakses data dalam satu berbeda dengan mekanisme yang lain. Dalam beberapa kasus, Anda akhirnya menulis kode dua kali: sekali di lapisan bisnis dan mengulanginya di lapisan data.

Cara paling efektif untuk membuat lapisan data umum adalah dengan mengetahui berbagai jenis sumber data yang akan digunakan aplikasi sebelumnya. Seperti yang Anda lihat, dengan asumsi LINQ atau SQL bersifat universal dapat menjadi masalah. Mencoba retrofit penyimpanan data baru kemungkinan akan menghasilkan penulisan ulang.

[Sunting: Menambahkan yang berikut.]

Ini juga tergantung pada apa yang dibutuhkan aplikasi dari lapisan data. Jika aplikasi hanya memuat atau menyimpan objek, lapisan data bisa sangat sederhana. Karena kebutuhan untuk mencari, mengurutkan dan memfilter meningkat, kompleksitas lapisan data meningkat dan abstraksi mulai menjadi bocor (seperti mengekspos kueri LINQ dalam pertanyaan). Namun, setelah pengguna dapat memberikan kueri sendiri, biaya / manfaat lapisan data perlu ditimbang dengan cermat.

akton
sumber
1

Memiliki lapisan kode di atas database bermanfaat dalam hampir semua kasus. Saya biasanya lebih suka pola "GetByXXXXX" dalam kode tersebut - ini memungkinkan Anda mengoptimalkan permintaan di belakangnya sesuai kebutuhan sambil menjaga UI bebas dari kekacauan antarmuka data.

Mengambil keuntungan dari obat generik jelas merupakan permainan yang adil - memiliki Load<T>(int id)metode membuat banyak akal. Tapi membangun repositori di sekitar LINQ adalah setara dengan 2010 dari menjatuhkan permintaan sql di mana-mana dengan sedikit keamanan jenis tambahan.

Wyatt Barnett
sumber
0

Nah, dengan tautan yang disediakan saya dapat melihat bahwa itu mungkin menjadi pembungkus berguna untuk DataServiceContext, tetapi tidak mengurangi manipulasi kode atau meningkatkan keterbacaan. Selain itu, akses ke DataServiceQuery<T>terhambat, membatasi fleksibilitas untuk .Where()dan .Single(). Juga tidak ada AddRange()atau alternatif yang disediakan. Juga tidak Delete(Predicate)disediakan yang bisa berguna ( repo.Delete<Person>( p => p.Name=="Joe" );untuk menghapus Joe-s). Dll

Kesimpulan: API semacam itu menghalangi API asli dan membatasinya pada beberapa operasi sederhana.

aiodintsov
sumber
Anda benar. Bukankah artikel itu menggunakan pola yang saya miliki dalam kode sampel saya. Akan mencoba mencari tautan ketika saya pulang.
Sam
Tautan yang diperbarui ditambahkan ke bagian bawah pertanyaan.
Sam