【问题标题】:How to avoid n+1 queries in EF Core 2.1?如何避免 EF Core 2.1 中的 n+1 查询?
【发布时间】:2018-08-27 00:16:53
【问题描述】:

我正在使用 EF Core 2.1 预览版,它本应减少 N+1 查询问题。 我正在尝试进行查询,选择带有帖子作者的论坛主题:

dbContext.ForumThreads
   .Include(t => t.Posts)
   .Take(n)
   .Select(t => new
   {
      t.Id,
      t.Title,
      PostAuhtors = t.Posts.Select(p => p.Author).Take(5)
   }).ToArray();

这会产生 n+1 个查询:对于每个 ForumThread,它会选择帖子作者

架构很简单:

public class ForumThread 
{
   public Guid Id {get;set;}
   public string Title {get;set;}
   public ICollection<ForumPost> Posts {get;set;}
}

public class ForumPost 
{
  public Guid Id {get;set;}
  public string Author {get;set;}
  public string Content {get;set;}
}

【问题讨论】:

    标签: c# sql-server entity-framework-core asp.net-core-2.0


    【解决方案1】:

    你的方法有利有弊。让我解释一下,

    您的方法

    缺点 => 查询 n+1 次。

    优点 => 您只请求了 5 位您的帖子的作者。 (n 次)。

    SELECT TOP(@__p_0) [t].[Id], [t].[Title] FROM [ForumThreads] AS [t] => 1 time
    
    SELECT TOP(5) [p].[Author] FROM [ForumPosts] AS [p] => n time (n => number of ForumThread)
    

    您可以根据数据库中数据的大小选择其他方法。

            var source = dbContext.ForumThreads.Include(t => t.Posts).Take(5).ToList();
            var result = source.Select(t => new { t.Id, t.Title, PostAuhtors = t.Posts.Select(p => p.Author).Take(5).ToList() }).ToArray();
    

    Pros => 查询 1 次。

    缺点 => 您从数据库中获取帖子的所有作者,然后过滤它们。 (根据您的数据大小,这可能会花费太多)。

        SELECT [t.Posts].[Id], [t.Posts].[Author], [t.Posts].[Content], [t.Posts].[ForumThreadId] FROM [ForumPosts] AS [t.Posts]
    INNER JOIN (SELECT TOP(@__p_0) [t0].[Id]
        FROM [ForumThreads] AS [t0] ORDER BY [t0].[Id]
    ) AS [t1] ON [t.Posts].[ForumThreadId] = [t1].[Id] ORDER BY [t1].[Id]
    

    【讨论】:

      【解决方案2】:

      我认为您可以通过更少的查询(只有 2 个)来实现这一点,在内存中进行一些行为。这段代码是否符合您的要求?

      class Program
          {
              static void Main(string[] args)
              {
                  using (var db = new SampleContext())
                  {
                      Console.ReadLine();
                      var result = db.Threads
                          .Include(t => t.Posts)
                          .Take(10)
                          .Select(t => new
                          {
                              t.Id,
                              t.Title,
                              t.Posts
                              // Do this in memory  
                              //PostAuhtors = t.Posts.Select(p => p.Author).Take(5)
                          }).ToArray();
      
                      Console.WriteLine($"» {result.Count()} Threads.");
                      foreach (var thread in result)
                      {
                          // HERE !!
                          var PostAuhtors = thread.Posts.Select(p => p.Author).Take(5);
                          Console.WriteLine($"» {thread.Title}:  {string.Join("; ", PostAuhtors)} authors");
                      }
                      Console.ReadLine();
                  }
              }
          }
      
          public class SampleContext : DbContext
          {
              public static readonly LoggerFactory MyLoggerFactory = new LoggerFactory(new[] {
                      new ConsoleLoggerProvider((category, level)
                          => category == DbLoggerCategory.Database.Command.Name
                             && level == LogLevel.Debug, true)
                  });
      
              public DbSet<ForumThread> Threads { get; set; }
              public DbSet<ForumPost> Posts { get; set; }
      
              protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
              {
                  optionsBuilder  
                      .EnableSensitiveDataLogging()
                      .UseLoggerFactory(MyLoggerFactory)
                      .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFStart;Trusted_Connection=True;");
              }
          }
      
          public class ForumThread
          {
              public Guid Id { get; set; }
              public string Title { get; set; }
              public ICollection<ForumPost> Posts { get; set; }
          }
      
          public class ForumPost
          {
              public Guid Id { get; set; }
              public string Author { get; set; }
              public string Content { get; set; }
          }
      

      这是输出:

      【讨论】:

      • 我不想加载所有帖子,因为它可能是兆字节的数据
      • 那么我认为目前 EFCore 不允许其他行为,但正在解决这个问题:github.com/aspnet/EntityFrameworkCore/issues/4007
      • 嗯,就是这样。我正在使用 2.1 preview final ans 仍然收到 N+1 个查询
      • 就我所见,我认为错误在于发出 n+1 次查询的 Take(5),没有 Take 它只会产生 2 个。(#9282)也许一种选择是有 Where 子句来限制从线程发布? (我知道不是一回事……)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多