Выберите Top (n) из дочерней таблицы с одним сгенерированным запросом - PullRequest
0 голосов
/ 08 ноября 2019

Итак, я пытаюсь сгенерировать запрос в структуре сущностей, который выбирает только последнюю запись из дочернего элемента дочернего элемента. Я могу сделать это, однако структура сущностей генерирует несколько запросов для достижения этого, что негативно влияет на производительность.

Вот что я придумал:

_entities.Rentals.Include(r => r.CarClass)
                                .Include(r => r.Car).ThenInclude(x => x.CheckSheets).ThenInclude(y => y.ElectronicCheckSheetVehicleDetail)
                                .Include(r => r.Hirer)
                                .Include(r => r.Agent)
                                .Include(r => r.AdditionalDrivers)
                                .Include(x => x.Payments)
                                .Include(r => r.RentalAccessories).ThenInclude(y => y.Accessory)
                                .Where(....)
                                            ).Select(s => new Rental()
                                            {
                                                RentalId = s.RentalId,
                                                // More props
                                                Agent = s.Agent,
                                                Hirer = s.Hirer,
                                                Car = new Car {
                                                    // More props from this child
                                                    CheckSheets = s.Car.CheckSheets.OrderByDescending(o => o.Id).Take(1).Select(a => new ElectronicCheckSheet() { ElectronicCheckSheetVehicleDetail = a.ElectronicCheckSheetVehicleDetail  }).ToList()
                                                },
                                                CarClass = s.CarClass,
                                                AdditionalDrivers = s.AdditionalDrivers,
                                                RentalAccessories = s.RentalAccessories.Select(a => new RentalAccessory() {
                                                    // More props from this child
                                                    Accessory = a.Accessory
                                                }).ToList(),
                                                Payments = s.Payments,
                                                AssignedDriver = s.AssignedDriver
                                            });

Все работает сниже основной целью было выбрать только последнюю запись из CheckSheets и получить соответствующий ElectronicCheckSheetVehicleDetail.

Но когда я запускаю Profiler, я вижу много запросов, выполняемых для чего-то, что может быть достигнуто с помощью объединений, но я не могу понять, что это правильно.

Сгенерированы следующие запросы:

exec sp_executesql N'SELECT {Column names}
FROM [Rental] AS [r]
INNER JOIN [CarClass] AS [r.CarClass] ON [r].[BookedCarClassId] = [r.CarClass].[CarClassId]
LEFT JOIN [Car] AS [r.Car] ON [r].[BookedCarId] = [r.Car].[CarId]
INNER JOIN [Hirer] AS [r.Hirer] ON [r].[HirerId] = [r.Hirer].[HirerId]
LEFT JOIN [Agent] AS [r.Agent] ON [r].[AgentId] = [r.Agent].[AgentId]
WHERE (([r].[PickupDate] IS NOT NULL AND (CONVERT(date, [r].[PickupDate]) = CONVERT(date, CONVERT(date, GETDATE())))) AND (([r].[Status] = @__GetDescription_0) OR (((([r].[Status] = @__GetDescription_1) AND [r].[IsOnlineCheckinCompleted] IS NOT NULL) AND ([r].[IsOnlineCheckinCompleted] = 1)) AND ([r].[OnlineCheckinMethod] = @__GetDescription_2)))) AND ([r].[PickupPoint] IS NOT NULL AND ([r].[PickupPoint] = @__branchId_Value_3))
ORDER BY [r].[RentalId]',N'@__GetDescription_0 nvarchar(4000),@__GetDescription_1 nvarchar(4000),@__GetDescription_2 nvarchar(4000),@__branchId_Value_3 int',@__GetDescription_0=N'Picked up',@__GetDescription_1=N'Confirmed',@__GetDescription_2=N'App',@__branchId_Value_3=10


