Основы сущности запрашиваемых асинхронного


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

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

 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 64

2 ответа:

проблема, похоже, заключается в том, что вы неправильно поняли, как асинхронная/ожидающая работа с 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

почему 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 версия позволяет больше параллельных запросов, поскольку она не блокирует поток обработки, пока он ожидает завершения ввода-вывода (запрос БД, файл доступ, веб-запрос и т. д.).