Entity Framework запрашиваемая асинхронная

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

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

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

vs

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

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

2 ответов


проблема заключается в том, что вы неправильно поняли, как асинхронная/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(). Наши!--6--> объект компилируется в 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();
}

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

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

об асинхронности / await

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

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

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

  1. начиная с строки 1 var stuff1 = ...
  2. мы отправляем запрос на sql server, что мы не хотим получить некоторые stuff1 для userId
  3. ждем (текущий поток заблокирован)
  4. ждем (текущий поток заблокирован)
  5. ...
  6. Sql server отправить нам ответ
  7. переходим к строке 2 var stuff2 = ...
  8. мы отправляем запрос на sql server, что мы не хотим получить некоторые stuff2 для userId
  9. ждем (текущий поток заблокирован)
  10. и снова
  11. ...
  12. Sql server отправить нам ответ
  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 server, чтобы получить stuff1 (строка 1)
  2. мы отправляем запрос на sql server, чтобы получить stuff2 (строка 2)
  3. мы ждем ответов от sql server, но текущий поток не блокируется, он может обрабатывать запросы от других пользователей
  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>.


существует огромная разница в Примере, который вы опубликовали, первая версия:

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

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

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

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

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