exec sp_executesql N'SELECT TOP(1) {Column names}
FROM [ElectronicCheckSheet] AS [o]
LEFT JOIN [ElectronicCheckSheetVehicleDetail] AS [o.ElectronicCheckSheetVehicleDetail] ON [o].[VehicleDetail_Id] = [o.ElectronicCheckSheetVehicleDetail].[Id]
WHERE @_outer_CarId = [o].[Car_CarId]
ORDER BY [o].[Id] DESC',N'@_outer_CarId int',@_outer_CarId=17789


exec sp_executesql N'SELECT {Column names}
FROM [AdditionalDriver] AS [r.AdditionalDrivers]
INNER JOIN (
    SELECT [r0].[RentalId]
    FROM [Rental] AS [r0]
    INNER JOIN [CarClass] AS [r.CarClass0] ON [r0].[BookedCarClassId] = [r.CarClass0].[CarClassId]
    LEFT JOIN [Car] AS [r.Car0] ON [r0].[BookedCarId] = [r.Car0].[CarId]
    INNER JOIN [Hirer] AS [r.Hirer0] ON [r0].[HirerId] = [r.Hirer0].[HirerId]
    LEFT JOIN [Agent] AS [r.Agent0] ON [r0].[AgentId] = [r.Agent0].[AgentId]
    WHERE (([r0].[PickupDate] IS NOT NULL AND (CONVERT(date, [r0].[PickupDate]) = CONVERT(date, CONVERT(date, GETDATE())))) AND (([r0].[Status] = @__GetDescription_0) OR (((([r0].[Status] = @__GetDescription_1) AND [r0].[IsOnlineCheckinCompleted] IS NOT NULL) AND ([r0].[IsOnlineCheckinCompleted] = 1)) AND ([r0].[OnlineCheckinMethod] = @__GetDescription_2)))) AND ([r0].[PickupPoint] IS NOT NULL AND ([r0].[PickupPoint] = @__branchId_Value_3))
) AS [t] ON [r.AdditionalDrivers].[RentalId] = [t].[RentalId]
ORDER BY [t].[RentalId]',N'@__GetDescription_0 nvarchar(4000),@__GetDescription_1 nvarchar(4000),@__GetDescription_2 nvarchar(4000),@__branchId_Value_3 int',@__GetDescription_0=N'Picked up',@__GetDescription_1=N'Confirmed',@__GetDescription_2=N'App',@__branchId_Value_3=10


exec sp_executesql N'SELECT {Column names}
FROM [RentalAccessory] AS [r.RentalAccessories]
LEFT JOIN [Accessory] AS [a.Accessory] ON [r.RentalAccessories].[AccessoryId] = [a.Accessory].[AccessoryId]
INNER JOIN (
    SELECT [r1].[RentalId]
    FROM [Rental] AS [r1]
    INNER JOIN [CarClass] AS [r.CarClass1] ON [r1].[BookedCarClassId] = [r.CarClass1].[CarClassId]
    LEFT JOIN [Car] AS [r.Car1] ON [r1].[BookedCarId] = [r.Car1].[CarId]
    INNER JOIN [Hirer] AS [r.Hirer1] ON [r1].[HirerId] = [r.Hirer1].[HirerId]
    LEFT JOIN [Agent] AS [r.Agent1] ON [r1].[AgentId] = [r.Agent1].[AgentId]
    WHERE (([r1].[PickupDate] IS NOT NULL AND (CONVERT(date, [r1].[PickupDate]) = CONVERT(date, CONVERT(date, GETDATE())))) AND (([r1].[Status] = @__GetDescription_0) OR (((([r1].[Status] = @__GetDescription_1) AND [r1].[IsOnlineCheckinCompleted] IS NOT NULL) AND ([r1].[IsOnlineCheckinCompleted] = 1)) AND ([r1].[OnlineCheckinMethod] = @__GetDescription_2)))) AND ([r1].[PickupPoint] IS NOT NULL AND ([r1].[PickupPoint] = @__branchId_Value_3))
) AS [t0] ON [r.RentalAccessories].[RentalId] = [t0].[RentalId]
ORDER BY [t0].[RentalId]',N'@__GetDescription_0 nvarchar(4000),@__GetDescription_1 nvarchar(4000),@__GetDescription_2 nvarchar(4000),@__branchId_Value_3 int',@__GetDescription_0=N'Picked up',@__GetDescription_1=N'Confirmed',@__GetDescription_2=N'App',@__branchId_Value_3=10


