从带有单个生成查询的子表中选择Top(n)

时间:2019-11-08 01:02:02

标签: sql entity-framework entity-framework-core

因此,我尝试在实体框架中生成查询,该查询仅从一个孩子的孩子中选择最新记录。我能够做到这一点,但是实体框架正在生成多个查询来实现此目标,这会对性能产生负面影响。

这是我想出的:

_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。

但是当我运行探查器时,我看到很多查询正在运行,这些查询可以通过使用联接来实现,但是我似乎无法正确执行。

生成的查询是:

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 :(得分:0)

我不确定这是否真的是答案,但这是我现在要解决的问题。经过更多研究之后,我发现了这个线程:

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

基本上,EF会评估查询,如果认为有必要,则会由于称为“可怕的笛卡尔积”的原因而将查询分为多个查询: 一个包含(根+ 1个子集合):10列* 100行= 1000个数据点。 两个包含项(根+ 2个子集合):15列* 200行= 3000个数据点。 三个包含项(根+ 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尚不受长期支持。但希望我能够尽快对其进行升级,这将解决我遇到的更多类似情况