EF: Включить с предложением where, + SubIncludes - PullRequest
0 голосов
/ 01 сентября 2018

Это продолжение вопроса здесь: Включить с предложением where . Этот вопрос хочет найти всех пассажиров «Пробуждение» на вождении автобусов

Без предложения WHERE о пассажирах это очень просто, например:

var result = Context.Busses.Where(x => x.IsDriving)
    .Include(x => x.Passengers);

Без предложения WHERE о пассажирах также очень просто включить вспомогательные отношения от пассажиров, например:

var result = Context.Busses.Where(x => x.IsDriving)
    .Include(x => x.Passengers.CarryOns)
    .Include(x => x.Passengers.Luggage);

Но этот вопрос требует использования предложения WHERE для свойства навигации. Ответ на предыдущий вопрос работает отлично, без Взаимоотношений:

var result = Context.Busses.Where(x => x.IsDriving)
    .Select(bus => new {bus, Passengers = bus.Passengers.Where(x => x.Awake)})
    .AsEnumerable().Select(x => x.bus).ToList();

Но как вы также включаете ручную кладь и багаж Пассажира, используя этот метод? Пассажиры не запрашиваются, поэтому вы не можете Include на данный момент. Я попытался сделать что-то подобное, но первая часть была просто перезаписана второй:

var bussesQuery = Context.Busses.Where(x => x.IsDriving)
    .Include(x => x.Passengers.CarryOns)
    .Include(x => x.Passengers.Luggage);
// var check = bussesQuery.ToList(); 
// The Sub-Relationship data is included here, but the Passengers are not filtered.
var result = bussesQuery
    .Select(bus => new {bus, Passengers = bus.Passengers.Where(x => x.Awake)})
    .AsEnumerable().Select(x => x.bus).ToList();
// The Sub-Relationship data is missing, but the Passengers are filtered

Ответы [ 2 ]

0 голосов
/ 01 сентября 2018

Для ответа прокрутите вниз до раздела ответа.

Отказ от ответственности: Я люблю EF. Для 99,999% вызовов, сделанных в моей системе, я могу написать код (LINQ) быстрее, а OR-Mapping - самая быстрая система. Кроме того, сгенерированные запросы (хотя и сбивают с толку) имеют гораздо более быстрые планы выполнения, чем рукописный SQL. Но это не тот случай.

Научно-исследовательский отдел

Для начала В стороне: необработанный SQL для просмотра моего окончательного запроса выглядит примерно так:

SELECT * FROM [Busses] [bus]
LEFT JOIN [Passengers] [passenger] ON [passenger].[BusID] = [bus].[BusID] AND [passenger].[Awake] <> 1
    LEFT JOIN [CarryOns] [carryOn] ON [carryOn].[PassengerID] = [passenger].[PassengerID]
    LEFT JOIN [Luggages] [luggage] ON [luggage].[PassengerID] = [passenger].[PassengerID]
WHERE [bus].[IsDriving] = 1

Конечно, если бы EF сгенерировал что-то для этих результатов, потребовалось бы вложение и ключевые поля, чтобы знать, как их отобразить. Ничего страшного.

К сожалению, чтобы достичь этого одним попаданием в базу данных, мне нужно сделать следующее:

var busses = context.Set<BusEntity>().Where(x => x.IsDriving);
var passengers = context.Set<PassengerEntity>().Where(x => x.Awake);
var carryOns = context.Set<CarryOnEntity>();
var luggages = context.Set<LuggageEntity>();

var passengerJoins = passengers.GroupJoin(
        carryOns,
        x => x.PassengerID,
        y => y.PassengerID,
        (x, y) => new { Passenger = x, CarryOns = y }
    )
    .SelectMany(
        x => x.CarryOns.DefaultIfEmpty(),
        (x, y) => new { Passenger = x.Passenger, CarryOns = x.CarryOns }
    ).GroupJoin(
        luggages,
        x => x.Passenger.PassengerID,
        y => y.PassengerID,
        (x, y) => new { Passenger = x.Passenger, CarryOns = x.CarryOns, Luggages = y }
    )
    .SelectMany(
        x => x.Luggages.DefaultIfEmpty(),
        (x, y) => new { Passenger = x.Passenger, CarryOns = x.CarryOns, Luggages = x.Luggages }
    );

