IQueryable и ленивая загрузка
-
04-10-2019 - |
Вопрос
Мне трудно определить лучший способ справиться с этим...При использовании Entity Framework (и L2S) запросы LINQ возвращают IQueryable.Я читал различные мнения о том, должен ли DAL/BLL возвращать IQueryable, IEnumerable или IList.Если мы используем IList, то запрос выполняется немедленно, и управление не передается на следующий уровень.Это упрощает модульное тестирование и т. д.Вы теряете возможность уточнять запрос на более высоких уровнях, но можете просто создать другой метод, который позволит уточнить запрос и при этом возвращать IList.И еще много плюсов/минусов.Все идет нормально.
Теперь идет Entity Framework и отложенная загрузка.Я использую объекты POCO с прокси в .NET 4/VS 2010.На уровне представления я делаю:
foreach (Order order in bll.GetOrders())
{
foreach (OrderLine orderLine in order.OrderLines)
{
// Do something
}
}
В этом случае GetOrders() возвращает IList, поэтому он выполняется непосредственно перед возвратом в PL.Но в следующем foreach у вас есть отложенная загрузка, которая выполняет несколько запросов SQL по мере получения всех строк заказа.По сути, PL выполняет SQL-запросы «по требованию» не на том уровне.
Есть ли разумный способ избежать этого?Я мог бы отключить отложенную загрузку, но тогда какой смысл в этой «функции», на которую все жаловались, что в EF1 ее нет?И я признаю, что это очень полезно во многих сценариях.Поэтому я вижу несколько вариантов:
- Каким-то образом удалите все ассоциации в сущностях и добавьте методы для их возврата.Это противоречит поведению/генерации кода EF по умолчанию и усложняет выполнение некоторых составных (множественных сущностей) запросов LINQ.Это похоже на шаг назад.Я голосую нет.
- Если у нас все равно отложенная загрузка, что затрудняет модульное тестирование, тогда пройдите до конца и верните IQueryable.Дальше по слоям у вас будет больше контроля.Я все еще не думаю, что это хороший вариант, поскольку IQueryable привязывает вас к L2S, L2E или вашей собственной полной реализации IQueryable.Отложенная загрузка может выполнять запросы «по требованию», но не привязывает вас к какому-либо конкретному интерфейсу.Я голосую нет.
- Отключите отложенную загрузку.Вам придется обрабатывать ассоциации вручную.Это может быть с помощью функции .Include() с активной загрузкой.Я голосую за в некоторых конкретных случаях.
- Сохраняйте IList и отложенную загрузку.Во многих случаях я голосую за, только из-за проблем с остальными.
Есть еще варианты или предложения?Я не нашел варианта, который бы меня действительно убедил.
Решение
Вы можете заставить свои методы принимать какую-то стратегию загрузки.
Func<ObjectSet<Order>, ObjectQuery<Order>> loadSpan =
orders=> orders.Include("OrderLines");
foreach (Order order in bll.GetOrders(loadSpan))
{
foreach (OrderLine orderLine in order.OrderLines)
{
// Do something
}
}
И внутри вашего метода GetOrders вы делаете что-то вроде
public IList<Oorder> GetOrders(
Func<ObjectSet<Order>, ObjectQuery<Order>> loadSpan)
{
var ordersWithSpan = loadSpan(context.OrderSet);
var orders = from order in ordersWithSpan
where ...your normal filters etc
return orders.ToList();
}
Это позволит вам указать целые графики нагрузки для каждого варианта использования.Вы, конечно, также можете обернуть эти стратегии в какой-нибудь класс-оболочку, чтобы написать:
//wrapped in a static class "OrderLoadSpans"
foreach (Order order in bll.GetOrders(OrderLoadSpans.WithOrderLines))
ХТХ