Ответ 1
Кажется, проблема в том, что вы неправильно поняли, как 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 Server отправляет вам только 10 объектов из вашей базы данных (не все миллиарды URL, хранящиеся в базе данных)
Хорошо, давайте посмотрим на первый код:
public async Task<IQueryable<URL>> GetAllUrlsAsync()
{
var urls = await context.Urls.ToListAsync();
return urls.AsQueryable();
}
С тем же примером использования мы получили:
- Мы загружаем в память все миллиарды URL-адресов, хранящихся в вашей базе данных, используя
await context.Urls.ToListAsync();
, - У нас переполнение памяти. Правильный способ убить ваш сервер
Об асинхронности/ожидании
Почему async/await предпочтительнее использовать? Давайте посмотрим на этот код:
var stuff1 = repo.GetStuff1ForUser(userId);
var stuff2 = repo.GetStuff2ForUser(userId);
return View(new Model(stuff1, stuff2));
Что здесь происходит?
- Начиная с строки 1
var stuff1 =...
- Мы отправляем запрос к серверу sql, что мы хотим получить некоторые вещи1 для
userId
- Ждем (текущий поток заблокирован)
- Ждем (текущий поток заблокирован)
- ...
- Sql сервер отправьте нам ответ
-
var stuff2 =...
к строке 2var 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 сервер для получения вещи1 (строка 1)
- Отправляем запрос на sql сервер для получения вещи2 (строка 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>
.