Entity Framework Queryable async


99

Entity Framework 6 kullanarak bazı Web API öğeleri üzerinde çalışıyorum ve denetleyici yöntemlerimden biri, bir tablonun içeriğini veritabanımdan olarak almayı bekleyen "Tümünü Al" IQueryable<Entity>. Depomda, asenkron ile EF'yi kullanmaya yeni başladığımdan, bunu eşzamansız olarak yapmak için avantajlı bir neden olup olmadığını merak ediyorum.

Temelde kaynar

 public async Task<IQueryable<URL>> GetAllUrlsAsync()
 {
    var urls = await context.Urls.ToListAsync();
    return urls.AsQueryable();
 }

vs

 public IQueryable<URL> GetAllUrls()
 {
    return context.Urls.AsQueryable();
 }

Eşzamansız sürüm burada gerçekten performans avantajları sağlayacak mı yoksa önce bir Listeye (eşzamansız düşünerek) ve SONRA IQueryable'a projeksiyon yaparak gereksiz ek yüklere mi maruz kalıyorum?


1
context.Urls, IQueryable <URL> 'yi uygulayan DbSet <URL> tipindedir, böylece .AsQueryable () gereksizdir. msdn.microsoft.com/en-us/library/gg696460(v=vs.113).aspx EF'nin sağladığı kalıpları izlediğinizi veya sizin için bağlamı oluşturan araçları kullandığınızı varsayarsak.
Sean B

Yanıtlar:


229

Sorun, zaman uyumsuz / beklemenin Entity Framework ile nasıl çalıştığını yanlış anladığınız gibi görünüyor.

Entity Framework hakkında

Öyleyse, şu koda bakalım:

public IQueryable<URL> GetAllUrls()
{
    return context.Urls.AsQueryable();
}

ve kullanım örneği:

repo.GetAllUrls().Where(u => <condition>).Take(10).ToList()

