Есть ли способ запретить EF Core выполнять многократные обходы БД при вызове одной функции перечисления?
Примите во внимание это относительно простое выражение LINQ:
var query2 = context.CheckinTablets.Select(ct => new
{
Id = ct.Id,
DeviceName = ct.Name,
Status = ct.CheckinTabletStatuses
.OrderByDescending(cts => cts.TimestampUtc).FirstOrDefault()
}).ToList();
В прошломобъяснение состояло в том, что «один вызов перечисления преобразуется в один вызов БД» (если вы отключите отложенную загрузку).В EF Core это больше не так!
В EF 6.2.0 этот LINQ переведен на
SELECT [Extent1].[CheckinTabletID] AS [CheckinTabletID],
[Limit1].[TimestampUtc] AS [TimestampUtc]
--...
FROM [dbo].[CheckinTablet] AS [Extent1] OUTER APPLY (
SELECT TOP (1) [Project1].[CheckinTabletStatusID] AS [CheckinTabletStatusID],
[Project1].[CheckinTabletID] AS [CheckinTabletID],
[Project1].[TimestampUtc] AS [TimestampUtc]
FROM (
SELECT [Extent2].[CheckinTabletStatusID] AS [CheckinTabletStatusID],
[Extent2].[CheckinTabletID] AS [CheckinTabletID],
[Extent2].[TimestampUtc] AS [TimestampUtc]
--...
FROM [dbo].[CheckinTabletStatus] AS [Extent2]
WHERE [Extent1].[CheckinTabletID] = [Extent2].[CheckinTabletID]
) AS [Project1] ORDER BY [Project1].[TimestampUtc] DESC
) AS [Limit1];
Хотя это было довольно некрасиво, это было то, что следовало POLA довольно красиво.Более того, это было то, с чем мы могли бы работать для оптимизации стороны БД (индексов).
С EF Core 2.1.0 мы получаем что-то вроде этого:
SELECT [ct].[CheckinTabletID] AS [Id], [ct].[strName] AS [DeviceName] FROM [CheckinTablet] AS [ct]
exec sp_executesql N'SELECT TOP(1) [cts].[CheckinTabletStatusID], [cts].[CheckinTabletID], [cts].[TimestampUtc] FROM [CheckinTabletStatus] AS [cts] WHERE @_outer_Id = [cts].[CheckinTabletID] ORDER BY [cts].[TimestampUtc] DESC',N'@_outer_Id int',@_outer_Id=1
exec sp_executesql N'SELECT TOP(1) [cts].[CheckinTabletStatusID], [cts].[CheckinTabletID], [cts].[TimestampUtc] FROM [CheckinTabletStatus] AS [cts] WHERE @_outer_Id = [cts].[CheckinTabletID] ORDER BY [cts].[TimestampUtc] DESC',N'@_outer_Id int',@_outer_Id=2
exec sp_executesql N'SELECT TOP(1) [cts].[CheckinTabletStatusID], [cts].[CheckinTabletID], [cts].[TimestampUtc] FROM [CheckinTabletStatus] AS [cts] WHERE @_outer_Id = [cts].[CheckinTabletID] ORDER BY [cts].[TimestampUtc] DESC',N'@_outer_Id int',@_outer_Id=3
exec sp_executesql N'SELECT TOP(1) [cts].[CheckinTabletStatusID], [cts].[CheckinTabletID], [cts].[TimestampUtc] FROM [CheckinTabletStatus] AS [cts] WHERE @_outer_Id = [cts].[CheckinTabletID] ORDER BY [cts].[TimestampUtc] DESC',N'@_outer_Id int',@_outer_Id=4
exec sp_executesql N'SELECT TOP(1) [cts].[CheckinTabletStatusID], [cts].[CheckinTabletID], [cts].[TimestampUtc] FROM [CheckinTabletStatus] AS [cts] WHERE @_outer_Id = [cts].[CheckinTabletID] ORDER BY [cts].[TimestampUtc] DESC',N'@_outer_Id int',@_outer_Id=5
Да, это один вызовсначала получить все сущности (CheckinTablets), а затем вызывать каждую строку, чтобы получить статус для каждой сущности ...
Таким образом, за один вызов ToList()
Entity Framework делает n+1
вызовы базы данных.Это крайне нежелательно, есть ли способ отключить это поведение или обходной путь?
Edit 1:
.Include () не помогает решить проблему ... Этопо-прежнему выполняет n + 1 запросов к БД.
Редактировать 2 (кредит @ jmdon ):
Не возвращая объект, но простое значение делает только один вызов!Конечно, это не очень помогает, если вы не хотите выравнивать свою сущность или если вы хотите получить несколько значений из второй таблицы.Тем не менее, полезно знать!
var query2 = _context.CheckinTablets.Select(ct => new
{
Id = ct.Id,
DeviceName = ct.Name,
Status = new CheckinTabletStatus
{
Id = ct.CheckinTabletStatuses.OrderByDescending(cts => cts.TimestampUtc).FirstOrDefault().Id,
CheckinTabletId = ct.CheckinTabletStatuses.OrderByDescending(cts => cts.TimestampUtc).FirstOrDefault().CheckinTabletId,
}
}).ToList();
Производит один вызов в БД:
SELECT [ct].[intCheckinTabletID] AS [Id0],
[ct].[strName] AS [DeviceName],
(
SELECT TOP (1) [cts].[intCheckinTabletStatusID]
FROM [tCheckinTabletStatus] AS [cts]
WHERE [ct].[intCheckinTabletID] = [cts].[intCheckinTabletID]
ORDER BY [cts].[dtmTimestampUtc] DESC
) AS [Id],
(
SELECT TOP (1) [cts0].[intCheckinTabletID]
FROM [tCheckinTabletStatus] AS [cts0]
WHERE [ct].[intCheckinTabletID] = [cts0].[intCheckinTabletID]
ORDER BY [cts0].[dtmTimestampUtc] DESC
) AS [CheckinTabletId]
FROM [tCheckinTablet] AS [ct];