.ToList (), .AsEnumerable (), AsQueryable () arasındaki farklar nelerdir?


182

LINQ'un Varlıklara ve LINQ'dan Nesnelere olan bazı farklarını biliyorum, ilk uygulayan IQueryableve ikinci uygulayan IEnumerableve soru kapsamım EF 5 dahilindedir.

Benim sorum şu 3 yöntemin teknik farkı nedir? Birçok durumda hepsinin işe yaradığını görüyorum. Bunların kombinasyonlarını kullanmayı da görüyorum .ToList().AsQueryable().

  1. Bu yöntemler tam olarak ne anlama geliyor?

  2. Birinin diğerinin üzerinde kullanılmasına yol açacak herhangi bir performans sorunu veya başka bir şey var mı?

  3. Mesela neden .ToList().AsQueryable()bunun yerine neden kullanılır .AsQueryable()?


Yanıtlar:


354

Bununla ilgili söylenecek çok şey var. Beni odaklanalım AsEnumerableve AsQueryableve söz ToList()yol boyunca.

Bu yöntemler ne işe yarar?

AsEnumerableve AsQueryabledökme veya dönüştürme için IEnumerableya IQueryablesırasıyla. Döküm ya da bir sebeple dönüştürmek diyorum :

  • Kaynak nesne zaten hedef arabirimini uygulayan, kaynak nesnenin kendisi döndürülür fakat döküm hedef arabirimine. Başka bir deyişle: tür değiştirilmez, ancak derleme zamanı türü değiştirilir.

  • Kaynak nesne hedef arabirimi uygulamadığında, kaynak nesne hedef arabirimi uygulayan bir nesneye dönüştürülür . Böylece hem tür hem de derleme zamanı türü değiştirilir.

Bunu bazı örneklerle göstereyim. Derleme zamanı türü ve bir nesnenin gerçek türü ( nezaket Jon Skeet ) bildiren bu küçük yöntem var :

void ReportTypeProperties<T>(T obj)
{
    Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
    Console.WriteLine("Actual type: {0}", obj.GetType().Name);
}

Şimdi Table<T>uygulayan keyfi bir linq-sql'yi deneyelim IQueryable:

ReportTypeProperties(context.Observations);
ReportTypeProperties(context.Observations.AsEnumerable());
ReportTypeProperties(context.Observations.AsQueryable());

Sonuç:

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

Tablo sınıfının kendisinin her zaman döndürüldüğünü görürsünüz, ancak temsili değişir.

Şimdi bir nesne olduğunu uygular IEnumerabledeğil IQueryable:

var ints = new[] { 1, 2 };
ReportTypeProperties(ints);
ReportTypeProperties(ints.AsEnumerable());
ReportTypeProperties(ints.AsQueryable());

Sonuçlar:

Compile-time type: Int32[]
Actual type: Int32[]

