У меня есть проект ASP.NET Core 2.2 с EF Core 2.2 Code-First DB. У меня есть следующие сущности:
- Здание, которое в основном представляет собой адрес с некоторыми другими важными данными.
- Этаж, содержащий номер этажа. Здание может быть многоэтажным. На этаже должно быть ровно одно здание, в котором он находится.
- Комната, в которой есть номер. На этаже может быть несколько комнат. В комнате должен быть ровно один этаж.
- Рабочая группа, которая содержит информацию о количестве сотрудников в группе, о том, активна ли группа и когда группа начала работать (что может произойти в будущем).
- RoomOccupancy, который представляет собой соединительную таблицу между рабочей группой и комнатой и показывает, в какой комнате находится / была / будет рабочая группа.
Мне нужен список зданий с названием здания, сколько в нем этажей, сколько комнат в здании (не этаж) и сколько людей в настоящее время работает в здании.
В настоящее время я могу получить все данные, но переведенный SQL не является оптимальным и требует много обращений к БД. Мне удалось вручную написать одну инструкцию SQL select (с внутренним выбором) для решения этой проблемы, поэтому я знаю, что это должно быть возможно с помощью одного запроса.
dbContext.Buildings.Select(x=> new BuildingDatableElementDTO(){
BuildingId = b.Id,
Name = b.Name,
FloorCount = b.Floors.Count(),
//this is the part where problems start,
//this translates to multiple SQL statements
RoomCount = b.Floors.Sum(f=>f.Rooms.Count()),
// I replaced the next line with
// CurrentWorkerCount = 10, but a solution would be nice
CurrentWorkerCount = b.Floors.Sum(f=>f.Rooms
.Sum(r=>r.RoomOccupancies
.Where(o=>!o.WorkGroup.IsFinished && o.WorkGroup.StartDate < Datetime.Now).
.Sum(w => w.NumberOfEmployees)
))),
}).ToList();
В целях тестирования я заменил лямбда CurrentWorkerCount на CurrentWorkerCount = 10, потому что я могу понять, трудно ли его перевести в SQL, но все равно не удается создать один оператор SQL с помощью RoomCount.
Ведение журнала с информационным уровнем показывает следующее: «Выражение LINQ '« Sum () »' не может быть переведено и будет оцениваться локально» для каждого здания, которое имеет хотя бы один этаж. Затем у меня есть одна большая команда DbCommand (слишком длинная для копирования), затем одна команда DbCommand для каждого здания, которая считает количество комнат.
Я читал, что есть проблемы с агрегатами в EF Core 2.1, но я думаю, что для ORM не должно быть сложной задачей преобразовать эту проекцию в один запрос.
Я что-то делаю не так или это возможности LINQ и EF Core? Думаю, раньше я мог легко сделать это с помощью не-Core EF. Я читал о некоторых обходных путях для GroupBy и агрегатов, но в моем случае это не помогло.
ОБНОВЛЕНИЕ
Вот сгенерированный журнал (только интересные части). Я использую специальное решение для фильтрации, сортировки и разбиения по страницам, которое отлично справляется с простыми проблемами. В этом примере нет фильтрации, сортировка по названию зданий и базовая выборка (пропустить 0, взять 15). В базе данных только минимальное количество тестовых данных (15 зданий: одно по 1 этажу, другое - 2, из них в одном - 1 комната, в котором 1 рабочая группа со 100 сотрудниками). У меня также есть мягкое удаление с глобальным фильтром, настроенным для флага IsDeleted. Я не думаю, что эти вещи влияют на результаты, но вот они, может быть, они влияют.
- Выражение LINQ Sum () не может быть переведено и будет вычислено локально.
- Выражение LINQ Sum () не может быть переведено и будет вычислено локально.
- Выражение LINQ Sum () не может быть переведено и будет вычислено локально.
- Выражение LINQ Sum () не может быть переведено и будет вычислено локально.
- Выражение LINQ Sum () не может быть переведено и будет вычислено локально.
- Выражение LINQ Sum () не может быть переведено и будет вычислено локально.
- Выражение LINQ Sum () не может быть переведено и будет вычислено локально.
- Выражение LINQ Sum () не может быть переведено и будет вычислено локально.
- Выполненная команда DbCommand ("2" мс) [Parameters = ["@__ p_0 = '?' (DbType = Int32), @__ p_1 = '?' (DbType = Int32) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT CONVERT(VARCHAR(36), [x].[Id]) AS [BuildingId], [x].[Name], (
SELECT COUNT(*)
FROM [Floors] AS [x0]
WHERE ([x0].[IsDeleted] = 0) AND ([x].[Id] = [x0].[BuildingId])
) AS [FloorCount], [x].[Id]
FROM [Buildings] AS [x]
WHERE [x].[IsDeleted] = 0
ORDER BY [x].[Name]
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT (
SELECT COUNT(*)
FROM [Rooms] AS [x4]
WHERE ([x4].[IsDeleted] = 0) AND ([x3].[Id] = [x4].[FloorId])
)
FROM [Floors] AS [x3]
WHERE ([x3].[IsDeleted] = 0) AND (@_outer_Id = [x3].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id2 = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT [x10].[Id]
FROM [Floors] AS [x10]
WHERE ([x10].[IsDeleted] = 0) AND (@_outer_Id2 = [x10].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT (
SELECT COUNT(*)
FROM [Rooms] AS [x4]
WHERE ([x4].[IsDeleted] = 0) AND ([x3].[Id] = [x4].[FloorId])
)
FROM [Floors] AS [x3]
WHERE ([x3].[IsDeleted] = 0) AND (@_outer_Id = [x3].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id2 = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT [x10].[Id]
FROM [Floors] AS [x10]
WHERE ([x10].[IsDeleted] = 0) AND (@_outer_Id2 = [x10].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT (
SELECT COUNT(*)
FROM [Rooms] AS [x4]
WHERE ([x4].[IsDeleted] = 0) AND ([x3].[Id] = [x4].[FloorId])
)
FROM [Floors] AS [x3]
WHERE ([x3].[IsDeleted] = 0) AND (@_outer_Id = [x3].[BuildingId])
- Выполненная команда DbCommand ("0" мс) [Parameters = ["@_ outer_Id2 = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT [x10].[Id]
FROM [Floors] AS [x10]
WHERE ([x10].[IsDeleted] = 0) AND (@_outer_Id2 = [x10].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT (
SELECT COUNT(*)
FROM [Rooms] AS [x4]
WHERE ([x4].[IsDeleted] = 0) AND ([x3].[Id] = [x4].[FloorId])
)
FROM [Floors] AS [x3]
WHERE ([x3].[IsDeleted] = 0) AND (@_outer_Id = [x3].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id2 = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT [x10].[Id]
FROM [Floors] AS [x10]
WHERE ([x10].[IsDeleted] = 0) AND (@_outer_Id2 = [x10].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT (
SELECT COUNT(*)
FROM [Rooms] AS [x4]
WHERE ([x4].[IsDeleted] = 0) AND ([x3].[Id] = [x4].[FloorId])
)
FROM [Floors] AS [x3]
WHERE ([x3].[IsDeleted] = 0) AND (@_outer_Id = [x3].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id2 = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT [x10].[Id]
FROM [Floors] AS [x10]
WHERE ([x10].[IsDeleted] = 0) AND (@_outer_Id2 = [x10].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@__ Now_2 = '?' (DbType = DateTime2), @ _outer_Id3 = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT (
SELECT SUM([x14].[NumberOfEmployees])
FROM [RoomOccupancys] AS [x14]
LEFT JOIN [WorkGroups] AS [k.WorkGroup2] ON [x14].[WorkGroupId] = [k.WorkGroup2].[Id]
WHERE (([x14].[IsDeleted] = 0) AND (([k.WorkGroup2].[IsFinished] = 0) AND ([k.WorkGroup2].[StartDate] < @__Now_2))) AND ([x13].[Id] = [x14].[RoomId])
)
FROM [Rooms] AS [x13]
WHERE ([x13].[IsDeleted] = 0) AND (@_outer_Id3 = [x13].[FloorId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT (
SELECT COUNT(*)
FROM [Rooms] AS [x4]
WHERE ([x4].[IsDeleted] = 0) AND ([x3].[Id] = [x4].[FloorId])
)
FROM [Floors] AS [x3]
WHERE ([x3].[IsDeleted] = 0) AND (@_outer_Id = [x3].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id2 = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT [x10].[Id]
FROM [Floors] AS [x10]
WHERE ([x10].[IsDeleted] = 0) AND (@_outer_Id2 = [x10].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT (
SELECT COUNT(*)
FROM [Rooms] AS [x4]
WHERE ([x4].[IsDeleted] = 0) AND ([x3].[Id] = [x4].[FloorId])
)
FROM [Floors] AS [x3]
WHERE ([x3].[IsDeleted] = 0) AND (@_outer_Id = [x3].[BuildingId])
- Выполненная команда DbCommand ("0" мс) [Parameters = ["@_ outer_Id2 = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT [x10].[Id]
FROM [Floors] AS [x10]
WHERE ([x10].[IsDeleted] = 0) AND (@_outer_Id2 = [x10].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT (
SELECT COUNT(*)
FROM [Rooms] AS [x4]
WHERE ([x4].[IsDeleted] = 0) AND ([x3].[Id] = [x4].[FloorId])
)
FROM [Floors] AS [x3]
WHERE ([x3].[IsDeleted] = 0) AND (@_outer_Id = [x3].[BuildingId])
- Выполненная команда DbCommand ("0" мс) [Parameters = ["@_ outer_Id2 = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT [x10].[Id]
FROM [Floors] AS [x10]
WHERE ([x10].[IsDeleted] = 0) AND (@_outer_Id2 = [x10].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@__ Now_2 = '?' (DbType = DateTime2), @ _outer_Id3 = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT (
SELECT SUM([x14].[RemainingAmount])
FROM [RoomOccupancys] AS [x14]
LEFT JOIN [WorkGroups] AS [k.WorkGroup2] ON [x14].[WorkGroupId] = [k.WorkGroup2].[Id]
WHERE (([x14].[IsDeleted] = 0) AND (([k.WorkGroup2].[IsFinished] = 0) AND ([k.WorkGroup2].[StartDate] < @__Now_2))) AND ([x13].[Id] = [x14].[RoomId])
)
FROM [Rooms] AS [x13]
WHERE ([x13].[IsDeleted] = 0) AND (@_outer_Id3 = [x13].[FloorId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT (
SELECT COUNT(*)
FROM [Rooms] AS [x4]
WHERE ([x4].[IsDeleted] = 0) AND ([x3].[Id] = [x4].[FloorId])
)
FROM [Floors] AS [x3]
WHERE ([x3].[IsDeleted] = 0) AND (@_outer_Id = [x3].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id2 = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT [x10].[Id]
FROM [Floors] AS [x10]
WHERE ([x10].[IsDeleted] = 0) AND (@_outer_Id2 = [x10].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT (
SELECT COUNT(*)
FROM [Rooms] AS [x4]
WHERE ([x4].[IsDeleted] = 0) AND ([x3].[Id] = [x4].[FloorId])
)
FROM [Floors] AS [x3]
WHERE ([x3].[IsDeleted] = 0) AND (@_outer_Id = [x3].[BuildingId])
- Выполненная команда DbCommand ("0" мс) [Parameters = ["@_ outer_Id2 = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT [x10].[Id]
FROM [Floors] AS [x10]
WHERE ([x10].[IsDeleted] = 0) AND (@_outer_Id2 = [x10].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT (
SELECT COUNT(*)
FROM [Rooms] AS [x4]
WHERE ([x4].[IsDeleted] = 0) AND ([x3].[Id] = [x4].[FloorId])
)
FROM [Floors] AS [x3]
WHERE ([x3].[IsDeleted] = 0) AND (@_outer_Id = [x3].[BuildingId])
- Выполненная команда DbCommand ("0" мс) [Parameters = ["@_ outer_Id2 = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT [x10].[Id]
FROM [Floors] AS [x10]
WHERE ([x10].[IsDeleted] = 0) AND (@_outer_Id2 = [x10].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT (
SELECT COUNT(*)
FROM [Rooms] AS [x4]
WHERE ([x4].[IsDeleted] = 0) AND ([x3].[Id] = [x4].[FloorId])
)
FROM [Floors] AS [x3]
WHERE ([x3].[IsDeleted] = 0) AND (@_outer_Id = [x3].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id2 = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT [x10].[Id]
FROM [Floors] AS [x10]
WHERE ([x10].[IsDeleted] = 0) AND (@_outer_Id2 = [x10].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT (
SELECT COUNT(*)
FROM [Rooms] AS [x4]
WHERE ([x4].[IsDeleted] = 0) AND ([x3].[Id] = [x4].[FloorId])
)
FROM [Floors] AS [x3]
WHERE ([x3].[IsDeleted] = 0) AND (@_outer_Id = [x3].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id2 = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT [x10].[Id]
FROM [Floors] AS [x10]
WHERE ([x10].[IsDeleted] = 0) AND (@_outer_Id2 = [x10].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT (
SELECT COUNT(*)
FROM [Rooms] AS [x4]
WHERE ([x4].[IsDeleted] = 0) AND ([x3].[Id] = [x4].[FloorId])
)
FROM [Floors] AS [x3]
WHERE ([x3].[IsDeleted] = 0) AND (@_outer_Id = [x3].[BuildingId])
- Выполненная команда DbCommand ("0" мс) [Parameters = ["@_ outer_Id2 = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT [x10].[Id]
FROM [Floors] AS [x10]
WHERE ([x10].[IsDeleted] = 0) AND (@_outer_Id2 = [x10].[BuildingId])
- Выполненная команда DbCommand ("1" мс) [Parameters = ["@_ outer_Id = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT (
SELECT COUNT(*)
FROM [Rooms] AS [x4]
WHERE ([x4].[IsDeleted] = 0) AND ([x3].[Id] = [x4].[FloorId])
)
FROM [Floors] AS [x3]
WHERE ([x3].[IsDeleted] = 0) AND (@_outer_Id = [x3].[BuildingId])
- Выполненная команда DbCommand ("0" мс) [Parameters = ["@_ outer_Id2 = '?' (DbType = Guid) "], CommandType = 'Text', CommandTimeout = '30 ']"
SELECT [x10].[Id]
FROM [Floors] AS [x10]
WHERE ([x10].[IsDeleted] = 0) AND (@_outer_Id2 = [x10].[BuildingId])
x
иb
? Вы говорите, что используете LINQ to SQL, но похоже, что используете Linq to EF Core 2.1? - person NetMage   schedule 25.06.2019Floors
илиBuilding
дляSum
? - person NetMage   schedule 25.06.2019Sum
s можно заменить наSelectMany
и одинSum
, что, вероятно, сгенерирует немного менее неэффективный SQL. - person Gert Arnold   schedule 25.06.2019FloorCount
, похоже, для этого выполняется отдельный запрос для каждого здания. Я действительно видел, гдеCount()
может быть проблемой для EF Core в некоторых случаях. Полагаю, у вас есть критерий "не удален" вFloors
виртуальной коллекции? Помогает ли добавлениеInclude(b => b.Floors).ThenInclude(f => f.Rooms)
передSelect
? - person NetMage   schedule 25.06.2019SELECT ( SELECT COUNT(*) FROM [Rooms] AS [x4] WHERE ([x4].[IsDeleted] = 0) AND ([x3].[Id] = [x4].[FloorId]) ) FROM [Floors] AS [x3] WHERE ([x3].[IsDeleted] = 0) AND (@_outer_Id = [x3].[BuildingId])
я принес для стола. - person gyomihaly   schedule 26.06.2019