Избегайте Распространения DbContext или IQueryable в Приложениях. Начало
Большинство приложений .NET используют EF Core и DbContext для доступа к данным, но удобство сопровождения может пострадать, если использование DbContext или производного от него IQueryable распространяется по всему приложению.
Интерфейсы IQueryable и IQueryable<T> в .NET позволяют описывать запросы в виде деревьев выражений, которые при выполнении преобразуются провайдером в запросы к БД. В EF IQueryable используется, чтобы создавать динамические SQL-запросы, что позволяет выполнять детальные и эффективные запросы к базе данных, а не извлекать больше данных, чем необходимо, в память приложения, а затем фильтровать результат в памяти.
Ниже создаётся набор заказов, отфильтрованных по дате. В первом примере все заказы из БД передаются в приложение, а затем фильтруются. Во втором используется динамический SQL-запрос, позволяющий БД возвращать только совпадающие записи:
var since = new DateTime(2023,1,1);Очевидно, что скорость и эффективность второго подхода почти всегда делают его предпочтительным методом.
// фильтрация в памяти
var all = dbContext.Orders.ToList();
var recent1 = all
.Where(o.Date > since)
.ToList();
// фильтрация в БД
var recent2 = dbContext.Orders
.Where(o.Date > since)
.ToList();
Можно создать выражение IQueryable с помощью серии операторов, даже в разных функциях, классах или проектах. Когда об этом было впервые объявлено, в Microsoft высоко оценили эту возможность, т.к. разработчики могли создавать нужный запрос «по требованию», где бы им это ни требовалось, гарантируя, что при окончательном выполнении запроса будут возвращены только необходимые данные. У вас может быть сервис, возвращающий «чистые» данные:
public class DataServiceСервис бизнес-логики, который накладывает некоторые ограничения на выборку:
{
public async
Task<IQueryable<Order>> List()
{
return await
_dbContext.Orders.AsQueryable();
}
}
public class OrderServiceЗатем в контроллере добавляется фильтр текущего пользователя:
{
// сервис данных внедряется в _dataService
public async
Task<IQueryable<Order>> ActiveOrders()
{
var all = await _dataService.List();
return await all
.Where(o => !o.Canceled)
.AsQueryable();
}
}
var viewModel =Наконец, на странице в коде Razor мы генерируем окончательный список:
await _orderSvc.ActiveOrders()
.Where(o => o.CreatedBy = username)
.AsQueryable();
foreach(var ord inВ итоге у нас получается следующий запрос, который будет выполнен:
model.Orders.Where(o => o.Shipped()))
{
// список в HTML
}
_dbContext.OrdersЭто здорово, если сравнивать с наивной реализацией, в которой сервис данных просто возвращал бы все заказы, а последующие правила фильтровали данные в памяти. Но это странное решение, есть лучшие способы организовать этот код, сохраняя при этом эффективность запросов.
.Where(o => !o.Canceled &&
o.CreatedBy == username &&
o.Shipped);
Окончание следует…
Источник: https://ardalis.com/avoid-dbcontext-iqueryable-proliferation/