var bussesToPassengers = busses.GroupJoin(
        passengerJoins,
        x => x.BusID,
        y => y.Passenger.BusID,
        (x, y) => new { Bus = x, Passengers = y }
    )
    .SelectMany(
        x => x.Passengers.DefaultIfEmpty(),
        (x, y) => new { Bus = x.Bus, Passengers = x.Passengers }
    )
    .GroupBy(x => x.Bus);

var rez = bussesToPassengers.ToList()
    .Select(x => x.First().Bus)
    .ToList();

Я не жалуюсь на EF-сгенерированный SQL, но один оператор SQL составлял пару сотен строк. Я взломал его, удалил столбцы SELECT и изменил некоторые идентификаторы, чтобы соответствовать этому вопросу, это было что-то вроде этого:

SELECT *
FROM ( SELECT *
    FROM   (SELECT *
        FROM ( SELECT DISTINCT *
            FROM  [dbo].[Bus] AS [Extent1]
            LEFT OUTER JOIN  (SELECT *
                FROM    [dbo].[Passenger] AS [Extent2]
                LEFT OUTER JOIN [dbo].[CarryOns] AS [Extent3] ON [Extent2].[PassengerId] = [Extent3].[PassengerId]
                LEFT OUTER JOIN [dbo].[Luggages] AS [Extent4] ON [Extent2].[PassengerId] = [Extent4].[PassengerId]
            WHERE [Extent1].[IsDriving] = 1
        )  AS [Distinct1] ) AS [Project2]
    OUTER APPLY  (SELECT *
        FROM   (SELECT *
            FROM  [dbo].[Bus] AS [Extent6]
            LEFT OUTER JOIN  (SELECT *
                FROM    [dbo].[Passenger] AS [Extent7]
                LEFT OUTER JOIN [dbo].[CarryOns] AS [Extent8] ON [Extent7].[PassengerId] = [Extent8].[PassengerId]
                LEFT OUTER JOIN [dbo].[Luggages] AS [Extent9] ON [Extent7].[PassengerId] = [Extent9].[PassengerId]
            WHERE ([Extent6].[IsDriving] = 1) AND ([Project2].[BusId] = [Extent6].[BusId]) ) AS [Project3]
        OUTER APPLY  (SELECT *
            FROM     [dbo].[Passenger] AS [Extent11]
            LEFT OUTER JOIN [dbo].[CarryOns] AS [Extent12] ON [Extent11].[PassengerId] = [Extent12].[PassengerId]
            LEFT OUTER JOIN [dbo].[Luggages] AS [Extent13] ON [Extent11].[PassengerId] = [Extent13].[PassengerId]
            LEFT OUTER JOIN [dbo].[CarryOns] AS [Extent15] ON [Extent11].[PassengerId] = [Extent15].[PassengerId]
            WHERE ([Extent11].[IsAwake] = 1) AND ([Project3].[BusId] = [Extent11].[BusId])
        UNION ALL
            SELECT *
            FROM     [dbo].[Passenger] AS [Extent16]
            LEFT OUTER JOIN [dbo].[CarryOns] AS [Extent17] ON [Extent16].[PassengerId] = [Extent17].[PassengerId]
            LEFT OUTER JOIN [dbo].[Luggages] AS [Extent18] ON [Extent16].[PassengerId] = [Extent18].[PassengerId]
            WHERE ([Extent16].[IsAwake] = 1) AND ([Project3].[BusId] = [Extent16].[BusId])
)  AS [Project7]
ORDER BY ........................

Для моих личных тестовых данных Мой рукописный запрос SQL возвращает 54 строки, а EF Generated Query возвращает около 30 000 строк. Поэтому, если вы учитываете только увеличение времени для передачи данных по проводному каналу, это недопустимо.

Ответ Раздел

Ответ таков: вы можете использовать Linq to Entities (в БД) и Linq to Objects (в коде) для достижения ваших результатов за один вызов, но он не будет производительным. Вместо этого вы можете выбрать несколько вызовов с лучшей производительностью, включая меньшее количество данных, передаваемых по сети, более читаемые сгенерированные запросы и более понятный код.

Лучше всего выполнять несколько запросов. Вот как я это делаю:

var bus = context.Set<BusEntity>().Where(x => x.IsDriving).ToList();
var busIDs = bus.Select(x => x.BusID).ToList();
var passengers = context.Set<PassengerEntity>().Where(x => x.IsAwake && busIDs.Contains(x.BusID)).ToList();
var passengerIDs = passengers.Select(x => x.PassengerID).ToList();
var carryOns = context.Set<CarryOnEntity>().Where(x => passengerIDs.Contains(x.PassengerID)).ToList();
var luggages = context.Set<LuggageEntity>().Where(x => passengerIDs.Contains(x.PassengerID)).ToList();
passengers.ForEach(x => {
    x.CarryOns = carryOns.Where(y => y.PassengerID == x.PassengerID).ToList();
    x.Luggages = luggages.Where(y => y.PassengerID == x.PassengerID).ToList();
});
bus.ForEach(x => x.Passengers = passengers.Where(y => y.BusID == x.BusID).ToList());

Это сгенерировало 4 звонка. Всего в SQL было около 40 строк. Я взломал его, удалил столбцы SELECT и изменил некоторые идентификаторы, чтобы соответствовать этому вопросу, это было примерно так:

SELECT * FROM [dbo].[Busses] AS [Extent1]
    WHERE [Extent1].[IsDriving] = 1

SELECT * FROM [dbo].[Passengers] AS [Extent1]
    WHERE ([Extent1].[Awake] = 1) AND ([Extent1].[BusID] IN (......................))

SELECT * FROM [dbo].[CarryOns] AS [Extent1]
    WHERE [Extent1].[PassengerID] IN (......................)

SELECT * FROM [dbo].[Luggages] AS [Extent1]
    WHERE [Extent1].[PassengerID] IN (......................)

EF Generated Query возвращает около 100 строк по 4 вызовам в оба конца. Так что это означает 4 обращения к базе данных, но все они очень маленькие, удобочитаемые и очень быстрые.

Я не рассчитывал время, но всякий раз, когда я делаю паузу на точке останова над кодом ответа и F5 с другой стороны результата, это происходит мгновенно. Когда я делаю то же самое для Single-Call в моем исследовании, это заняло солидную секунду или более, заметное отставание от этого.

0 голосов
/ 01 сентября 2018

Посмотрев на ваш предыдущий запрос, я вижу, что вы переходите от крайнего значения N + 1 к крайнему значению одного запроса. Раньше у вас было много запросов, и теперь вы хотите иметь их, но, пожалуйста, подумайте, что происходит под капотом. Для извлечения структуры сущностей данных необходимо перекрестное объединение всех сущностей, поэтому для каждой включенной сущности вы получаете дополнительные столбцы в разделе «Результаты» И результат объединяется с этим включением.

Допустим, у вас есть 5 водительских автобусов, 30 не спящих пассажиров и 15 багажа на автобус, в результате вы получаете автобусы x багаж x пассажиров = 2250 записей, каждая из которых содержит данные о пассажире и багаже`. Если вы запрашиваете пассажиров и багаж отдельными запросами, у вас будет гораздо меньше записей (5 * 30 + 5 * 15 = 225), и каждый объект будет выбран один раз.

Выполнение одного большого запроса, который вернет все, не является хорошей идеей - это медленнее, сложнее поддерживать и не стоит Вашего времени. Просто сделайте запрос для проснувшихся пассажиров, а затем запросите багаж.

...