Haruskah Repositori mengembalikan IQueryable?

66

Saya telah melihat banyak proyek yang memiliki repositori yang mengembalikan instance IQueryable. Ini memungkinkan filter tambahan dan penyortiran dapat dilakukan IQueryableoleh kode lain, yang diterjemahkan menjadi SQL yang berbeda yang dihasilkan. Saya ingin tahu dari mana pola ini berasal dan apakah itu ide yang bagus.

Kekhawatiran terbesar saya adalah bahwa sebuah IQueryablejanji untuk mencapai database beberapa waktu kemudian, ketika disebutkan. Ini berarti bahwa kesalahan akan terjadi di luar repositori. Ini bisa berarti pengecualian Entity Framework dilemparkan ke dalam lapisan aplikasi yang berbeda.

Saya juga mengalami masalah dengan Multiple Active Result Set (MARS) di masa lalu (terutama ketika menggunakan transaksi) dan pendekatan ini sepertinya akan menyebabkan hal ini terjadi lebih sering.

Saya selalu menelepon AsEnumerableatau ToArraydi akhir setiap ekspresi LINQ saya untuk memastikan database terkena sebelum meninggalkan kode repositori.

Saya bertanya-tanya apakah pengembalian IQueryabledapat berguna sebagai blok bangunan untuk lapisan data. Saya telah melihat beberapa kode yang sangat mewah dengan satu repositori memanggil repositori lain untuk membangun yang lebih besar lagi IQueryable.

Taman Travis
sumber
Apa perbedaannya jika db tidak tersedia pada saat eksekusi query yang ditunda vs saat konstruksi query? Mengkonsumsi kode harus berurusan dengan situasi itu.
Steven Evers
Pertanyaan yang menarik, tetapi mungkin akan sulit dijawab. Pencarian sederhana menunjukkan perdebatan yang cukup panas tentang pertanyaan Anda: duckduckgo.com/?q=repository+return+iqueryable
Corbin
6
Itu semua bermuara pada apakah Anda ingin mengizinkan klien Anda menambahkan klausa Linq yang dapat mengambil manfaat dari eksekusi yang ditangguhkan. Jika mereka menambahkan whereklausul untuk ditangguhkan IQueryable, Anda hanya perlu mengirim bahwa data melalui kawat, tidak seluruh hasil set.
Robert Harvey
Jika Anda menggunakan pola repositori - tidak, Anda tidak boleh mengembalikan IQueryable. Inilah alasannya .
Arnis Lapsa
1
@SteveEvers Ada perbedaan besar. Jika pengecualian dilemparkan ke repositori saya, saya bisa membungkusnya dengan tipe pengecualian khusus lapisan data. Jika itu terjadi nanti, saya harus menangkap jenis pengecualian asli di sana. Semakin dekat saya dengan sumber pengecualian, semakin besar kemungkinan saya akan tahu apa yang menyebabkannya. Ini penting untuk membuat pesan kesalahan yang bermakna. IMHO, mengizinkan pengecualian khusus EF untuk meninggalkan repositori saya merupakan pelanggaran enkapsulasi.
Travis Parks

Jawaban:

47

Mengembalikan IQueryable pasti akan memberikan lebih banyak fleksibilitas kepada konsumen repositori. Ini menempatkan tanggung jawab mempersempit hasil kepada klien, yang secara alami dapat bermanfaat sekaligus sebagai penopang.

Sisi baiknya, Anda tidak perlu membuat banyak metode repositori (setidaknya pada layer ini) - GetAllActiveItems, GetAllNonActiveItems, dll - untuk mendapatkan data yang Anda inginkan. Tergantung pada preferensi Anda, sekali lagi, ini bisa baik atau buruk. Anda akan (harus) mendefinisikan kontrak perilaku yang dipatuhi oleh implementasi Anda, tetapi kemana Anda akan pergi.

Jadi Anda bisa meletakkan logika pencarian berpasir di luar repositori dan membiarkannya digunakan sesuai keinginan pengguna. Jadi mengekspos IQueryable memberikan fleksibilitas yang paling dan memungkinkan untuk query yang efisien dibandingkan dengan penyaringan dalam memori, dll, dan dapat mengurangi kebutuhan untuk membuat satu ton metode pengambilan data tertentu.

Di sisi lain, sekarang Anda telah memberi pengguna Anda senapan. Mereka dapat melakukan hal-hal yang mungkin tidak Anda maksudkan (terlalu sering menggunakan .include (), melakukan permintaan berat yang berat dan melakukan penyaringan dalam memori dalam implementasi masing-masing, dll), yang pada dasarnya akan meminggirkan kontrol pelapisan dan perilaku karena Anda telah memberikan akses penuh.