Orada ne olur?

  1. IQueryableKullanarak nesne alıyoruz (henüz veritabanına erişmiyoruz)repo.GetAllUrls()
  2. IQueryableKullanarak belirtilen koşulda yeni bir nesne oluşturuyoruz.Where(u => <condition>
  3. IQueryableKullanarak belirtilen sayfalama sınırına sahip yeni bir nesne oluşturuyoruz.Take(10)
  4. Kullanarak veritabanından sonuçları alırız .ToList(). Bizim IQueryablenesne (gibi sql için derlendi select top 10 * from Urls where <condition>). Ve veritabanı dizinleri kullanabilir, sql sunucusu size veritabanınızdan yalnızca 10 nesne gönderir (veritabanında depolanan milyarlarca url'nin tamamı değil)

Tamam, ilk koda bakalım:

public async Task<IQueryable<URL>> GetAllUrlsAsync()
{
    var urls = await context.Urls.ToListAsync();
    return urls.AsQueryable();
}

Aynı kullanım örneğiyle elde ettik:

  1. Kullanarak veritabanınızda depolanan tüm milyar url'leri belleğe yüklüyoruz await context.Urls.ToListAsync();.
  2. Hafıza taşması var. Sunucunuzu öldürmenin doğru yolu

Async / await hakkında

Async / await neden kullanılması tercih edilir? Bu koda bakalım:

var stuff1 = repo.GetStuff1ForUser(userId);
var stuff2 = repo.GetStuff2ForUser(userId);
return View(new Model(stuff1, stuff2));

Burada ne oluyor?

  1. 1. satırdan başlayarak var stuff1 = ...
  2. Bir şeyler almak istediğimiz sql sunucusuna istek gönderiyoruz1. userId
  3. Bekleriz (mevcut konu bloke edilir)
  4. Bekleriz (mevcut konu bloke edilir)
  5. .....
  6. Sql sunucusu bize yanıt gönder
  7. 2. satıra geçiyoruz var stuff2 = ...
  8. Bir şeyler almak istediğimiz sql sunucusuna istek gönderiyoruz2 için userId
  9. Bekleriz (mevcut konu bloke edilir)
  10. Ve yeniden
  11. .....
  12. Sql sunucusu bize yanıt gönder
  13. Görünüm oluşturuyoruz

Şimdi bunun eşzamansız bir sürümüne bakalım:

var stuff1Task = repo.GetStuff1ForUserAsync(userId);
var stuff2Task = repo.GetStuff2ForUserAsync(userId);
await Task.WhenAll(stuff1Task, stuff2Task);
return View(new Model(stuff1Task.Result, stuff2Task.Result));

Burada ne oluyor?

  1. Malzeme almak için sql sunucusuna istek gönderiyoruz (1. satır)
  2. Stuff2'yi almak için sql sunucusuna istek gönderiyoruz (2. satır)
  3. Sql sunucusundan yanıtları bekliyoruz, ancak mevcut iş parçacığı bloke edilmedi, başka kullanıcıların sorularını halledebilir
  4. Görünüm oluşturuyoruz

Bunu yapmanın doğru yolu

Burada çok iyi kod:

using System.Data.Entity;

public IQueryable<URL> GetAllUrls()
{
   return context.Urls.AsQueryable();
}

public async Task<List<URL>> GetAllUrlsByUser(int userId) {
   return await GetAllUrls().Where(u => u.User.Id == userId).ToListAsync();
}

IQueryable using System.Data.Entityyöntemini kullanmak ToListAsync()için eklemeniz gerekenden fazlasını unutmayın .

Unutmayın, filtrelemeye, sayfalandırmaya vb IQueryable. İhtiyacınız yoksa, çalışmanıza gerek yoktur . Sadece await context.Urls.ToListAsync()materyalize kullanabilir ve çalışabilirsiniz List<Url>.


3
Resmi bakarak @Korijn i2.iis.net/media/7188126/... gelen IIS mimarisine giriş IIS tüm istekleri asenkron şekilde işlenmektedir söyleyebiliriz
Viktor Lova

7
GetAllUrlsByUserYöntemdeki sonuç kümesine göre hareket etmediğiniz için, onu eşzamansız yapmanıza gerek yoktur. Sadece Görevi geri verin ve kendinizi gereksiz bir durum makinesinin derleyici tarafından oluşturulmasını önleyin.
Johnathon Sullinger

1
@JohnathonSullinger Her ne kadar mutlu bir akış içinde işe yarayacak olsa da, bu herhangi bir istisnanın burada yüzeye çıkmaması ve bekleyen ilk yere yayılmaması gibi bir yan etkiye sahip değil mi? (Bu mutlaka kötü değil, ama davranışta bir değişiklik mi?)
Henry

9
İlginçtir ki, "About async / await" deki 2. kod örneğinin tamamen anlamsız olduğunu farketmez, çünkü ne EF ne de EF Core iş parçacığı güvenli olmadığından bir istisna atar, bu nedenle paralel olarak çalışmaya çalışmak sadece bir istisna atar
Tseng

1
Bu cevap doğru olsa da, ben kullanmaktan kaçınmak tavsiye ederim asyncve awaitlistedeki ilgili şeyler yapmayı DEĞİL eğer. Arayanın awaitbunu yapmasına izin verin . Bu aşamada çağrıyı beklediğinizde, return await GetAllUrls().Where(u => u.User.Id == userId).ToListAsync();derlemeyi çözüp IL'ye baktığınızda fazladan bir zaman uyumsuz sarmalayıcı oluşturursunuz.
Ali Khakpouri

10

Gönderdiğiniz örnekte, ilk sürümde çok büyük bir fark var:

var urls = await context.Urls.ToListAsync();

Bu kötüdür , temelde yapar select * from table, tüm sonuçları belleğe döndürür ve ardından veritabanına karşı whereyapmak yerine bellek koleksiyonundaki buna karşı uygular select * from table where....

İkinci yöntem, bir sorgu uygulanıncaya kadar veritabanını gerçekten vurmayacaktır IQueryable(muhtemelen linq .Where().Select()tarzı bir işlem yoluyla , yalnızca sorgu ile eşleşen db değerlerini döndürür.

Örnekleriniz karşılaştırılabilir olsaydı async, asyncişlevselliğe izin vermek için derleyicinin oluşturduğu durum makinesinde daha fazla ek yük olduğundan, sürüm genellikle istek başına biraz daha yavaş olacaktır .

Ancak en büyük fark (ve yararı), asyncsürümün, IO'nun tamamlanmasını beklerken (db sorgusu, dosya erişimi, web isteği vb.) İşleme iş parçacığını engellemediği için daha fazla eşzamanlı isteklere izin vermesidir.


7
IQueryable .... öğesine bir sorgu uygulanana kadar ne IQueryable.Where ve IQueryable.Select sorguyu yürütmeye zorlar. Önceki bir yüklem uygular ve ikincisi bir projeksiyon uygular. ToList, ToArray, Single veya First gibi bir materyalleştirme operatörü kullanılana kadar yürütülmez.
JJS

0

Uzun lafın kısası,
IQueryableÇALIŞMA sürecini ertelemek ve önce ifadeyi diğer IQueryableifadelerle birlikte oluşturmak ve ardından ifadeyi bir bütün olarak yorumlamak ve çalıştırmak için tasarlanmıştır.
Ancak ToList()yöntem (veya bunun gibi birkaç yöntem), ifadenin anında "olduğu gibi" çalıştırılmasıdır.
İlk metodunuz ( GetAllUrlsAsync) hemen çalışacaktır çünkü metodu IQueryabletakip eder ToListAsync(). dolayısıyla anında çalışır (eşzamansız) ve bir grup IEnumerables döndürür .
Bu arada ikinci yönteminiz ( GetAllUrls) çalışmayacaktır. Bunun yerine, bir ifade döndürür ve bu yöntemin CALLER'ı ifadeyi çalıştırmaktan sorumludur.

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.