Ya, keduanya akan memberi Anda eksekusi yang ditangguhkan .
Perbedaannya adalah IQueryable<T>
antarmuka yang memungkinkan LINQ-to-SQL (LINQ.-to-anything really) bekerja. Jadi jika Anda lebih mempersempit kueri Anda pada IQueryable<T>
, kueri itu akan dieksekusi dalam database, jika memungkinkan.
Untuk IEnumerable<T>
kasus ini, itu akan menjadi LINQ-to-object, yang berarti bahwa semua objek yang cocok dengan permintaan asli harus dimuat ke dalam memori dari database.
Dalam kode:
IQueryable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);
Kode itu akan menjalankan SQL hanya untuk memilih pelanggan emas. Kode berikut, di sisi lain, akan mengeksekusi permintaan asli dalam database, kemudian menyaring pelanggan non-emas di memori:
IEnumerable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);
Ini adalah perbedaan yang cukup penting, dan bekerja pada IQueryable<T>
kaleng dalam banyak kasus menyelamatkan Anda dari mengembalikan terlalu banyak baris dari database. Contoh utama lainnya adalah melakukan paging: Jika Anda menggunakan Take
dan Skip
menghidupkan IQueryable
, Anda hanya akan mendapatkan jumlah baris yang diminta; melakukan itu pada suatu IEnumerable<T>
akan menyebabkan semua baris Anda dimuat dalam memori.
IEnumerable
untukIQueryable
adalah bahwa tidak semua operasi LINQ didukung oleh semua penyedia LINQ. Jadi, selama Anda tahu apa yang Anda lakukan, Anda bisa menggunakanIQueryable
untuk mendorong sebanyak mungkin permintaan ke penyedia LINQ (LINQ2SQL, EF, NHibernate, MongoDB, dll.). Tetapi jika Anda membiarkan kode lain melakukan apa pun yang diinginkan dengan Anda,IQueryable
Anda pada akhirnya akan mendapat masalah karena beberapa kode klien di suatu tempat menggunakan operasi yang tidak didukung. Saya setuju dengan rekomendasi untuk tidak merilisIQueryable
"into the wild" melewati repositori atau lapisan yang setara.Jawaban teratas adalah baik tetapi tidak menyebutkan pohon ekspresi yang menjelaskan "bagaimana" dua antarmuka berbeda. Pada dasarnya, ada dua set ekstensi LINQ yang identik.
Where()
,Sum()
,Count()
,FirstOrDefault()
, Dll semua memiliki dua versi: satu yang menerima fungsi dan satu yang menerima ekspresi.Tanda
IEnumerable
tangan versi adalah:Where(Func<Customer, bool> predicate)
Tanda
IQueryable
tangan versi adalah:Where(Expression<Func<Customer, bool>> predicate)
Anda mungkin telah menggunakan keduanya tanpa menyadarinya karena keduanya disebut menggunakan sintaks identik:
misalnya
Where(x => x.City == "<City>")
bekerja pada keduanyaIEnumerable
danIQueryable
Saat menggunakan
Where()
padaIEnumerable
koleksi, kompiler meneruskan fungsi yang dikompilasi keWhere()
Saat menggunakan
Where()
padaIQueryable
koleksi, kompiler meneruskan pohon ekspresi keWhere()
. Pohon ekspresi seperti sistem refleksi tetapi untuk kode. Kompiler mengubah kode Anda menjadi struktur data yang menjelaskan apa yang kode Anda lakukan dalam format yang mudah dicerna.Kenapa repot-repot dengan pohon ekspresi ini? Saya hanya ingin
Where()
memfilter data saya. Alasan utama adalah bahwa kedua ORM EF dan Linq2SQL dapat mengkonversi pohon ekspresi langsung ke SQL di mana kode Anda akan mengeksekusi lebih cepat.Oh, itu terdengar seperti peningkatan kinerja gratis, haruskah saya menggunakan
AsQueryable()
semua tempat dalam kasus itu? Tidak,IQueryable
hanya berguna jika penyedia data yang mendasarinya dapat melakukan sesuatu dengannya. Mengubah sesuatu seperti biasaList
menjadiIQueryable
tidak akan memberi Anda manfaat apa pun.sumber
IQueryable
tidak fungsi parse sertaIEnumerable
tidak: misalnya, jika Anda ingin tahu mana unsur-unsur dariDBSet<BookEntity>
tidak dalamList<BookObject>
,dbSetObject.Where(e => !listObject.Any(o => o.bookEntitySource == e))
melempar pengecualian:Expression of type 'BookEntity' cannot be used for parameter of type 'BookObject' of method 'Boolean Contains[BookObject] (IEnumerable[BookObject], BookObject)'
. Saya harus menambahkan.ToList()
setelahdbSetObject
.Ya, keduanya menggunakan eksekusi yang ditangguhkan. Mari kita ilustrasikan perbedaan menggunakan profiler SQL Server ....
Ketika kami menjalankan kode berikut:
Dalam SQL Server profiler kami menemukan perintah sama dengan:
Diperlukan waktu sekitar 90 detik untuk menjalankan blok kode itu terhadap tabel WebLog yang memiliki 1 juta catatan.
Jadi, semua catatan tabel dimuat ke dalam memori sebagai objek, dan kemudian dengan masing-masing. Di mana () itu akan menjadi filter lain dalam memori terhadap objek-objek ini.
Ketika kita menggunakan
IQueryable
bukannyaIEnumerable
dalam contoh di atas (baris kedua):Dalam SQL Server profiler kami menemukan perintah sama dengan:
Diperlukan sekitar empat detik untuk menjalankan blok kode ini menggunakan
IQueryable
.IQueryable memiliki properti yang disebut
Expression
yang menyimpan ekspresi pohon yang mulai dibuat ketika kami menggunakanresult
dalam contoh kami (yang disebut eksekusi ditangguhkan), dan pada akhirnya ekspresi ini akan dikonversi ke query SQL untuk dijalankan pada mesin database.sumber
Keduanya akan memberi Anda eksekusi yang ditangguhkan, ya.
Adapun yang lebih disukai daripada yang lain, itu tergantung pada apa sumber data dasar Anda.
Mengembalikan sebuah
IEnumerable
akan secara otomatis memaksa runtime untuk menggunakan LINQ ke Objek untuk menanyakan koleksi Anda.Mengembalikan
IQueryable
(yang mengimplementasikanIEnumerable
, dengan cara) menyediakan fungsionalitas tambahan untuk menerjemahkan permintaan Anda menjadi sesuatu yang mungkin berkinerja lebih baik pada sumber yang mendasarinya (LINQ ke SQL, LINQ ke XML, dll.).sumber
Secara umum saya akan merekomendasikan yang berikut ini:
Kembali
IQueryable<T>
jika Anda ingin mengaktifkan pengembang menggunakan metode Anda untuk memperbaiki kueri yang Anda kembalikan sebelum mengeksekusi.Kembali
IEnumerable
jika Anda ingin memindahkan satu set Objek untuk disebutkan.Bayangkan
IQueryable
sebagai apa itu - "permintaan" untuk data (yang dapat Anda saring jika Anda mau). AnIEnumerable
adalah seperangkat objek (yang telah diterima atau telah dibuat) yang dapat Anda hitung.sumber
Banyak yang telah dikatakan sebelumnya, tetapi kembali ke akarnya, dengan cara yang lebih teknis:
IEnumerable
adalah kumpulan objek dalam memori yang dapat Anda hitung - urutan dalam memori yang memungkinkan untukforeach
diulang (membuatnya mudah untuk dilakukan dalam loop, meskipun Anda hanya dapat menggunakannyaIEnumerator
). Mereka tinggal di memori apa adanya.IQueryable
adalah pohon ekspresi yang akan diterjemahkan ke sesuatu yang lain di beberapa titik dengan kemampuan untuk menghitung hasil akhir . Saya kira inilah yang membingungkan kebanyakan orang.Mereka jelas memiliki konotasi yang berbeda.
IQueryable
mewakili pohon ekspresi (permintaan, sederhana) yang akan diterjemahkan ke sesuatu yang lain oleh penyedia permintaan yang mendasari segera setelah rilis API disebut, seperti fungsi agregat LINQ (Jumlah, Hitungan, dll.) atau Daftar [Array, Kamus,. ..] DanIQueryable
objek juga diimplementasikanIEnumerable
,IEnumerable<T>
sehingga jika mereka merepresentasikan kueri , hasil dari kueri itu bisa diulang. Itu berarti IQueryable tidak harus menjadi pertanyaan saja. Istilah yang tepat adalah mereka pohon ekspresi .Sekarang bagaimana ekspresi itu dieksekusi dan apa yang mereka berubah adalah semua yang disebut penyedia kueri (eksekutor ekspresi kita dapat memikirkannya).
Di dunia Entity Framework (yaitu penyedia sumber data yang mendasari mistis, atau penyedia kueri)
IQueryable
diterjemahkan ke dalam kueri T-SQL asli .Nhibernate
melakukan hal serupa dengan mereka. Anda dapat menulis sendiri mengikuti konsep yang dijelaskan dengan baik di LINQ: Membangun tautan Penyedia IQueryable , misalnya, dan Anda mungkin ingin memiliki API kueri khusus untuk layanan penyedia toko produk Anda.Jadi pada dasarnya,
IQueryable
objek sedang dibangun jauh sampai kita secara eksplisit melepaskannya dan memberitahu sistem untuk menulis ulang mereka ke dalam SQL atau apa pun dan mengirimkan rantai eksekusi untuk diproses selanjutnya.Seolah-olah untuk menunda eksekusi, ini adalah
LINQ
fitur untuk menahan skema pohon ekspresi di memori dan mengirimkannya ke eksekusi hanya berdasarkan permintaan, setiap kali API tertentu dipanggil terhadap urutan (Hitungan yang sama, Daftar, dll.).Penggunaan keduanya sangat tergantung pada tugas yang Anda hadapi untuk kasus tertentu. Untuk pola repositori terkenal saya pribadi memilih untuk kembali
IList
, yaituIEnumerable
lebih dari Daftar (pengindeks dan sejenisnya). Jadi ini adalah saran saya untuk menggunakanIQueryable
hanya di dalam repositori dan IEnumerable di tempat lain dalam kode. Tidak mengatakan tentang kekhawatiran yang dapat diuji yangIQueryable
rusak dan merusak prinsip pemisahan kekhawatiran . Jika Anda mengembalikan ekspresi dari dalam repositori, konsumen dapat bermain dengan lapisan kegigihan seperti yang mereka inginkan.Sedikit tambahan ke kekacauan :) (dari diskusi di komentar)) Tidak satu pun dari mereka adalah objek dalam memori karena mereka bukan tipe nyata per se, mereka adalah penanda jenis - jika Anda ingin pergi sedalam itu. Tetapi masuk akal (dan itulah sebabnya bahkan MSDN mengatakannya seperti ini) untuk memikirkan IEnumerables sebagai koleksi dalam memori sedangkan IQueryables sebagai pohon ekspresi. Intinya adalah bahwa antarmuka IQueryable mewarisi antarmuka IEnumerable sehingga jika itu merupakan permintaan, hasil dari permintaan itu dapat disebutkan. Pencacahan menyebabkan pohon ekspresi yang terkait dengan objek IQueryable dieksekusi. Jadi, sebenarnya, Anda tidak dapat benar-benar memanggil anggota IEnumerable tanpa memiliki objek di memori. Ini akan masuk ke sana jika Anda melakukannya, jika tidak kosong. IQueryables hanyalah pertanyaan, bukan data.
sumber
Secara umum, Anda ingin mempertahankan tipe statis asli kueri sampai masalah.
Untuk alasan ini, Anda dapat mendefinisikan variabel Anda sebagai 'var' dan bukannya
IQueryable<>
atauIEnumerable<>
dan Anda akan tahu bahwa Anda tidak mengubah jenisnya.Jika Anda memulai dengan
IQueryable<>
, Anda biasanya ingin menyimpannyaIQueryable<>
sampai ada alasan kuat untuk mengubahnya. Alasan untuk ini adalah bahwa Anda ingin memberikan prosesor informasi sebanyak mungkin. Misalnya, jika Anda hanya akan menggunakan 10 hasil (Anda telah meneleponTake(10)
) maka Anda ingin SQL Server tahu tentang itu sehingga dapat mengoptimalkan rencana kueri dan mengirimkan hanya data yang akan Anda gunakan.Alasan kuat untuk mengubah jenis dari
IQueryable<>
menjadiIEnumerable<>
mungkin karena Anda memanggil beberapa fungsi ekstensi yang implementasiIQueryable<>
di objek khusus Anda tidak dapat menangani atau menangani secara tidak efisien. Dalam hal itu, Anda mungkin ingin mengonversi tipe keIEnumerable<>
(dengan menetapkan ke variabel tipeIEnumerable<>
atau dengan menggunakanAsEnumerable
metode ekstensi misalnya) sehingga fungsi ekstensi yang Anda panggil akhirnya menjadi yang ada diEnumerable
kelas alih-alihQueryable
kelas.sumber
Ada posting blog dengan contoh kode sumber singkat tentang bagaimana penyalahgunaan
IEnumerable<T>
secara dramatis dapat mempengaruhi kinerja permintaan LINQ: Kerangka Entitas: IQueryable vs IEnumerable .Jika kita menggali lebih dalam dan mencari sumbernya, kita dapat melihat bahwa ada beberapa metode ekstensi yang berbeda untuk
IEnumerable<T>
:dan
IQueryable<T>
:Yang pertama mengembalikan iterator yang dapat dihitung, dan yang kedua membuat kueri melalui penyedia kueri, ditentukan dalam
IQueryable
sumber.sumber
Saya baru-baru ini mengalami masalah dengan
IEnumerable
vIQueryable
. Algoritme yang digunakan pertama kali melakukanIQueryable
kueri untuk mendapatkan serangkaian hasil. Ini kemudian diteruskan keforeach
loop, dengan item yang dipakai sebagai kelas Entity Framework (EF). Kelas EF ini kemudian digunakan dalamfrom
klausa kueri Linq to Entity, menyebabkan hasilnyaIEnumerable
.Saya cukup baru di EF dan Linq untuk Entitas, jadi butuh beberapa saat untuk mencari tahu apa hambatannya. Dengan menggunakan MiniProfiling, saya menemukan kueri dan kemudian mengonversi semua operasi individual menjadi satu
IQueryable
Linq untuk kueri Entitas. TheIEnumerable
mengambil 15 detik danIQueryable
mengambil 0,5 detik untuk mengeksekusi. Ada tiga tabel yang terlibat dan, setelah membaca ini, saya percaya bahwaIEnumerable
kueri sebenarnya membentuk produk silang tiga tabel dan memfilter hasilnya.Cobalah untuk menggunakan IQueryables sebagai patokan dan buat profil pekerjaan Anda untuk membuat perubahan Anda terukur.
sumber
IQueryable
s juga terjebak dalam memori setelah Anda memanggil salah satu dari API tersebut tetapi jika tidak, Anda dapat meneruskan ekspresi ke atas tumpukan layer dan bermain-main dengan filter sampai panggilan API. DAL yang dirancang dengan baik sebagai Repositori yang dirancang dengan baik akan menyelesaikan masalah seperti ini;)Saya ingin mengklarifikasi beberapa hal karena tanggapan yang tampaknya saling bertentangan (kebanyakan seputar IEnumerable).
(1)
IQueryable
memperluasIEnumerable
antarmuka. (Anda dapat mengirimIQueryable
ke sesuatu yang diharapkanIEnumerable
tanpa kesalahan.)(2) Keduanya
IQueryable
danIEnumerable
LINQ mencoba memuat dengan malas saat iterasi pada set hasil. (Perhatikan bahwa implementasi dapat dilihat dalam metode ekstensi antarmuka untuk setiap jenis.)Dengan kata lain,
IEnumerables
tidak secara eksklusif "dalam memori".IQueryables
tidak selalu dieksekusi pada database.IEnumerable
harus memuat barang ke dalam memori (sekali diambil, mungkin malas) karena tidak memiliki penyedia data abstrak.IQueryables
mengandalkan penyedia abstrak (seperti LINQ-to-SQL), meskipun ini juga bisa menjadi penyedia NET. Memori.Contoh penggunaan case
(a) Ambil daftar catatan
IQueryable
dari konteks EF. (Tidak ada catatan di memori.)(B) Lulus
IQueryable
ke tampilan model siapaIEnumerable
. (Valid.IQueryable
MeluasIEnumerable
.)(c) Iterate over dan akses catatan set data, entitas anak, dan properti dari tampilan. (Dapat menyebabkan pengecualian!)
Kemungkinan Masalah
(1)
IEnumerable
Upaya pemuatan malas dan konteks data Anda kedaluwarsa. Pengecualian dilemparkan karena penyedia tidak lagi tersedia.(2) Proksi entitas Kerangka Entitas diaktifkan (default), dan Anda berupaya mengakses objek terkait (virtual) dengan konteks data yang kedaluwarsa. Sama seperti (1).
(3) Beberapa Set Hasil Aktif (MARS). Jika Anda iterasi atas
IEnumerable
dalamforeach( var record in resultSet )
blok dan secara bersamaan mencoba untuk aksesrecord.childEntity.childProperty
, Anda mungkin berakhir dengan MARS karena malas loading dari kedua set data dan entitas relasional. Ini akan menyebabkan pengecualian jika tidak diaktifkan di string koneksi Anda.Larutan
Jalankan permintaan dan simpan hasil dengan memohon
resultList = resultSet.ToList()
Ini tampaknya menjadi cara paling mudah untuk memastikan entitas Anda berada di memori.Dalam kasus di mana Anda mengakses entitas terkait, Anda mungkin masih memerlukan konteks data. Entah itu, atau Anda dapat menonaktifkan proxy entitas dan
Include
entitas terkait yang eksplisit dari AndaDbSet
.sumber
Perbedaan utama antara "IEnumerable" dan "IQueryable" adalah tentang di mana logika filter dijalankan. Satu dijalankan di sisi klien (dalam memori) dan yang lainnya dijalankan pada database.
Sebagai contoh, kita dapat mempertimbangkan contoh di mana kita memiliki 10.000 catatan untuk pengguna dalam basis data kita dan katakan saja hanya 900 yang merupakan pengguna aktif, jadi dalam kasus ini jika kita menggunakan "IEnumerable" maka pertama-tama memuat semua 10.000 catatan dalam memori dan kemudian menerapkan filter IsActive di atasnya yang akhirnya mengembalikan 900 pengguna aktif.
Sementara di sisi lain pada kasus yang sama jika kita menggunakan "IQueryable" itu akan langsung menerapkan filter IsActive pada database yang langsung dari sana akan mengembalikan 900 pengguna aktif.
Tautan Referensi
sumber
Kita dapat menggunakan keduanya dengan cara yang sama, dan mereka hanya berbeda dalam kinerjanya.
IQueryable hanya mengeksekusi terhadap database secara efisien. Ini berarti bahwa ia menciptakan seluruh kueri pemilihan dan hanya mendapatkan catatan terkait.
Misalnya, kami ingin mengambil 10 pelanggan teratas yang namanya dimulai dengan 'Nimal'. Dalam hal ini kueri pemilihan akan dihasilkan sebagai
select top 10 * from Customer where name like ‘Nimal%’
.Tetapi jika kita menggunakan IEnumerable, kueri akan seperti
select * from Customer where name like ‘Nimal%’
dan sepuluh teratas akan difilter pada level pengkodean C # (ia mendapatkan semua catatan pelanggan dari basis data dan meneruskannya ke dalam C #).sumber
Selain 2 jawaban pertama yang sangat baik (oleh driis & oleh Jacob):
Objek IEnumerable mewakili satu set data dalam memori dan dapat bergerak maju hanya pada data ini. Kueri yang diwakili oleh objek IEnumerable dijalankan segera dan sepenuhnya, sehingga aplikasi menerima data dengan cepat.
Ketika kueri dieksekusi, IEnumerable memuat semua data, dan jika kita perlu menyaringnya, penyaringan itu sendiri dilakukan di sisi klien.
Objek IQueryable menyediakan akses jarak jauh ke database dan memungkinkan Anda menavigasi data baik dalam urutan langsung dari awal hingga akhir, atau dalam urutan terbalik. Dalam proses membuat kueri, objek yang dikembalikan adalah IQueryable, kueri dioptimalkan. Akibatnya, lebih sedikit memori yang dikonsumsi selama eksekusi, lebih sedikit bandwidth jaringan, tetapi pada saat yang sama dapat diproses sedikit lebih lambat dari permintaan yang mengembalikan objek IEnumerable.
Apa yang harus dipilih?
Jika Anda memerlukan seluruh rangkaian data yang dikembalikan, maka lebih baik menggunakan IEnumerable, yang menyediakan kecepatan maksimum.
Jika Anda TIDAK membutuhkan seluruh rangkaian data yang dikembalikan, tetapi hanya beberapa data yang difilter, maka lebih baik menggunakan IQueryable.
sumber
Selain hal di atas, menarik untuk dicatat bahwa Anda bisa mendapatkan pengecualian jika Anda menggunakan
IQueryable
alih-alihIEnumerable
:Berikut ini bekerja dengan baik jika
products
merupakanIEnumerable
:Namun jika itu
products
adalahIQueryable
dan sedang mencoba mengakses catatan dari tabel DB, maka Anda akan mendapatkan kesalahan ini:Ini karena kueri berikut dibuat:
dan OFFSET tidak dapat memiliki nilai negatif.
sumber