Jadi tergantung pada tim, pengalaman mereka, ukuran aplikasi, layering keseluruhan dan arsitektur ... itu tergantung: - \

Hanzolo
sumber
Perhatikan bahwa Anda bisa, jika Anda ingin bekerja untuk itu, mengembalikan IQueryable Anda, yang pada gilirannya memanggil DB, tetapi menambahkan kontrol layering dan perilaku.
psr
1
@psr Bisakah Anda menjelaskan apa yang Anda maksud dengan ini?
Travis Parks
1
@ TravisParks - IQueryable tidak harus diimplementasikan oleh DB, Anda dapat menulis sendiri kelas IQueryable - hanya saja cukup sulit. Jadi Anda bisa menulis pembungkus IQueryable di sekitar database IQueryable, dan minta batas penuh IQueryable Anda akses ke DB yang mendasarinya. Tetapi ini cukup sulit sehingga saya tidak berharap hal itu dilakukan secara luas.
psr
2
Perhatikan bahwa bahkan ketika Anda tidak mengembalikan IQueryables, Anda masih dapat mencegah keharusan membuat banyak metode (GetAllActiveItems, GetAllNonActiveItems, dll.) Hanya dengan meneruskan sebuah Expression<Func<Foo,bool>>ke metode Anda. Parameter ini dapat diteruskan ke Wheremetode secara langsung, sehingga memungkinkan konsumen memilih filter mereka tanpa perlu akses langsung ke <Foo> IQueryable.
Flater
1
@hanzolo: Tergantung pada apa yang Anda maksud dengan refactoring. Memang benar bahwa mengubah desain (misalnya menambahkan bidang baru atau mengubah hubungan) lebih menyebalkan ketika Anda memiliki basis kode berlapis dan longgar; tetapi refactoring tidak terlalu merepotkan ketika Anda hanya perlu mengubah satu layer. Itu semua tergantung pada apakah Anda berharap untuk mendesain ulang aplikasi, atau apakah Anda hanya refactoring implementasi Anda dari arsitektur dasar yang sama. Saya masih akan menyarankan pemasangan longgar dalam kedua kasus tetapi Anda tidak salah bahwa itu menambah refactoring ketika mengubah konsep domain inti.
Flater
29

Mengekspos IQueryable ke antarmuka publik bukanlah praktik yang baik. Alasannya mendasar: Anda tidak dapat memberikan realisasi IQueryable seperti yang dikatakan.

Antarmuka publik adalah kontrak antara penyedia dan klien. Dan dalam kebanyakan kasus ada harapan implementasi penuh. IQueryable adalah cheat:

  1. IQueryable hampir mustahil untuk diterapkan. Dan saat ini tidak ada solusi yang tepat. Bahkan Kerangka Entitas memiliki banyak Eksepsi yang Tidak Didukung .
  2. Hari ini penyedia Queryable memiliki ketergantungan besar pada infrastruktur. Sharepoint memiliki implementasi parsial sendiri dan penyedia SQL saya memiliki implementasi parsial yang berbeda.

Pola repositori memberi kita representasi yang jelas dengan melapisi, dan, yang paling penting, independensi realisasi. Jadi kita bisa mengubah sumber data di masa depan.

Pertanyaannya adalah " Haruskah ada kemungkinan substitusi sumber data? ".

Jika besok bagian dari data dapat dipindahkan ke layanan SAP, basis data NoSQL, atau hanya ke file teks, dapatkah kami menjamin implementasi yang tepat dari antarmuka IQueryable?

( lebih banyak poin bagus tentang mengapa ini adalah praktik buruk)

Artru
sumber
Jawaban ini tidak berdiri sendiri tanpa berkonsultasi dengan tautan eksternal yang tidak stabil. Pertimbangkan untuk merangkum setidaknya poin-poin utama yang dibuat oleh sumber daya eksternal, dan cobalah untuk menjaga pendapat pribadi dari jawaban Anda ("ide terburuk yang pernah saya dengar"). Juga pertimbangkan untuk menghapus cuplikan kode yang tampaknya tidak terkait dengan apa pun yang dapat IQueryable.
Lars Viklund
1
Terima kasih @LarsViklund, jawabannya diperbarui dengan contoh dan deskripsi masalah
Artru
19

Secara realistis, Anda memiliki tiga alternatif jika Anda ingin eksekusi yang ditangguhkan:

  • Lakukan dengan cara ini - memaparkan IQueryable.
  • Terapkan struktur yang memaparkan metode spesifik untuk filter atau "pertanyaan" tertentu. ( GetCustomersInAlaskaWithChildren, di bawah)
  • Menerapkan struktur yang memperlihatkan API filter / urutkan / halaman yang diketik dengan kuat dan membangunnya secara IQueryableinternal.