Compile-time type: IEnumerable`1
Actual type: Int32[]

Compile-time type: IQueryable`1
Actual type: EnumerableQuery`1

İşte burada. AsQueryable()diziyi EnumerableQuery" IEnumerable<T>bir IQueryable<T>veri kaynağı olarak bir koleksiyonu temsil eder" dizisine dönüştürdü . (MSDN).

Ne yararı var?

AsEnumerableIQueryableçoğunlukla L2O'nun sahip olduğu işlevleri desteklemediği için herhangi bir uygulamadan LINQ'ya nesnelere (L2O) geçmek için kullanılır. Daha fazla ayrıntı için bkz . AsEnumerable () öğesinin LINQ Varlığı üzerindeki etkisi nedir? .

Örneğin, bir Entity Framework sorgusunda yalnızca sınırlı sayıda yöntem kullanabiliriz. Örneğin, bir sorguda kendi yöntemlerimizden birini kullanmamız gerekirse, genellikle

var query = context.Observations.Select(o => o.Id)
                   .AsEnumerable().Select(x => MySuperSmartMethod(x))

ToList - Bir dönüştüren IEnumerable<T>bir karşı List<T>- genellikle de bu amaç için kullanılır. Kullanmanın avantajı AsEnumerableVS. ToListolmasıdır AsEnumerablesorguyu çalıştırmak değildir. AsEnumerableertelenmiş yürütmeyi korur ve genellikle işe yaramaz bir ara liste oluşturmaz.

Öte yandan, bir LINQ sorgusunun zorla yürütülmesi istendiğinde, ToListbunu yapmanın bir yolu olabilir.

AsQueryableLINQ ifadelerinde numaralandırılabilir bir koleksiyon kabul ifadeleri yapmak için kullanılabilir. Daha fazla ayrıntı için buraya bakın: Koleksiyonda gerçekten AsQueryable () kullanmam gerekiyor mu? .

Madde bağımlılığı hakkında not!

AsEnumerableilaç gibi çalışır. Bu hızlı bir düzeltme, ancak bir maliyetle ve altta yatan sorunu çözmez.

Birçok Stack Overflow yanıtında, insanların AsEnumerableLINQ ifadelerinde desteklenmeyen yöntemlerle ilgili herhangi bir sorunu düzeltmek için başvurduğunu görüyorum . Ancak fiyat her zaman net değildir. Örneğin, bunu yaparsanız:

context.MyLongWideTable // A table with many records and columns
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, x.CreateDate })

... her şey düzgün bir şekilde filtreler ( Where) ve projeler ( Select) bir SQL deyimine çevrilir . Yani, SQL sonuç kümesinin sırasıyla uzunluğu ve genişliği azaltılır.

Şimdi kullanıcıların yalnızca tarih bölümünü görmek istediğini varsayalım CreateDate. Entity Framework'te bunu çabucak keşfedeceksiniz ...

.Select(x => new { x.Name, x.CreateDate.Date })

... desteklenmez (yazma sırasında). Neyse ki çözüm var AsEnumerable:

context.MyLongWideTable.AsEnumerable()
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, x.CreateDate.Date })

Tabii, muhtemelen çalışıyor. Ancak tüm tabloyu belleğe çeker ve ardından filtreyi ve çıkıntıları uygular. Çoğu insan Whereilkini yapacak kadar zekidir :

context.MyLongWideTable
       .Where(x => x.Type == "type").AsEnumerable()
       .Select(x => new { x.Name, x.CreateDate.Date })

Ancak yine de tüm sütunlar önce getirilir ve projeksiyon bellekte yapılır.

Gerçek düzeltme:

context.MyLongWideTable
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) })

(Ancak bu biraz daha fazla bilgi gerektirir ...)

Bu yöntemler ne YAPAMAZ?

IQueryable yeteneklerini geri yükleme

Şimdi önemli bir uyarı. Ne zaman yaparsın

context.Observations.AsEnumerable()
                    .AsQueryable()

sonunda temsil edilen kaynak nesneyle sonuçlanacaksınız IQueryable. (Çünkü her iki yöntem de sadece döküm yapar ve dönüştürmez).

Ama yaptığın zaman

context.Observations.AsEnumerable().Select(x => x)
                    .AsQueryable()

sonuç ne olacak?

SelectBir üretir WhereSelectEnumerableIterator. Bunun anlamı uygular dahili Net sınıftır IEnumerable, değilIQueryable . Böylece başka bir türe dönüştükten sonraAsQueryable orijinal kaynak artık geri döndürülemez.

Bunun anlamı, kullanmanın AsQueryable, bir sorgu sağlayıcısını belirli özellikleriyle sihirli bir şekilde numaralandırılabilir bir şekilde enjekte etmenin bir yolu olmamasıdır . Varsayalım

var query = context.Observations.Select(o => o.Id)
                   .AsEnumerable().Select(x => x.ToString())
                   .AsQueryable()
                   .Where(...)

Where koşulu asla SQL'e çevrilmez. AsEnumerable()ardından LINQ ifadeleri varlık çerçevesi sorgu sağlayıcısıyla bağlantıyı kesin olarak keser.

Bu örneği kasten gösteriyorum çünkü burada insanların örneğin Includearayarak yetenekleri bir koleksiyona 'enjekte etmeye' çalıştıkları sorular gördüm AsQueryable. Derler ve çalışır, ancak hiçbir şey yapmaz çünkü altta yatan nesneninInclude artık uygulaması yoktur.

gerçekleştirmek

Hem AsQueryableve AsEnumerableyürütmek (veya yok numaralandırmak kaynak nesneyi). Sadece türlerini veya temsillerini değiştirirler. Her ikisi de ilgili arayüzler IQueryableve IEnumerable"gerçekleşmeyi bekleyen bir numaralandırma" dan başka bir şey değildir. Örneğin, yukarıda belirtildiği gibi, arayarak zorla çalıştırılmadan infaz edilmezler ToList().

Bu , bir nesneyi IEnumerableçağırarak elde edilen bir öğeyi yürütmenin temelini yürüteceği anlamına gelir . Daha sonra iradenin yerine getirilmesi . Bu çok pahalı olabilir.AsEnumerableIQueryableIQueryableIEnumerableIQueryable

Özel Uygulamalar

Şimdiye kadar, bu sadece Queryable.AsQueryableve Enumerable.AsEnumerablegenişletme yöntemleri ile ilgiliydi . Ancak elbette herkes aynı adlara (ve işlevlere) sahip örnek yöntemleri veya genişletme yöntemleri yazabilir.

Aslında, belirli bir AsEnumerableuzatma yönteminin yaygın bir örneğidir DataTableExtensions.AsEnumerable. DataTableuygulanmaz IQueryableveya IEnumerablenormal genişletme yöntemleri uygulanmaz.


Cevabınız için teşekkür ederiz, lütfen yanıtınızı OP - 3'ün 3. sorusuna paylaşabilir misiniz? Neden .AsQueryable () yerine .ToList () .AsQueryable () kullanılır? ?
kgzdev

@ikram Bunun yararlı olacağı hiçbir şey düşünemiyorum. Açıkladığım gibi, başvuru AsQueryable()çoğu zaman yanlış anlama dayanmaktadır. Ama bir süre kafamın arkasında kaynamaya bırakacağım ve bu soruya biraz daha kapsam ekleyip ekleyemeyeceğimi göreceğim.
Gert Arnold

1
Mükemmel cevap. Bir IQueryable üzerinde AsEnumerable () çağrılarak elde edilen IEnumerable birden çok kez numaralandırılırsa ne olacağını kesin olarak söyleyebilir misiniz? Sorgu birden çok kez çalıştırılacak mı yoksa veritabanından belleğe önceden yüklenmiş olan veriler yeniden kullanılacak mı?
antoninod

@antoninod İyi fikir. Bitti.
Gert Arnold

46

Listeye()

  • Sorguyu hemen yürütün

AsEnumerable ()

  • tembel (sorguyu daha sonra yürütün)
  • Parametre: Func<TSource, bool>
  • HER kaydı uygulama belleğine yükleyin ve ardından yönetin / filtreleyin. (örneğin, Nerede / Al / Atla, tablo1'den belleğe * seçer, ardından ilk X öğelerini seçer) (Bu durumda, ne yaptığını: Linq-to-SQL + Linq-to-Object)

AsQueryable ()

  • tembel (sorguyu daha sonra yürütün)
  • Parametre: Expression<Func<TSource, bool>>
  • İfadeyi T-SQL'e (belirli sağlayıcı ile) dönüştürün, uzaktan sorgulayın ve sonucu uygulama belleğinize yükleyin.
  • Bu nedenle DbSet (Entity Framework içinde) de etkin sorguyu almak için IQueryable'ı devralır.
  • Her kaydı yüklemeyin, örneğin Take (5) ise, arka planda seçili ilk 5 * SQL üretir. Bu, bu türün SQL Veritabanı için daha kolay olduğu anlamına gelir ve bu nedenle bu tür genellikle daha yüksek performansa sahiptir ve bir veritabanı ile uğraşırken önerilir.
  • Bu nedenle, AsQueryable()genellikle AsEnumerable()T-SQL ürettiğinden çok daha hızlı çalışır , bu da Linq'inizdeki tüm koşullarınızı içerir.

14

ToList () bellekteki her şey olacak ve daha sonra üzerinde çalışacaksınız. bu nedenle, ToList (). burada (bazı filtre uygulayın) yerel olarak yürütülür. AsQueryable () her şeyi uzaktan yürütür, yani üzerine bir filtre uygulamak için veritabanına gönderilir. Queryable onu yürütene kadar hiçbir şey yapmaz. Ancak ToList hemen yürütülür.

Ayrıca, bu cevaba bakın List () yerine neden AsQueryable () kullanılır? .

EDIT: Ayrıca, sizin durumda ToList () yaptıktan sonra AsQueryable () dahil olmak üzere sonraki her işlem yereldir. Yerel olarak yürütmeye başladıktan sonra uzaktan kumandaya geçemezsiniz. Umarım bu biraz daha açıktır.


2
"AsQueryable () her şeyi uzaktan yürütür" Sadece numaralandırılabilir zaten sorgulanabilir ise. Aksi takdirde, bu mümkün değildir ve her şey hala yerel olarak çalışır. Sorunun cevabı IMO'da bazı açıklamaları kullanabilen ".... ToList (). AsQueryable ()" vardır.

2

Aşağıdaki kodda kötü bir performansla karşılaşıldı.

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

İle düzeltildi

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();

}

Bir IQueryable için, mümkün olduğunda IQueryable içinde kalın, IEnumerable gibi kullanılmamalıdır.

Güncelleme . Gert Arnold sayesinde tek bir ifadeyle daha da basitleştirilebilir .

T single =  objects is IQueryable<T> q? 
                    q.First(): 
                    objects.First();
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.