Wydaje się, że problem polega na tym, że źle zrozumiałeś, jak działa async / await z Entity Framework.
Informacje o Entity Framework
Spójrzmy więc na ten kod:
public IQueryable<URL> GetAllUrls()
{
return context.Urls.AsQueryable();
}
i przykład użycia:
repo.GetAllUrls().Where(u => <condition>).Take(10).ToList()
Co tu się dzieje?
- Otrzymujemy
IQueryable
obiekt (jeszcze nie uzyskujemy dostępu do bazy danych) za pomocąrepo.GetAllUrls()
- Tworzymy nowy
IQueryable
obiekt z określonym warunkiem za pomocą.Where(u => <condition>
- Tworzymy nowy
IQueryable
obiekt z określonym limitem stronicowania za pomocą.Take(10)
- Pobieramy wyniki z bazy danych za pomocą
.ToList()
. Nasz IQueryable
obiekt jest kompilowany do sql (jak select top 10 * from Urls where <condition>
). Baza danych może korzystać z indeksów, serwer sql wysyła tylko 10 obiektów z bazy danych (nie wszystkie miliardy adresów URL przechowywane w bazie danych)
Dobra, spójrzmy na pierwszy kod:
public async Task<IQueryable<URL>> GetAllUrlsAsync()
{
var urls = await context.Urls.ToListAsync();
return urls.AsQueryable();
}
Z tego samego przykładu użycia otrzymaliśmy:
- Ładujemy do pamięci wszystkie miliardy adresów URL przechowywanych w Twojej bazie danych za pomocą
await context.Urls.ToListAsync();
.
- Mamy przepełnienie pamięci. Właściwy sposób na zabicie serwera
Informacje o async / await
Dlaczego preferowane jest użycie async / await? Spójrzmy na ten kod:
var stuff1 = repo.GetStuff1ForUser(userId);
var stuff2 = repo.GetStuff2ForUser(userId);
return View(new Model(stuff1, stuff2));
co się tutaj stało?
- Zaczynając od linii 1
var stuff1 = ...
- Wysyłamy żądanie do serwera sql, dla którego chcemy pobrać trochę rzeczy1
userId
- Czekamy (bieżący wątek jest zablokowany)
- Czekamy (bieżący wątek jest zablokowany)
- .....
- Serwer SQL wyśle do nas odpowiedź
- Przechodzimy do linii 2
var stuff2 = ...
- Wysyłamy żądanie do serwera sql, dla którego chcemy pobrać trochę stuff2
userId
- Czekamy (bieżący wątek jest zablokowany)
- I znowu
- .....
- Serwer SQL wyśle do nas odpowiedź
- Renderujemy widok
Spójrzmy więc na wersję asynchroniczną:
var stuff1Task = repo.GetStuff1ForUserAsync(userId);
var stuff2Task = repo.GetStuff2ForUserAsync(userId);
await Task.WhenAll(stuff1Task, stuff2Task);
return View(new Model(stuff1Task.Result, stuff2Task.Result));
co się tutaj stało?
- Wysyłamy żądanie do serwera sql, aby pobrać stuff1 (wiersz 1)
- Wysyłamy żądanie do serwera sql, aby pobrać stuff2 (linia 2)
- Czekamy na odpowiedzi z serwera sql, ale aktualny wątek nie jest blokowany, może obsłużyć zapytania od innych użytkowników
- Renderujemy widok
Właściwy sposób, aby to zrobić
Tak dobry kod tutaj:
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();
}
Uwaga, niż musisz dodać using System.Data.Entity
, aby użyć metody ToListAsync()
dla IQueryable.
Zwróć uwagę, że jeśli nie potrzebujesz filtrowania, stronicowania i innych rzeczy, nie musisz pracować z IQueryable
. Możesz po prostu używać await context.Urls.ToListAsync()
i pracować ze zmaterializowanymi List<Url>
.