Основы сущности запрашиваемых асинхронного
Я работаю над некоторыми материалами веб-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 ответа:
проблема, похоже, заключается в том, что вы неправильно поняли, как асинхронная/ожидающая работа с 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
почему async / await предпочтительнее использовать? Давайте посмотрим на этот код:
var stuff1 = repo.GetStuff1ForUser(userId); var stuff2 = repo.GetStuff2ForUser(userId); return View(new Model(stuff1, stuff2));
что здесь происходит?
- начиная с линии 1
var stuff1 = ...
- мы посылаем запрос на sql server, что мы не хотим, чтобы получить некоторые stuff1 для
userId
- ждем (текущий поток заблокирован)
- ждем (текущий поток заблокирован)
- ...
- Sql server отправить нам ответ
- переходим к строке 2
var stuff2 = ...
- мы посылаем запрос на sql server, что мы не хотим, чтобы получить некоторые stuff2 для
userId
- ждем (текущий поток заблокирован)
- и снова
- ...
- Sql server отправить нам ответ
- мы делаем вид
Итак, давайте посмотрим на асинхронную версию:
var stuff1Task = repo.GetStuff1ForUserAsync(userId); var stuff2Task = repo.GetStuff2ForUserAsync(userId); await Task.WhenAll(stuff1Task, stuff2Task); return View(new Model(stuff1Task.Result, stuff2Task.Result));
что здесь происходит?
- мы отправляем запрос на sql server, чтобы получить stuff1 (строка 1)
- мы отправляем запрос на sql server, чтобы получить stuff2 (строка 2)
- мы ждем ответов от sql server, но текущий поток не блокируется, он может обрабатывать запросы от других пользователей
- мы предоставляем посмотреть
правильный способ сделать это
так что хороший код здесь:
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
версия позволяет больше параллельных запросов, поскольку она не блокирует поток обработки, пока он ожидает завершения ввода-вывода (запрос БД, файл доступ, веб-запрос и т. д.).