Ada banyak yang bisa dikatakan tentang ini. Mari saya fokus pada AsEnumerable
dan AsQueryable
dan menyebutkan ToList()
sepanjang jalan.
Apa yang dilakukan metode ini?
AsEnumerable
dan AsQueryable
cor atau mengkonversi ke IEnumerable
atau IQueryable
, masing-masing. Saya katakan cast atau convert dengan alasan:
Ketika objek sumber sudah mengimplementasikan antarmuka target, objek sumber itu sendiri dikembalikan tetapi dilemparkan ke antarmuka target. Dengan kata lain: tipe tidak berubah, tetapi tipe waktu kompilasi adalah.
Ketika objek sumber tidak mengimplementasikan antarmuka target, objek sumber dikonversi menjadi objek yang mengimplementasikan antarmuka target. Jadi tipe dan tipe waktu kompilasi diubah.
Izinkan saya menunjukkan ini dengan beberapa contoh. Saya punya metode kecil ini yang melaporkan tipe kompilasi-waktu dan tipe aktual objek ( seizin Jon Skeet ):
void ReportTypeProperties<T>(T obj)
{
Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
Console.WriteLine("Actual type: {0}", obj.GetType().Name);
}
Mari kita coba Linq-to-sql Table<T>
yang berubah-ubah , yang mengimplementasikan IQueryable
:
ReportTypeProperties(context.Observations);
ReportTypeProperties(context.Observations.AsEnumerable());
ReportTypeProperties(context.Observations.AsQueryable());
Hasil:
Compile-time type: Table`1
Actual type: Table`1
Compile-time type: IEnumerable`1
Actual type: Table`1
Compile-time type: IQueryable`1
Actual type: Table`1
Anda melihat bahwa kelas tabel itu sendiri selalu dikembalikan, tetapi perwakilannya berubah.
Sekarang objek yang mengimplementasikan IEnumerable
, bukan IQueryable
:
var ints = new[] { 1, 2 };
ReportTypeProperties(ints);
ReportTypeProperties(ints.AsEnumerable());
ReportTypeProperties(ints.AsQueryable());
Hasil:
Compile-time type: Int32[]
Actual type: Int32[]
Compile-time type: IEnumerable`1
Actual type: Int32[]
Compile-time type: IQueryable`1
Actual type: EnumerableQuery`1
Itu ada. AsQueryable()
telah mengonversi array menjadi EnumerableQuery
, yang "mewakili IEnumerable<T>
koleksi sebagai IQueryable<T>
sumber data." (MSDN).
Apa gunanya?
AsEnumerable
sering digunakan untuk beralih dari setiap IQueryable
implementasi ke LINQ ke objek (L2O), sebagian besar karena mantan tidak mendukung fungsi yang dimiliki L2O. Untuk detail lebih lanjut lihat Apa efek AsEnumerable () pada Entitas LINQ? .
Misalnya, dalam kueri Entity Framework kita hanya bisa menggunakan sejumlah metode terbatas. Jadi, jika, misalnya, kita perlu menggunakan salah satu metode kita sendiri dalam kueri, kita biasanya akan menulis sesuatu seperti
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => MySuperSmartMethod(x))
ToList
- yang mengubah suatu IEnumerable<T>
ke List<T>
- sering juga digunakan untuk tujuan ini. Keuntungan menggunakan AsEnumerable
vs. ToList
adalah AsEnumerable
tidak menjalankan query. AsEnumerable
mempertahankan eksekusi yang ditangguhkan dan tidak membangun daftar perantara yang sering tidak berguna.
Di sisi lain, ketika eksekusi yang diinginkan dari permintaan LINQ diinginkan, ToList
bisa menjadi cara untuk melakukan itu.
AsQueryable
dapat digunakan untuk membuat koleksi yang dapat diterima menerima ekspresi dalam pernyataan LINQ. Lihat di sini untuk detail lebih lanjut: Apakah saya benar-benar perlu menggunakan AsQueryable () pada koleksi? .
Catatan tentang penyalahgunaan zat!
AsEnumerable
bekerja seperti narkoba. Ini perbaikan cepat, tetapi dengan biaya dan tidak mengatasi masalah yang mendasarinya.
Dalam banyak jawaban Stack Overflow, saya melihat orang yang mendaftar AsEnumerable
untuk memperbaiki masalah apa pun dengan metode yang tidak didukung dalam ekspresi LINQ. Tapi harganya tidak selalu jelas. Misalnya, jika Anda melakukan ini:
context.MyLongWideTable // A table with many records and columns
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate })
... semuanya diterjemahkan dengan rapi ke dalam pernyataan SQL yang memfilter ( Where
) dan proyek ( Select
). Artinya, baik panjang dan lebar, masing-masing, dari himpunan hasil SQL berkurang.
Sekarang anggaplah pengguna hanya ingin melihat bagian tanggal dari CreateDate
. Dalam Kerangka Entitas Anda akan dengan cepat menemukan bahwa ...
.Select(x => new { x.Name, x.CreateDate.Date })
... tidak didukung (pada saat penulisan). Ah, untungnya ada AsEnumerable
perbaikannya:
context.MyLongWideTable.AsEnumerable()
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate.Date })
Tentu, ini berjalan, mungkin. Tapi itu menarik seluruh tabel ke dalam memori dan kemudian menerapkan filter dan proyeksi. Yah, kebanyakan orang cukup pintar untuk melakukan yang Where
pertama:
context.MyLongWideTable
.Where(x => x.Type == "type").AsEnumerable()
.Select(x => new { x.Name, x.CreateDate.Date })
Namun tetap semua kolom diambil terlebih dahulu dan proyeksi dilakukan dalam memori.
Perbaikan sebenarnya adalah:
context.MyLongWideTable
.Where(x => x.Type == "type")
.Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) })
(Tapi itu hanya membutuhkan sedikit pengetahuan ...)
Apa yang tidak dilakukan metode ini?
Kembalikan kemampuan IQueryable
Sekarang peringatan penting. Saat kamu melakukan
context.Observations.AsEnumerable()
.AsQueryable()
Anda akan berakhir dengan objek sumber direpresentasikan sebagai IQueryable
. (Karena kedua metode hanya menggunakan dan tidak mengkonversi).
Tetapi ketika Anda melakukannya
context.Observations.AsEnumerable().Select(x => x)
.AsQueryable()
apa hasilnya?
The Select
menghasilkan WhereSelectEnumerableIterator
. Ini adalah kelas .Net internal yang mengimplementasikan IEnumerable
, bukanIQueryable
. Jadi konversi ke jenis lain telah terjadi dan selanjutnya AsQueryable
tidak akan pernah dapat mengembalikan sumber aslinya lagi.
Implikasi dari hal ini adalah bahwa menggunakan AsQueryable
adalah bukan cara yang ajaib menyuntikkan penyedia permintaan dengan spesifik fitur menjadi enumerable. Misalkan Anda melakukannya
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => x.ToString())
.AsQueryable()
.Where(...)
Kondisi di mana tidak akan pernah diterjemahkan ke dalam SQL. AsEnumerable()
diikuti oleh pernyataan LINQ yang secara definitif memutus koneksi dengan penyedia query framework framework.
Saya sengaja menunjukkan contoh ini karena saya telah melihat pertanyaan di sini di mana orang-orang misalnya mencoba 'menyuntikkan' Include
kemampuan ke dalam koleksi dengan menelepon AsQueryable
. Ini mengkompilasi dan menjalankan, tetapi tidak melakukan apa-apa karena objek yang mendasarinya tidak memiliki Include
implementasi lagi.
Menjalankan
Keduanya AsQueryable
dan AsEnumerable
tidak mengeksekusi (atau menyebutkan ) objek sumber. Mereka hanya mengubah tipe atau representasi mereka. Kedua antarmuka yang terlibat, IQueryable
dan IEnumerable
, tidak lain adalah "enumerasi yang menunggu untuk terjadi". Mereka tidak dieksekusi sebelum mereka dipaksa untuk melakukannya, misalnya, seperti yang disebutkan di atas, dengan menelepon ToList()
.
Itu berarti bahwa mengeksekusi IEnumerable
diperoleh dengan menelepon AsEnumerable
pada IQueryable
objek, akan mengeksekusi mendasari IQueryable
. Eksekusi selanjutnya IEnumerable
akan kembali mengeksekusi IQueryable
. Yang mungkin sangat mahal.
Implementasi khusus
Sejauh ini, ini hanya tentang Queryable.AsQueryable
dan Enumerable.AsEnumerable
metode penyuluhan. Tetapi tentu saja siapa pun dapat menulis metode instan atau metode ekstensi dengan nama (dan fungsi) yang sama.
Bahkan, contoh umum dari AsEnumerable
metode ekstensi spesifik adalah DataTableExtensions.AsEnumerable
. DataTable
tidak menerapkan IQueryable
atau IEnumerable
, jadi metode ekstensi reguler tidak berlaku.