Ada banyak yang bisa dikatakan tentang ini. Mari saya fokus pada AsEnumerabledan AsQueryabledan menyebutkan ToList()sepanjang jalan.
Apa yang dilakukan metode ini?
AsEnumerabledan AsQueryablecor atau mengkonversi ke IEnumerableatau 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?
AsEnumerablesering digunakan untuk beralih dari setiap IQueryableimplementasi 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 AsEnumerablevs. ToListadalah AsEnumerabletidak menjalankan query. AsEnumerablemempertahankan eksekusi yang ditangguhkan dan tidak membangun daftar perantara yang sering tidak berguna.
Di sisi lain, ketika eksekusi yang diinginkan dari permintaan LINQ diinginkan, ToListbisa menjadi cara untuk melakukan itu.
AsQueryabledapat 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!
AsEnumerablebekerja seperti narkoba. Ini perbaikan cepat, tetapi dengan biaya dan tidak mengatasi masalah yang mendasarinya.
Dalam banyak jawaban Stack Overflow, saya melihat orang yang mendaftar AsEnumerableuntuk 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 AsEnumerableperbaikannya:
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 Wherepertama:
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 Selectmenghasilkan WhereSelectEnumerableIterator. Ini adalah kelas .Net internal yang mengimplementasikan IEnumerable, bukanIQueryable . Jadi konversi ke jenis lain telah terjadi dan selanjutnya AsQueryabletidak akan pernah dapat mengembalikan sumber aslinya lagi.
Implikasi dari hal ini adalah bahwa menggunakan AsQueryableadalah 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' Includekemampuan ke dalam koleksi dengan menelepon AsQueryable. Ini mengkompilasi dan menjalankan, tetapi tidak melakukan apa-apa karena objek yang mendasarinya tidak memiliki Includeimplementasi lagi.
Menjalankan
Keduanya AsQueryabledan AsEnumerabletidak mengeksekusi (atau menyebutkan ) objek sumber. Mereka hanya mengubah tipe atau representasi mereka. Kedua antarmuka yang terlibat, IQueryabledan 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 IEnumerablediperoleh dengan menelepon AsEnumerablepada IQueryableobjek, akan mengeksekusi mendasari IQueryable. Eksekusi selanjutnya IEnumerableakan kembali mengeksekusi IQueryable. Yang mungkin sangat mahal.
Implementasi khusus
Sejauh ini, ini hanya tentang Queryable.AsQueryabledan Enumerable.AsEnumerablemetode penyuluhan. Tetapi tentu saja siapa pun dapat menulis metode instan atau metode ekstensi dengan nama (dan fungsi) yang sama.
Bahkan, contoh umum dari AsEnumerablemetode ekstensi spesifik adalah DataTableExtensions.AsEnumerable. DataTabletidak menerapkan IQueryableatau IEnumerable, jadi metode ekstensi reguler tidak berlaku.