Проблема, похоже, в том, что вы неправильно поняли, как async / await работают с Entity Framework.
О Entity Framework
Итак, посмотрим на этот код:
public IQueryable<URL> GetAllUrls()
{
return context.Urls.AsQueryable();
}
и пример его использования:
repo.GetAllUrls().Where(u => <condition>).Take(10).ToList()
Что там происходит?
- Мы получаем
IQueryableобъект (пока не получаем доступ к базе данных), используяrepo.GetAllUrls()
- Создаем новый
IQueryableобъект с заданным условием, используя.Where(u => <condition>
- Мы создаем новый
IQueryableобъект с указанным пределом разбиения на страницы, используя.Take(10)
- Мы получаем результаты из базы данных с помощью
.ToList(). Наш IQueryableобъект скомпилирован в sql (вроде select top 10 * from Urls where <condition>). И база данных может использовать индексы, sql-сервер отправляет вам только 10 объектов из вашей базы данных (не все миллиарды URL-адресов, хранящиеся в базе данных)
Хорошо, посмотрим на первый код:
public async Task<IQueryable<URL>> GetAllUrlsAsync()
{
var urls = await context.Urls.ToListAsync();
return urls.AsQueryable();
}
На том же примере использования мы получили:
- Мы загружаем в память все миллиард URL-адресов, хранящихся в вашей базе данных, используя
await context.Urls.ToListAsync();.
- У нас переполнение памяти. Правильный способ убить ваш сервер
О async / await
Почему предпочтительнее использовать async / await? Посмотрим на этот код:
var stuff1 = repo.GetStuff1ForUser(userId);
var stuff2 = repo.GetStuff2ForUser(userId);
return View(new Model(stuff1, stuff2));
Что здесь происходит?
- Начиная со строки 1
var stuff1 = ...
- Мы отправляем запрос на sql server, который хотим получить кое-что1 для
userId
- Ждем (текущий поток заблокирован)
- Ждем (текущий поток заблокирован)
- .....
- Sql-сервер отправит нам ответ
- Переходим к строке 2
var stuff2 = ...
- Мы отправляем запрос на sql-сервер, который хотим получить кое-что2 для
userId
- Ждем (текущий поток заблокирован)
- И опять
- .....
- Sql-сервер отправит нам ответ
- Оказываем вид
Итак, давайте посмотрим на его асинхронную версию:
var stuff1Task = repo.GetStuff1ForUserAsync(userId);
var stuff2Task = repo.GetStuff2ForUserAsync(userId);
await Task.WhenAll(stuff1Task, stuff2Task);
return View(new Model(stuff1Task.Result, stuff2Task.Result));
Что здесь происходит?
- Отправляем запрос на сервер sql, чтобы получить файл stuff1 (строка 1)
- Отправляем запрос на sql server для получения stuff2 (строка 2)
- Ждем ответов от sql сервера, но текущий поток не заблокирован, он может обрабатывать запросы от других пользователей
- Оказываем вид
Правильный способ сделать это
Итак, хороший код здесь:
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();
}
Обратите внимание, что вы должны добавить using System.Data.Entity, чтобы использовать метод ToListAsync()для IQueryable.
Обратите внимание: если вам не нужны фильтрация, разбивка по страницам и прочее, вам не нужно работать IQueryable. Вы можете просто использовать await context.Urls.ToListAsync()и работать с материализованным List<Url>.