Подтвердить что ты не робот

EF: включить с предложением where

Как видно из названия, я ищу способ сделать предложение where в сочетании с include.

Вот мои ситуации: Я несу ответственность за поддержку большого приложения, наполненного запахами кода. Слишком много кода вызывает ошибки везде, поэтому я ищу безопасное решение.

Скажем, у меня есть объект Bus и объект People (у шины есть навигационная накладка Collection of People). В моем запросе мне нужно выбрать все автобусы только с теми, кто бодрствует. Это упрощенный пример фиктивного

В текущем коде:

var busses = Context.Busses.Where(b=>b.IsDriving == true);
foreach(var bus in busses)
{
   var passengers = Context.People.Where(p=>p.BusId == bus.Id && p.Awake == true);
   foreach(var person in passengers)
   {
       bus.Passengers.Add(person);
   }
}

После этого кода Контекст расположен и в вызывающем методе результирующие объекты шины отображаются в класс DTO (100% -ная копия Entity).

Этот код вызывает несколько вызовов в БД, который является No-Go, поэтому я нашел это решение В блогах MSDN

Это отлично работало при отладке результата, но когда объекты сопоставлены с DTO (с использованием AutoMapper), я получаю исключение, что Контекст/Соединение закрыто и что объект не может быть загружен. (Контекст всегда закрыт, не может изменить это:()

Поэтому мне нужно убедиться, что выбранные пассажиры уже загружены (свойство IsLoaded on navigation также является False). Если я осмотрю коллекцию Пассажиров, The Count также выбрасывает Exception, но есть также коллекция Collection of Passegers, называемая "обернутые связанные сущности", которые содержат мои фильтрованные объекты.

Есть ли способ загрузить эти завернутые связанные объекты во всю коллекцию? (Я не могу изменить конфигурацию отображения automapper, потому что это используется во всем приложении).

Есть ли другой способ получить активных пассажиров?

Любые подсказки приветствуются...

Изменить

Ответ Герта Арнольда не работает, потому что данные не загружаются с нетерпением. Но когда я упрощаю его и удаляю, где он загружен. Это действительно странно, так как sql execute возвращает всех пассажиров в обоих случаях. Таким образом, возникает проблема при возврате результатов в объект.

Context.Configuration.LazyLoadingEnabled = false;
var buses = Context.Busses.Where(b => b.IsDriving)
        .Select(b => new 
                     { 
                         b,
                         Passengers = b.Passengers
                     })
        .ToList()
        .Select(x => x.b)
        .ToList();

Edit2

После большой борьбы ответ Герта Арнольда работает! Как сказал Герт Арнольд, вам необходимо отключить Lazy Loading и Keep it OFF. Это потребует внесения дополнительных изменений в приложение, так как предыдущий разработчик любил Lazy Loading -_-

4b9b3361

Ответ 1

Вы можете запросить необходимые объекты по

Context.Configuration.LazyLoadingEnabled = false;
// Or: Context.Configuration.ProxyCreationEnabled = false;
var buses = Context.Busses.Where(b => b.IsDriving)
            .Select(b => new 
                         { 
                             b,
                             Passengers = b.Passengers
                                           .Where(p => p.Awake)
                         })
            .AsEnumerable()
            .Select(x => x.b)
            .ToList();

Здесь происходит то, что вы сначала выбираете ведущие автобусы и пробуждаете пассажиров из базы данных. Затем AsEnumerable() переключается с LINQ на Entities на LINQ на объекты, что означает, что автобусы и пассажиры будут материализованы и затем обработаны в памяти. Это важно, потому что без него EF только материализует окончательную проекцию, Select(x => xb), а не пассажиров.

Теперь в EF есть исправление отношений между объектами, которое заботится об установке всех ассоциаций между объектами, которые материализуются в контексте. Это означает, что для каждого Bus теперь загружены только его активные пассажиры.

Когда вы получаете коллекцию автобусов с помощью ToList вас есть автобусы с ToList пассажирами, и вы можете сопоставить их с AutoMapper.

Это работает только тогда, когда отложенная загрузка отключена. В противном случае EF будет лениво загружать всех пассажиров для каждого автобуса при обращении к пассажирам во время преобразования в DTO.

Есть два способа отключить отложенную загрузку. Отключение LazyLoadingEnabled повторно активирует LazyLoadingEnabled загрузку, когда она снова включается. Отключение ProxyCreationEnabled создаст сущности, которые не способны выполнять ProxyCreationEnabled загрузку, поэтому они не начнут ProxyCreationEnabled загрузку после ProxyCreationEnabled включения ProxyCreationEnabled. Это может быть лучшим выбором, когда контекст живет дольше, чем этот единственный запрос.

Но... многие ко многим

Как уже говорилось, этот обходной путь зависит от исправления отношений. Однако, как объяснил здесь Слаума, исправление отношений не работает с ассоциациями "многие ко многим". Если Bus - Passenger много-ко-многим, единственное, что вы можете сделать, это исправить это самостоятельно:

Context.Configuration.LazyLoadingEnabled = false;
// Or: Context.Configuration.ProxyCreationEnabled = false;
var bTemp = Context.Busses.Where(b => b.IsDriving)
            .Select(b => new 
                         { 
                             b,
                             Passengers = b.Passengers
                                           .Where(p => p.Awake)
                         })
            .ToList();
foreach(x in bTemp)
{
    x.b.Pasengers = x.Passengers;
}
var busses = bTemp.Select(x => x.b).ToList();

... и все это становится еще менее привлекательным.

Сторонние инструменты

Существует библиотека EntityFramework.DynamicFilters, которая делает это намного проще. Это позволяет вам определять глобальные фильтры для сущностей, которые впоследствии будут применяться каждый раз, когда сущность запрашивается. В вашем случае это может выглядеть так:

modelBuilder.Filter("Awake", (Person p) => p.Awake, true);

Теперь, если вы делаете...

Context.Busses.Where(b => b.IsDriving)
       .Include(b => b.People)

... вы увидите, что фильтр применяется к включенной коллекции.

Вы также можете включить/отключить фильтры, чтобы вы могли контролировать, когда они применяются. Я думаю, что это очень аккуратная библиотека.

Есть похожая библиотека от создателя AutoMapper: EntityFramework.Filters

Ядро Entity Framework

Начиная с версии 2.0.0, EF-core имеет фильтры запросов на уровне модели. Хотя это отличное дополнение к его функциям, до сих пор существует ограничение, заключающееся в том, что его нельзя применять к свойствам навигации, а только к корневому объекту запроса. Надеемся, что в более поздней версии эти фильтры получат более широкое использование.

Отфильтрованные включает в себя давний запрос функции. EF-core проблема может быть найдена здесь.

Ответ 2

Отказ от ответственности: я владелец проекта Entity Framework Plus

EF+ Функция Query IncludeFilter позволяет фильтровать связанные объекты.

var buses = Context.Busses
                   .Where(b => b.IsDriving)
                   .IncludeFilter(x => x.Passengers.Where(p => p.Awake))
                   .ToList();

Wiki: EF+ Query IncludeFilter