exec sp_executesql N'SELECT {Column names}
FROM [Payment] AS [r.Payments]
INNER JOIN (
    SELECT [r2].[RentalId]
    FROM [Rental] AS [r2]
    INNER JOIN [CarClass] AS [r.CarClass2] ON [r2].[BookedCarClassId] = [r.CarClass2].[CarClassId]
    LEFT JOIN [Car] AS [r.Car2] ON [r2].[BookedCarId] = [r.Car2].[CarId]
    INNER JOIN [Hirer] AS [r.Hirer2] ON [r2].[HirerId] = [r.Hirer2].[HirerId]
    LEFT JOIN [Agent] AS [r.Agent2] ON [r2].[AgentId] = [r.Agent2].[AgentId]
    WHERE (([r2].[PickupDate] IS NOT NULL AND (CONVERT(date, [r2].[PickupDate]) = CONVERT(date, CONVERT(date, GETDATE())))) AND (([r2].[Status] = @__GetDescription_0) OR (((([r2].[Status] = @__GetDescription_1) AND [r2].[IsOnlineCheckinCompleted] IS NOT NULL) AND ([r2].[IsOnlineCheckinCompleted] = 1)) AND ([r2].[OnlineCheckinMethod] = @__GetDescription_2)))) AND ([r2].[PickupPoint] IS NOT NULL AND ([r2].[PickupPoint] = @__branchId_Value_3))
) AS [t1] ON [r.Payments].[RentalId] = [t1].[RentalId]
ORDER BY [t1].[RentalId]',N'@__GetDescription_0 nvarchar(4000),@__GetDescription_1 nvarchar(4000),@__GetDescription_2 nvarchar(4000),@__branchId_Value_3 int',@__GetDescription_0=N'Picked up',@__GetDescription_1=N'Confirmed',@__GetDescription_2=N'App',@__branchId_Value_3=10

Можно ли как-то заставить инфраструктуру сущностей сделать это в одном запросе? Желательно без использования запроса Linq?

ОБНОВЛЕНИЕ:

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

.Include(r => r.Car).ThenInclude(x => x.CheckSheets).ThenInclude(y => y.ElectronicCheckSheetVehicleDetail)

1 Ответ

0 голосов
/ 11 ноября 2019

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

https://github.com/aspnet/EntityFrameworkCore/issues/12098

По сути, EF оценивает запрос и, если он считает, что это необходимо, разделяет запрос на несколько запросов из-за того, что называется «страшно»декартово произведение »: одно включение (корневая + 1 дочерняя коллекция): 10 столбцов * 100 строк = 1000 точек данных. Включает два (Корень + 2 дочерние коллекции): 15 столбцов * 200 строк = 3000 точек данных. Три включения (Root + 3 дочерних коллекции): 20 столбцов * 300 строк = 6000 точек данных. С 12 включениями это составит 78000 точек данных! И наоборот, если вы получите все записи для каждой таблицы отдельно, а не 12 включений, у вас будет 13 * 5 * 100 точек данных: 6500, менее 10%

Предположительно, существует баланс, при котором вышеприведенное становится актуальным (именно поэтому они сделали это в EFCore 2.0.0). В случаях, когда вы выбираете только небольшое количество строк (скажем, ниже 10), более эффективно выбрать все в одном запросе. Но как только вы выбираете много строк, становится более эффективным выбирать меньшие множества в нескольких запросах.

Однако, согласно потоку, это было обновлено в v3.0.0 (мы в настоящее время на 2.2.1).

Таким образом, мое решение состояло в том, чтобы создать хранимую процедуру, поскольку я не делаюдумаю, что EFCore v3.0.0 находится на долгосрочной поддержке еще. Но, надеюсь, я скоро смогу обновить его, что решит еще несколько подобных случаев, которые у меня есть

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...