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()
что там происходит?
- мы получаем
IQueryable
объект (еще не доступ к базе данных) с помощьюrepo.GetAllUrls()
- мы создаем новый
IQueryable
объект с заданным условием использования.Where(u => <condition>
- мы создаем новый
IQueryable
объект с указанным пределом подкачки с помощью.Take(10)
- мы получаем результаты из базы данных с помощью
.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:
- мы загружаем в память все миллиарды url, хранящиеся в вашей базе данных, используя
await context.Urls.ToListAsync();
. - мы получили переполнение памяти. Правильный способ убить ваш сервер
об асинхронности / 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
версия позволяет больше параллельных запросов, поскольку она не блокирует поток обработки, пока он ждет завершения ввода-вывода (запрос db, файл доступ, веб-запрос и т. д.).