简体   繁体   English

使用联接防止Entity Framework查询中的SELECT N + 1问题

[英]Prevent SELECT N+1 issue in Entity Framework query using Joins

I'm trying to query something from an indirectly related entity into a single-purpose view model. 我正在尝试从间接相关的实体中查询某些内容到单一用途的视图模型中。 Here's a repro of my entities: 这是我实体的复制品:

public class Team {
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
    public List<Member> Members { get; set; }
}

public class Member {
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
}

public class Pet {
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
    public Member Member { get; set; }
}

Each class is in a DbSet<T> in my database context. 每个类都在我的数据库上下文中的DbSet<T>中。

This is the view model I want to construct from a query: 这是我想从查询中构造的视图模型:

public class PetViewModel {
    public string Name { get; set; }
    public string TeamItIndirectlyBelongsTo { get; set; }
}

I do so with this query: 我这样做与这个查询:

public PetViewModel[] QueryPetViewModel_1(string pattern) {
    using (var context = new MyDbContext(connectionString)) {
        return context.Pets
            .Where(p => p.Name.Contains(pattern))
            .ToArray()
            .Select(p => new PetViewModel {
                Name = p.Name,
                TeamItIndirectlyBelongsTo = "TODO",
            })
            .ToArray();
    }
}

But obviously there's still a "TODO" in there. 但是显然那里仍然有一个“ TODO”。

Gotcha: I can not change the entities at this moment , so I can't just include a List<Pet> property or a Team property on Member to help out. Gotcha:目前我无法更改实体 ,因此我不能仅在Member上包括List<Pet>属性或Team属性来提供帮助。 I want to fix things inside the query at the moment. 我现在想在查询中修复问题。

Here's my current solution: 这是我当前的解决方案:

public PetViewModel[] QueryPetViewModel_2(string pattern) {
    using (var context = new MyDbContext(connectionString)) {
        var petInfos = context.Pets
            .Where(p => p.Name.Contains(pattern))
            .Join(context.Members,
                p => p.Member.Id,
                m => m.Id,
                (p, m) => new { Pet = p, Member = m }
            )
            .ToArray();

        var result = new List<PetViewModel>();

        foreach (var info in petInfos) {
            var team = context.Teams
                .SingleOrDefault(t => t.Members.Any(m => m.Id == info.Member.Id));

            result.Add(new PetViewModel {
                Name = info.Pet.Name,
                TeamItIndirectlyBelongsTo = team?.Name,
            });
        }

        return result.ToArray();
    }
}

However, this has a "SELECT N+1" issue in there. 但是,这存在“ SELECT N + 1”问题。

Is there a way to create just one EF query to get the desired result, without changing the entities? 有没有一种方法可以只创建一个 EF查询来获得所需的结果,而无需更改实体?

PS. PS。 If you prefer a "plug and play" repro containing the above, see this gist . 如果您希望包含上述内容的“即插即用”复制品,请参见此要点

You've made the things quite harder by not providing the necessary navigation properties, which as @Evk mentioned in the comments do not affect your database structure, but allow EF to supply the necessary joins when you write something like pet.Member.Team.Name (what you need here). 通过不提供必要的导航属性,使事情变得更加困难,正如注释中提到的@Evk不会影响数据库的结构,但是当您编写诸如pet.Member.Team.Name类的pet.Member.Team.Name时,允许EF提供必要的联接pet.Member.Team.Name (这里需要什么)。

The additional problem with your model is that you don't have a navigation path neither from Team to Pet nor from Pet to Team since the "joining" entity Member has no navigation properties. 模型的另一个问题是您既没有从TeamPet的导航路径,也没有从PetTeam的导航路径,因为“加入的”实体Member没有导航属性。

Still it's possible to get the information needed with a single query in some not so intuitive way by using the existing navigation properties and unusual join operator like this: 仍然可以通过一些不那么直观的方式使用现有的导航属性和不寻常的join运算符来获得单个查询所需的信息,如下所示:

var result = (
    from team in context.Teams
    from member in team.Members
    join pet in context.Pets on member.Id equals pet.Member.Id
    where pet.Name.Contains(pattern)
    select new PetViewModel
    {
        Name = pet.Name,
        TeamItIndirectlyBelongsTo = team.Name
    }).ToArray();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM