【问题标题】:Select Top(n) from child table with single generated query使用单个生成的查询从子表中选择 Top(n)
【发布时间】:2019-11-11 07:42:38
【问题描述】:

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

这是我想出的:

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

【问题讨论】:

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


    【解决方案1】:

    我不确定这是否真的是一个答案,但这是我现在要采用的解决方案。在做了更多研究后,我发现了这个线程:

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

    基本上,EF 会评估查询,如果它认为需要它,它会将查询拆分为多个查询,因为这种查询称为“可怕的笛卡尔积”: 一个包含(根 + 1 个子集合):10 列 * 100 行 = 1000 个数据点。 两个包含(根 + 2 个子集合):15 列 * 200 行 = 3000 个数据点。 三个包含(根 + 3 个子集合):20 列 * 300 行 = 6000 个数据点。 如果包含 12 个,这将达到 78000 个数据点! 相反,如果您分别获取每个表的所有记录而不是 12 个 Include,则您有 13 * 5 * 100 个数据点:6500,小于 10%

    据说有一个平衡点,上面的内容变得相关(这就是他们在 EFCore 2.0.0 中这样做的原因)。如果您只选择少量行(比如低于 10 行),那么在一个查询中选择所有内容会更有效。但是,一旦您选择了很多行,在多个查询中选择较小的集合就会变得更加高效。

    但是,根据线程,这已在 v3.0.0 中更新(我们目前在 2.2.1)。

    所以我的解决方案是创建一个存储过程,因为我认为 EFCore v3.0.0 尚未获得长期支持。但希望我能尽快升级它,这将解决我遇到的更多类似情况

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-05-27
      • 2021-04-27
      • 2014-03-22
      • 1970-01-01
      • 2013-01-06
      • 2014-06-08
      • 2017-11-20
      • 2021-03-23
      相关资源
      最近更新 更多