Apa perbedaan antara .ToList (), .AsEnumerable (), AsQueryable ()?

182

Saya tahu beberapa perbedaan LINQ untuk Entitas dan LINQ untuk Objek yang mengimplementasikan pertama IQueryabledan kedua IEnumerabledan ruang lingkup pertanyaan saya berada di dalam EF 5.

Pertanyaan saya adalah apa perbedaan teknis dari ketiga metode tersebut? Saya melihat bahwa dalam banyak situasi semuanya bekerja. Saya juga melihat menggunakan kombinasi mereka suka .ToList().AsQueryable().

  1. Apa arti metode-metode itu, tepatnya?

  2. Apakah ada masalah kinerja atau sesuatu yang akan mengarah pada penggunaan satu di atas yang lain?

  3. Mengapa seseorang menggunakan, misalnya, .ToList().AsQueryable()bukan .AsQueryable()?

Amin Saqi
sumber

Jawaban:

354

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.

Gert Arnold
sumber
Terima kasih atas jawabannya, bisakah Anda membagikan jawaban Anda untuk pertanyaan ke-3 OP - 3. Mengapa seseorang menggunakan, misalnya, .ToList (). AsQueryable () alih-alih .AsQueryable ()? ?
kgzdev
@ Ikram Saya tidak bisa memikirkan apa pun di mana itu akan berguna. Seperti yang saya jelaskan, melamar AsQueryable()sering didasarkan pada kesalahpahaman. Tapi saya akan biarkan mendidih di belakang kepala saya untuk sementara waktu dan melihat apakah saya dapat menambahkan beberapa liputan tentang pertanyaan itu.
Gert Arnold
1
Jawaban yang bagus Bisakah Anda menjelaskan apa yang terjadi jika IEnumerable diperoleh dengan memanggil AsEnumerable () pada IQueryable disebutkan beberapa kali? Apakah kueri akan dieksekusi beberapa kali, atau apakah data yang sudah dimuat dari database ke dalam memori akan digunakan kembali?
antoninod
@ Cantoninod Ide bagus. Selesai
Gert Arnold
46

ToList ()

  • Jalankan permintaan segera

AsEnumerable ()

  • lazy (jalankan query nanti)
  • Parameter: Func<TSource, bool>
  • Muat SETIAP catatan ke dalam memori aplikasi, dan kemudian tangani / saring. (mis. Di mana / Ambil / Lewati, ia akan memilih * dari table1, ke dalam memori, lalu pilih elemen X pertama) (Dalam hal ini, apa yang dilakukannya: Linq-to-SQL + Linq-to-Object)

AsQueryable ()

  • lazy (jalankan query nanti)
  • Parameter: Expression<Func<TSource, bool>>
  • Konversi Ekspresi menjadi T-SQL (dengan penyedia tertentu), permintaan dari jarak jauh dan memuat hasil ke memori aplikasi Anda.
  • Itu sebabnya DbSet (dalam Entity Framework) juga mewarisi IQueryable untuk mendapatkan kueri yang efisien.
  • Jangan memuat setiap record, misalnya jika Take (5), itu akan menghasilkan pilih top 5 * SQL di latar belakang. Ini berarti tipe ini lebih bersahabat dengan SQL Database, dan itulah sebabnya tipe ini biasanya memiliki kinerja yang lebih tinggi dan direkomendasikan ketika berhadapan dengan database.
  • Jadi AsQueryable()biasanya bekerja jauh lebih cepat daripada AsEnumerable()yang menghasilkan T-SQL pada awalnya, yang mencakup semua kondisi Anda di Linq Anda.
Xin
sumber
14

ToList () akan menjadi segalanya dalam memori dan kemudian Anda akan mengerjakannya. jadi, ToList (). di mana (terapkan beberapa filter) dijalankan secara lokal. AsQueryable () akan menjalankan semuanya dari jarak jauh yaitu filter di atasnya dikirim ke database untuk diterapkan. Queryable tidak melakukan apa pun hingga Anda menjalankannya. ToList, namun dieksekusi segera.

Juga, lihat jawaban ini Mengapa menggunakan AsQueryable () bukan List ()? .

EDIT: Juga, dalam kasus Anda setelah Anda melakukan ToList () maka setiap operasi selanjutnya adalah lokal termasuk AsQueryable (). Anda tidak dapat beralih ke jarak jauh setelah Anda mulai mengeksekusi secara lokal. Semoga ini membuatnya sedikit lebih jelas.

ashutosh raina
sumber
2
"AsQueryable () akan menjalankan semuanya dari jarak jauh" Hanya jika enumerable sudah dapat di-query. Kalau tidak, itu tidak mungkin, dan semuanya masih berjalan secara lokal. Pertanyaannya adalah ".... ToList (). AsQueryable ()", yang dapat menggunakan beberapa klarifikasi dalam jawaban Anda, IMO.
2

Mengalami kinerja buruk pada kode di bawah ini.

void DoSomething<T>(IEnumerable<T> objects){
    var single = objects.First(); //load everything into memory before .First()
    ...
}

Diperbaiki dengan

void DoSomething<T>(IEnumerable<T> objects){
    T single;
    if (objects is IQueryable<T>)
        single = objects.AsQueryable().First(); // SELECT TOP (1) ... is used
    else
        single = objects.First();

}

Untuk IQueryable, tetap di IQueryable bila memungkinkan, cobalah untuk tidak digunakan seperti IEnumerable.

Perbarui . Lebih lanjut dapat disederhanakan dalam satu ungkapan, terima kasih Gert Arnold .

T single =  objects is IQueryable<T> q? 
                    q.First(): 
                    objects.First();
Rm558
sumber