C# 如何在select表达式中重用子查询?

C# 如何在select表达式中重用子查询?,c#,entity-framework-core,C#,Entity Framework Core,在我的数据库中,我有两个表Organizations和OrganizationMembers,它们之间的关系为1:N 我想表达一个查询,返回每个组织的第一个组织所有者的名字和姓氏 我当前的select表达式可以工作,但它既不高效,也不适合我,因为每个子查询都会被定义多次 await dbContext.Organizations .AsNoTracking() .Select(x => { return new OrganizationListIte

在我的数据库中,我有两个表
Organizations
OrganizationMembers
,它们之间的关系为1:N

我想表达一个查询,返回每个组织的第一个组织所有者的名字和姓氏

我当前的select表达式可以工作,但它既不高效,也不适合我,因为每个子查询都会被定义多次

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();
是否有可能汇总或重用子查询,这样我就不需要多次定义它们了

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

这样如何:

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();

像这样做怎么样

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();

子查询可以通过引入中间投影(
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
    })
请注意,在EF Core 3.0之前的版本中,如果要避免客户评估,必须使用
FirstOrDefault
而不是
First

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

这就是为什么通常最好将嵌套对象投影到未格式化的DTO属性中,也就是说,不要使用
OwnerFirstName
OwnerLastName
OwnerEmailAddress
创建一个具有属性的类
FirstName
LastName
EmailAddress
和属性,比如说
OrganizationListItem
中该类型的
Owner
(类似于具有引用导航属性的实体)。这样你就可以使用

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将为此LINQ查询生成N+1个SQL查询,但在3.0+中,它将生成一个单一且非常高效的SQL查询。

谢谢,但我已经尝试过了。这是不可能的,并将导致错误:
带有语句体的lambda表达式无法转换为表达式树。
您是否尝试过对此linq使用“AsEnumerable()”?不直接建议这样做,因为它会将查询结果带到内存中,这可能会对代码的其余部分产生不必要的影响。OP不希望重复的部分
x.Members.FirstOrDefault(member=>member.Role==RoleType.Owner)
仍在代码中重复,那么代码解决了什么问题?