【问题标题】:Entity Framework Query is too slow实体框架查询太慢
【发布时间】:2018-01-07 11:41:16
【问题描述】:

我必须对您的数据库进行复杂查询。但查询在 8000 毫秒处结束。我做错了吗?我使用 .net 1.1 和 Entity Framework core 1.1.2 版本。

var fol = _context.UserRelations
                  .Where(u => u.FollowerId == id && u.State == true)
                  .Select(p => p.FollowingId)
                  .ToArray();

var Votes = await _context.Votes
                          .OrderByDescending(c => c.CreationDate)
                          .Skip(pageSize * pageIndex)
                          .Take(pageSize)
                          .Where(fo => fol.Contains(fo.UserId))
                          .Select(vote => new
                {
                    Id = vote.Id,
                    VoteQuestions = vote.VoteQuestions,
                    VoteImages = _context.VoteMedias.Where(m => m.VoteId == vote.Id)
                        .Select(k => k.MediaUrl.ToString()),

                    Options =  _context.VoteOptions.Where(m => m.VoteId == vote.Id).Select( ques => new
                    {
                        OptionsID = ques.Id,
                        OptionsName =  ques.VoteOption,
                        OptionsCount =  ques.VoteRating.Count(cout => cout.VoteOptionsId == ques.Id),
                    }),
                    User = _context.Users.Where(u => u.Id == vote.UserId).Select(usr => new
                    {
                        Id = usr.Id,
                        Name = usr.UserProperties.Where(o => o.UserId == vote.UserId).Select(l => l.Name.ToString())
                            .First(),
                        Surname = usr.UserProperties.Where(o => o.UserId == vote.UserId)
                            .Select(l => l.SurName.ToString()).First(),
                        ProfileImage = usr.UserProfileImages.Where(h => h.UserId == vote.UserId && h.State == true)
                            .Select(n => n.ImageUrl.ToString()).First()
                    }),
                    NextPage = nextPage
                }).ToListAsync();

【问题讨论】:

  • 您的数据库中有一个或多个相关索引吗? (你应该可以在VS的输出窗口看到SQL请求,然后你可以在SQL Server Managment Studio中运行这个请求,看看实际的执行计划)
  • 我的数据库架构。 prntscr.com/hx6btm
  • 乍一看有点啰嗦
  • 难怪它很慢。你甚至明白你在那里做什么吗?对于每一票,您都在执行 3 个查询。因此,如果您有 1000 票,您将对数据库进行 3000 次(!!!!)查询。您如何期望它会很快?您应该考虑您想要获取的数据并使用 EF Cores eager loading 在几个查询中获取大部分数据

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


【解决方案1】:

查看您生成到服务器的 SQL 查询(以及此查询的结果)。对于 SQL Server,最好的选择是 SQL Server Profiler,其他服务器也有方法。

  • 您创建了两个查询。首先创建fol 数组,然后使用 Contains 将其传递给第二个查询。你知道这是如何工作的吗?您可能会使用与数组中的项目一样多的参数生成查询。它既不漂亮也不高效。此处不需要,将其合并到主查询中,您将只有一个参数。

  • 您在过滤之前进行分页,这真的应该是这样吗?还可以查看基于 id 过滤而不是简单跳过的其他分页方式。

  • 您在一个查询中执行了过多的附加查询。当您查询三个每个包含 100 个项目的子列表时,您不会得到 300 行。要在一个查询中获取它,您需要创建连接并实际获得 100*100*100 = 1000000 行。除非您确定框架可以将其拆分为多个查询(可能不能),否则您应该在单独的查询中查询子列表。这可能是您遇到的主要性能问题。

  • 请使用单数来命名表格,而不是复数

  • 对于性能分析,索引结构和执行计划是至关重要的信息,没有它们你真的不能说太多

【讨论】:

    【解决方案2】:

    如 cmets 中所述,您可能会执行 100、1000 或 10000 个查询。对于数据库中与第一个结果匹配的每个 Vote,您执行 3 个其他查询。

    对于第一个查询产生的 1000 票,您需要执行 3000 次其他查询才能获取数据。太疯狂了!

    您必须使用 EF Cores eager loading 功能通过很少的查询来获取此数据。如果你的模型设计得很好,relations and navigation properties 很容易。

    当您加载没有投影的平面模型(使用.Select)时,您必须使用.Include 告诉EF 它应该加载哪些其他相关实体。

    // Assuming your navigation property is called VoteMedia
    await _context.Votes.
        .Include(vote => vote.VoteMedia)
        ...
    

    这将加载所有带有投票的VoteMedia 对象。所以不需要额外的查询来获取它们。

    但如果您使用项目,则无需调用 .Include(事实上,当您在投影中引用导航属性时,它们甚至会被忽略)。

    // Assuming your navigation property is called VoteMedia
    await _context.Votes.
        .Include(vote => vote.VoteMedia)
        ...
        .Select( vote => new
        {
            Id = vote.Id,
            VoteQuestions = vote.VoteQuestions,
    
            // here you reference to VoteMedia from your Model
            // EF Core recognize that and will load VoteMedia too.
            //
            // When using _context.VoteMedias.Where(...), EF won't do that
            // because you directly call into the context
            VoteImages = vote.VoteMedias.Where(m => m.VoteId == vote.Id)
                .Select(k => k.MediaUrl.ToString()),
    
            // Same here
            Options = vote.VoteOptions.Where(m => m.VoteId == vote.Id).Select( ques => ... );
        }
    

    【讨论】:

    • 你确定它是这样工作的吗?根据我的测试,它会生成一个连接而不是多个查询。
    • 是的,它将创建一个(或多个连接)并通过单个查询获取数据。如果 EF Core 找不到在单个查询中生成的方法,它可能会决定将其拆分为多个查询。但它会尝试在尽可能少的查询中做到这一点。但是一个具有多个连接的查询比 3000 个单个查询要快得多。但如果过滤器是正确的,它只会获取所需的数据并将其正确映射到模型
    • 由于您使用导航属性来访问 VoteMedias 和 VoteOptions,您可以安全地从您的代码中删除 Where(m => m.VoteId == vote.id)
    • 此外,如果您对非外键/非主键进行过滤,请确保在该列上放置 index 以加快连接和查询速度
    • 我不认为 OP 做错了什么。是的,有一些多余的 Where 过滤器,但主要问题是 EF Core 查询翻译/实现导致 N + 1 个查询,而不是类似于 Include 的少数查询。他们的跟踪器肯定有问题。
    猜你喜欢
    • 2014-12-13
    • 1970-01-01
    • 1970-01-01
    • 2011-04-22
    • 2023-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多