Saya lebih suka yang ketiga (meskipun saya sudah membantu rekan menerapkan yang kedua juga), tetapi jelas ada beberapa pengaturan dan pemeliharaan yang terlibat. (T4 untuk menyelamatkan!)

Sunting : Untuk menjernihkan kebingungan seputar apa yang saya bicarakan, pertimbangkan contoh berikut yang dicuri dari IQueryable Can Kill Your Dog, Steal Your Wife, Kill Your Will to Live, dll.

Dalam skenario di mana Anda akan mengekspos sesuatu seperti ini:

public class CustomerRepo : IRepo 
{ 
     private DataContext ct; 
     public Customer GetCustomerById(int id) { ... } 
     public Customer[] GetCustomersInAlaskaWithChildren() { ... } 
} 

API yang saya bicarakan akan memungkinkan Anda mengekspos metode yang akan memungkinkan Anda mengekspresikan GetCustomersInAlaskaWithChildren(atau kombinasi kriteria lainnya) secara fleksibel, dan repo akan menjalankannya sebagai IQueryable dan mengembalikan hasilnya kepada Anda. Tetapi yang penting adalah bahwa ia berjalan di dalam lapisan repositori, sehingga masih mengambil keuntungan dari eksekusi yang ditangguhkan. Setelah Anda mendapatkan hasilnya kembali, Anda masih dapat LINQ-to-objek di dalamnya untuk sesuka hati Anda.

Keuntungan lain dari pendekatan seperti ini adalah, karena mereka adalah kelas POCO, repositori ini dapat hidup di belakang layanan web atau WCF; itu bisa diekspos ke AJAX atau penelepon lain yang tidak tahu apa-apa tentang LINQ.

GalacticCowboy
sumber
4
Jadi, Anda akan membangun API kueri untuk menggantikan API kueri bawaan .NET?
Michael Brown
4
Kedengarannya tidak ada gunanya bagi saya
ozz
7
@MikeBrown Maksud pertanyaan ini - dan jawaban saya - adalah bahwa Anda ingin mengekspos perilaku atau keuntungan dari IQueryable tanpa mengekspos IQueryableitu sendiri . Bagaimana Anda akan melakukannya, dengan API bawaan?
GalacticCowboy
2
Itu tautologi yang sangat bagus. Anda belum menjelaskan mengapa Anda ingin menghindarinya. Layanan Data WCF Heck mengekspos IQueryable di seluruh kawat. Saya tidak mencoba untuk memilih Anda, saya hanya tidak memahami keengganan untuk IQueryable orang tampaknya miliki.
Michael Brown
2
Jika Anda hanya akan menggunakan IQueryable, lalu mengapa Anda bahkan menggunakan repositori? Repositori dimaksudkan untuk menyembunyikan detail implementasi. (Juga, Layanan Data WCF sedikit lebih baru daripada sebagian besar solusi yang telah saya kerjakan dalam 4 tahun terakhir yang menggunakan repositori seperti ini ... Jadi tidak mungkin mereka akan diperbarui dalam waktu dekat hanya untuk membuat penggunaan mainan baru yang mengkilap.)
GalacticCowboy
8

Hanya ada satu jawaban yang sah: itu tergantung pada bagaimana repositori akan digunakan.

Pada satu titik repositori Anda adalah pembungkus yang sangat tipis di sekitar DBContext sehingga Anda dapat menyuntikkan veneer testability di sekitar aplikasi berbasis database. Benar-benar tidak ada harapan dunia nyata bahwa ini dapat digunakan dalam cara yang terputus tanpa DB LINQ ramah di belakangnya karena aplikasi CRUD Anda tidak akan pernah membutuhkan itu. Maka tentu saja, mengapa tidak menggunakan IQueryable? Saya mungkin lebih suka IEnumarable karena Anda mendapatkan sebagian besar manfaat [baca: eksekusi tertunda] dan tidak terasa kotor.

Kecuali jika Anda duduk pada posisi ekstrem itu, saya akan berusaha keras untuk mengambil keuntungan dari semangat pola repositori dan mengembalikan koleksi terwujud yang sesuai yang tidak memiliki implikasi koneksi database yang mendasarinya.

Wyatt Barnett
sumber
6
Mengenai pengembalian IEnumerable, penting untuk dicatat bahwa ((IEnumerable<T>)query).LastOrDefault()ini sangat berbeda dari query.LastOrDefault()(anggap querysebagai IQueryable). Jadi, masuk akal untuk kembali IEnumerablejika Anda akan mengulangi semua elemen.
Lou
7
 > Should Repositories return IQueryable?

TIDAK jika Anda ingin melakukan pengembangan / unittesting testdriven karena tidak ada cara mudah untuk membuat repositori tiruan untuk menguji businesslogic Anda (= repositori-konsumen) dalam isolasi dari database atau penyedia IQueryable lainnya.

