Подтвердить что ты не робот

Entity Framework Queryable async

Я работаю над некоторыми материалами веб-API, использующими Entity Framework 6, и один из моих методов-контроллеров - это "Получить все", который ожидает получить содержимое таблицы из моей базы данных как IQueryable<Entity>. В моем репозитории мне интересно, есть ли какая-то выгодная причина сделать это асинхронно, поскольку я новичок в использовании EF с асинхронным доступом.

В основном это сводится к

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

против

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

Будет ли версия асинхронной версии на самом деле давать преимущества производительности здесь, или я нахожу ненужные накладные расходы, проецируя сначала список (используя async mind you) и THEN переходит в IQueryable?

4b9b3361

Ответ 1

Кажется, проблема в том, что вы неправильно поняли, как async/await работает с Entity Framework.

О Entity Framework

Итак, давайте посмотрим на этот код:

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

и пример его использования:

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

Что там происходит?

  1. Мы получаем объект IQueryable (еще не обращающийся к базе данных), используя repo.GetAllUrls()
  2. Мы создаем новый объект IQueryable с указанным условием, используя .Where(u => <condition>
  3. Мы создаем новый объект IQueryable с указанным лимитом подкачки, используя .Take(10)
  4. Мы получаем результаты из базы данных, используя .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();
}

С тем же примером использования мы получили:

  1. Мы загружаем в память все миллиарды URL-адресов, хранящихся в вашей базе данных, используя await context.Urls.ToListAsync(); ,
  2. У нас переполнение памяти. Правильный способ убить ваш сервер

Об асинхронности/ожидании

Почему async/await предпочтительнее использовать? Давайте посмотрим на этот код:

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

Что здесь происходит?

  1. Начиная с строки 1 var stuff1 =...
  2. Мы отправляем запрос к серверу sql, что мы хотим получить некоторые вещи1 для userId
  3. Ждем (текущий поток заблокирован)
  4. Ждем (текущий поток заблокирован)
  5. ...
  6. Sql сервер отправьте нам ответ
  7. var stuff2 =... к строке 2 var stuff2 =...
  8. Мы отправляем запрос к серверу sql, что мы хотим получить некоторые вещи2 для userId
  9. Ждем (текущий поток заблокирован)
  10. И опять
  11. ...
  12. Sql сервер отправьте нам ответ
  13. Мы делаем вид

Итак, давайте посмотрим на асинхронную версию:

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

Что здесь происходит?

  1. Отправляем запрос на sql сервер для получения вещи1 (строка 1)
  2. Отправляем запрос на sql сервер для получения вещи2 (строка 2)
  3. Мы ждем ответов от сервера SQL, но текущий поток не заблокирован, он может обрабатывать запросы от других пользователей
  4. Мы делаем вид

Правильный способ сделать это

Итак, хороший код здесь:

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>.

Ответ 2

В примере, который вы опубликовали, есть большая разница, первая версия:

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

Это плохой, он в основном делает select * from table, возвращает все результаты в память и затем применяет where к тому, что в коллекции памяти, а не делает select * from table where... к базе данных.

Второй метод фактически не попадет в базу данных до тех пор, пока не будет применен запрос к IQueryable (возможно, с помощью операции стиля linq .Where().Select()), которая будет возвращать только значения db, соответствующие запросу.

Если ваши примеры были сопоставимы, версия async, как правило, будет немного медленнее для каждого запроса, так как в конечной машине есть больше накладных расходов, которые компилятор генерирует для обеспечения async.

Однако основное отличие (и польза) в том, что версия async допускает более параллельные запросы, поскольку она не блокирует поток обработки, пока он ожидает завершения ввода-вывода (запрос db, доступ к файлам, веб-запрос и т.д.),.