如何在选择表达式中重用子查询?

Red*_*one 4 c# entity-framework-core

在我的数据库中,我有两个表OrganizationsOrganizationMembers,具有 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();
Run Code Online (Sandbox Code Playgroud)

是否可以以某种方式总结或重用子查询,所以我不需要多次定义它们?

请注意,我已经尝试将子查询结果存储在变量中。这不起作用,因为它需要将表达式转换为语句体,这会导致编译器错误。

Iva*_*oev 7

可以通过引入中间投影 ( 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
    })
Run Code Online (Sandbox Code Playgroud)

请注意,在 EF Core 3.0 之前的版本中FirstOrDefaultFirst如果您想避免客户端评估,则必须使用而不是。

此外,这不会使生成的 SQL 查询更好/更快 - 它仍然包含针对最终选择中包含的每个属性的单独内联子查询。因此会提高可读性,但不会提高效率。

这就是为什么通常最好将嵌套对象投影到未展平的 DTO 属性中,即,而不是OwnerFirstName, OwnerLastNameOwnerEmailAddress有一个具有属性的类FirstNameLastNameEmailAddress和属性让说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()
    })
Run Code Online (Sandbox Code Playgroud)

不幸的是,在 3.0 之前的版本中,EF Core 将为这个 LINQ 查询生成 N + 1 个 SQL 查询,但在 3.0+ 中,它将生成一个非常有效的 SQL 查询。