Aggregate of aggregate with EF Core Linq2Sql

后端 未结 2 1628
南方客
南方客 2021-01-23 13:19

I have a ASP.NET Core 2.2 project with EF Core 2.2 Code-First DB. I have the following entities:

  • Building, which is basically an address with some other importan
2条回答
  •  一个人的身影
    2021-01-23 14:21

    I read that there are problems with the aggregates with EF Core 2.1, but I think it shouldn't be a hard task for the ORM to translate this Projection into one query.

    You are right that EF Core had (and still have - the latest at this time v2.2) problems translating GroupBy and aggregates (and not only). But not for "shouldn't be a hard task" - try converting arbitrary expression tree to pseudo SQL yourself and you'll quickly find that it is quite complicated task.

    Anyway, EF Core query translation improves over the time, but as mentioned, is far from perfect. The showstopper in this case are nested aggregates - sum of sum/count etc. The solution is to flatten the target set and apply single aggregate. For instance, rewriting your LINQ query as follows:

    dbContext.Buildings.Select(b => new //BuildingDatableElementDTO()
    {
        BuildingId = b.Id,
        Name = b.Name,
        FloorCount = b.Floors.Count(),
        // (1)
        RoomCount = b.Floors.SelectMany(f => f.Rooms).Count(),
        // (2)
        CurrentWorkerCount = b.Floors
            .SelectMany(f => f.Rooms)
            .SelectMany(r => r.RoomOccupancies)
            .Select(o => o.WorkGroup)
            .Where(w => !w.IsFinished && w.StartDate < DateTime.Now)
            .Sum(w => w.NumberOfEmployees),
    })
    .ToList();
    

    is translated to a single SQL (as expected):

      SELECT [e].[Id] AS [BuildingId], [e].[Name], (
          SELECT COUNT(*)
          FROM [Floors] AS [e0]
          WHERE ([e0].[IsDeleted] = 0) AND ([e].[Id] = [e0].[BuildingId])
      ) AS [FloorCount], (
          SELECT COUNT(*)
          FROM [Floors] AS [e1]
          INNER JOIN (
              SELECT [e2].[Id], [e2].[FloorId], [e2].[IsDeleted], [e2].[Name]
              FROM [Rooms] AS [e2]
              WHERE [e2].[IsDeleted] = 0
          ) AS [t] ON [e1].[Id] = [t].[FloorId]
          WHERE ([e1].[IsDeleted] = 0) AND ([e].[Id] = [e1].[BuildingId])
      ) AS [RoomCount], (
          SELECT SUM([f.Rooms.RoomOccupancies.WorkGroup].[NumberOfEmployees])
          FROM [Floors] AS [e3]
          INNER JOIN (
              SELECT [e4].*
              FROM [Rooms] AS [e4]
              WHERE [e4].[IsDeleted] = 0
          ) AS [t0] ON [e3].[Id] = [t0].[FloorId]
          INNER JOIN (
              SELECT [e5].*
              FROM [RoomOccupancies] AS [e5]
              WHERE [e5].[IsDeleted] = 0
          ) AS [t1] ON [t0].[Id] = [t1].[RoomId]
          INNER JOIN [WorkGroups] AS [f.Rooms.RoomOccupancies.WorkGroup] ON [t1].[WorkgroupId] = [f.Rooms.RoomOccupancies.WorkGroup].[Id]
          WHERE (([e3].[IsDeleted] = 0) AND (([f.Rooms.RoomOccupancies.WorkGroup].[IsFinished] = 0) AND ([f.Rooms.RoomOccupancies.WorkGroup].[StartDate] < GETDATE()))) AND ([e].[Id] = [e3].[BuildingId])
      ) AS [CurrentWorkerCount]
      FROM [Building] AS [e]
      WHERE [e].[IsDeleted] = 0
    

提交回复
热议问题