Подзапрос можно использовать повторно, введя промежуточную проекцию (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-запрос.