k3b
sumber
6

Dari sudut pandang arsitektur murni, IQueryable adalah abstraksi yang bocor. Secara umum, ini memberi pengguna kekuatan terlalu banyak. Yang sedang berkata, ada tempat di mana itu masuk akal. Jika Anda menggunakan OData, IQueryable membuatnya sangat mudah untuk memberikan titik akhir yang mudah difilter, disortir, dapat dikelompokkan ... dll. Saya sebenarnya lebih suka membuat DTO yang dipetakan oleh entitas saya dan sebaliknya IQueryable mengembalikan DTO. Dengan begitu saya melakukan pra-filter data saya dan benar-benar hanya menggunakan metode IQueryable untuk memungkinkan kustomisasi klien dari hasil.

Slick86
sumber
6

Saya telah menghadapi pilihan seperti itu juga.

Jadi, mari kita simpulkan sisi positif dan negatif:

Positif:

  • Fleksibilitas. Jelas fleksibel dan nyaman untuk memungkinkan sisi klien membangun kueri khusus hanya dengan satu metode penyimpanan

Negatif:

  • Tidak dapat diuji . (Anda akan benar-benar menguji implementasi IQueryable yang mendasarinya, yang biasanya ORM Anda. Tetapi Anda harus menguji logika Anda sebagai gantinya)
  • Mengaburkan tanggung jawab . Tidak mungkin untuk mengatakan, apa yang dilakukan metode repositori tertentu. Sebenarnya, ini adalah metode dewa, yang dapat mengembalikan apa pun yang Anda suka di seluruh basis data Anda
  • Tidak aman . Tanpa batasan pada apa yang dapat ditanyakan dengan metode repositori ini, dalam beberapa kasus implementasi seperti itu bisa tidak aman dari titik keamanan.
  • Masalah caching (atau, secara umum, masalah sebelum atau sesudah pemrosesan). Pada umumnya tidak bijaksana untuk, misalnya, menyimpan semua yang ada di aplikasi Anda. Anda akan mencoba untuk men-cache sesuatu, yang menyebabkan beban signifikan basis data Anda. Tetapi bagaimana cara melakukannya, jika Anda hanya memiliki satu metode repositori, yang digunakan klien untuk mengeluarkan hampir semua permintaan terhadap database?
  • Masalah pembalakan . Log sesuatu di lapisan repositori akan membuat Anda memeriksa IQueryable terlebih dahulu untuk memeriksa apakah panggilan khusus ini dicatat atau tidak.

Saya akan merekomendasikan untuk tidak menggunakan pendekatan ini. Sebaiknya pertimbangkan membuat beberapa Spesifikasi dan menerapkan metode repositori, yang dapat meminta basis data dengan menggunakannya. Pola spesifikasi adalah cara terbaik untuk merangkum serangkaian kondisi.

Vladislav Rastrusny
sumber
5

Saya pikir mengekspos IQueryable pada repositori Anda dapat diterima selama fase awal pengembangan. Ada alat UI yang dapat bekerja dengan IQueryable secara langsung dan menangani pengurutan, pemfilteran, pengelompokan, dll.

Yang sedang berkata, saya pikir hanya mengekspos mentah-mentah di repositori dan menyebutnya sehari tidak bertanggung jawab. Memiliki operasi yang bermanfaat dan bantuan kueri untuk kueri umum memungkinkan Anda memusatkan logika untuk kueri sembari juga memaparkan permukaan antarmuka yang lebih kecil kepada konsumen repositori.

Untuk beberapa iterasi pertama suatu proyek, mengekspos IQueryable secara publik di repositori memungkinkan untuk pertumbuhan yang lebih cepat. Sebelum rilis penuh pertama, saya akan merekomendasikan menjadikan operasi kueri menjadi pribadi atau terlindungi dan membawa kueri liar di bawah satu atap.

Michael Brown
sumber
4

Apa yang saya lihat dilakukan (dan apa yang saya laksanakan sendiri) adalah sebagai berikut:

public interface IEntity<TKey>
{
    TKey Id { get; set; }
}

public interface IRepository<TEntity, in TKey> where TEntity : IEntity<TKey>
{
    void Create(TEntity entity);
    TEntity Get(TKey key);
    IEnumerable<TEntity> GetAll();
    IEnumerable<TEntity> GetAll(Expression<Func<TEntity, bool>> expression);
    void Update(TEntity entity);
    void Delete(TKey id);
}

Apa yang memungkinkan Anda lakukan adalah memiliki fleksibilitas melakukan IQueryable.Where(a => a.SomeFilter)kueri pada repo Anda dengan melakukannya concreteRepo.GetAll(a => a.SomeFilter)tanpa memaparkan bisnis LINQy apa pun.

dav_i
sumber