Как я могу использовать подзапрос внутри выражения select? - PullRequest
1 голос
/ 19 сентября 2019

В моей базе данных есть две таблицы Organizations и OrganizationMembers с отношением 1: N.

Я хочу выразить запрос, который возвращает каждой организации имя и фамилию первый владелец организации .

Мое текущее выражение выбора работает, но оно неэффективно и не подходит мне, поскольку каждый подзапрос определяется несколько раз.

await dbContext.Organizations
    .AsNoTracking()
    .Select(x =>
    {
        return new OrganizationListItem
        {
            Id = x.Id,
            Name = x.Name,
            OwnerFirstName = (x.Members.OrderBy(member => member.CreatedAt).First(member => member.Role == RoleType.Owner)).FirstName,
            OwnerLastName = (x.Members.OrderBy(member => member.CreatedAt).First(member => member.Role == RoleType.Owner)).LastName,
            OwnerEmailAddress = (x.Members.OrderBy(member => member.CreatedAt).First(member => member.Role == RoleType.Owner)).EmailAddress
        };
    })
    .ToArrayAsync();

Можно ли как-то суммировать или повторно использовать подзапросы, поэтому мне не нужно определять их несколько раз?

Обратите внимание, что я уже пытался сохранить результат подзапроса в переменной.Это не работает, потому что требует преобразования выражения в тело оператора, что приводит к ошибке компилятора.

Ответы [ 3 ]

2 голосов
/ 19 сентября 2019

Подзапрос можно использовать повторно, введя промежуточную проекцию (Select), которая эквивалентна оператору let в синтаксисе запроса.

Например:

dbContext.Organizations.AsNoTracking()
    // intermediate projection
    .Select(x => new
    {
        Organization = x,
        Owner = x.Members
            .Where(member => member.Role == RoleType.Owner)
            .OrderBy(member => member.CreatedAt)
            .FirstOrDefault()
    })
    // final projection
    .Select(x => new OrganizationListItem
    {
        Id = x.Organization.Id,
        Name = x.Organization.Name,
        OwnerFirstName = Owner.FirstName,
        OwnerLastName = Owner.LastName,
        OwnerEmailAddress = Owner.EmailAddress
    })

Обратите внимание, что в pre EF Core 3.0 вы должны использовать FirstOrDefault вместо First, если хотите избежать оценки клиента.

Также это не делает сгенерированный SQL-запрос лучше / быстрее - он по-прежнему содержит отдельныйвстроенный подзапрос для каждого свойства, включенного в окончательный выбор.Следовательно, это улучшит читабельность, но не эффективность.

Именно поэтому обычно лучше проецировать вложенный объект в невыгруженное свойство DTO, т.е. вместо OwnerFirstName, OwnerLastName, OwnerEmailAddress есть класс со свойствами FirstName, LastName, EmailAddress и свойство, скажем, Owner этого типа в OrganizationListItem (аналогично объекту со ссылочным свойством навигации).Таким образом, вы сможете использовать что-то вроде

dbContext.Organizations.AsNoTracking()
    .Select(x => new
    {
        Id = x.Organization.Id,
        Name = x.Organization.Name,
        Owner = x.Members
            .Where(member => member.Role == RoleType.Owner)
            .OrderBy(member => member.CreatedAt)
            .Select(member => new OwnerInfo // the new class
             {
                 FirstName = member.FirstName,
                 LastName = member.LastName,
                 EmailAddress = member.EmailAddress
             })
            .FirstOrDefault()
    })

К сожалению, в версиях до 3.0 EF Core будет генерировать N + 1 SQL-запросов для этого запроса LINQ, но в 3.0+ он будет генерировать один и довольно эффективныйSQL-запрос.

0 голосов
/ 19 сентября 2019

Как насчет этого, как

await dbContext.Organizations
    .AsNoTracking()
    .Select(x => new OrganizationListItem
        {
            Id = x.Id,
            Name = x.Name,
            OwnerFirstName = x.Members.FirstOrDefault(member => member.Role == RoleType.Owner).FirstName,
            OwnerLastName = x.Members.FirstOrDefault(member => member.Role == RoleType.Owner)).LastName,
            OwnerEmailAddress = x.Members.FirstOrDefault(member => member.Role == RoleType.Owner)).EmailAddress
        })
    .ToArrayAsync();
0 голосов
/ 19 сентября 2019

Как насчет этого:

await dbContext.Organizations
    .AsNoTracking()
    .Select(x =>
    {
        var firstMember = x.Members.OrderBy(member => member.CreatedAt).First(member => member.Role == RoleType.Owner);
        return new OrganizationListItem
        {
            Id = x.Id,
            Name = x.Name,
            OwnerFirstName = firstMember.FirstName,
            OwnerLastName = firstMember.LastName,
            OwnerEmailAddress = firstMember.EmailAddress
        };
    })
    .ToArrayAsync();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...