Эффективный промежуточный итог SQL Server в EF Core - PullRequest
0 голосов
/ 23 января 2019

Я пытаюсь выполнить запрос, подобный следующему:

SELECT r.*, (SELECT COUNT(UserID) FROM RoleUsers ru WHERE ru.RoleId = r.Id) AS Assignments
FROM Roles r

Чтобы получить количество пользователей для каждой роли.

Самый простой и простой способ реализовать желаемый результат:

this.DbContext.Set<Role>().Include(x => x.RoleUser)
                .Select(x => new { x, Assignments = x.RoleUsers.Count() });

Извлекает все роли, а затем N запросов для получения количества:

SELECT COUNT(*)
FROM [dbo].[RoleUsers] AS [r0]
WHERE @_outer_Id = [r0].[RoleId]

Что вообще не вариант. Я также попытался использовать GroupJoin, но он загружает все необходимые данные в одном запросе и выполняет группировку в памяти:

    this.DbContext.Set<Role>().GroupJoin(this.DbContext.Set<RoleUser>(), role => role.Id,
        roleUser => roleUser.RoleId, (role, roleUser) => new
        {
            Role = role,
            Assignments = roleUser.Count()
        });

Сгенерированный запрос:

SELECT [role].[Id], [role].[CustomerId], [role].[CreateDate], [role].[Description], [role].[Mask], [role].[ModifyDate], [role].[Name], [assignment].[UserId], [assignment].[CustomerId], [assignment].[RoleId]
FROM [dbo].[Roles] AS [role]
LEFT JOIN [dbo].[RoleUser] AS [assignment] ON [role].[Id] = [assignment].[RoleId]
ORDER BY [role].[Id]

Кроме того, я искал способ использования оконных функций, где я могу просто разделить счетчик по разделам и использовать разные роли, но я не знаю, как подключить оконную функцию в EF:

SELECT DISTINCT r.*, COUNT(ra.UserID) OVER(PARTITION BY ru.RoleId)
FROM RoleUsers ru
    RIGHT JOIN Roles r ON r.Id = ru.RoleId

Итак, есть ли способ избежать EntitySQL?

Ответы [ 2 ]

0 голосов
/ 23 января 2019

В настоящее время существует дефект в преобразовании агрегата запросов EF Core в SQL, когда проекция запроса содержит целую сущность, например

.Select(role => new { Role = role, ...}

Единственный известный мне обходной путь - проецировать на новую сущность (по крайней мере, это поддерживается EF Core) как

var query = this.DbContext.Set<Role>()
    .Select(role => new
    {
        Role = new Role { Id = role.Id, Name = role.Name, /* all other Role properies */ },
        Assignments = role.RoleUsers.Count()
    });

Это переводит в один SQL-запрос.Недостатком является то, что вы должны вручную проецировать все свойства объекта.

0 голосов
/ 23 января 2019
this.DbContext.Set<Role>()
     .Select(x => new { x, Assignments = x.RoleUsers.Count() });

вам не нужно добавлять include для RoleUser, так как вы используете оператор Select.Более того, я предполагаю, что вы используете LazyLoading там, где это ожидаемое поведение.Если вы используете готовую загрузку, результат вашего LINQ будет запущен в одном запросе.

вы можете использовать context.Configuration.LazyLoadingEnabled = false; перед запросом LINQ, чтобы отключить отложенную загрузку специально для этой операции

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...