【问题标题】:Is there any way to optimize this LINQ to Entities query?有没有办法优化这个 LINQ to Entities 查询?
【发布时间】:2013-01-01 13:54:51
【问题描述】:

我被要求生成一份报告,该报告由针对 SQL Server 数据库的相当复杂的 SQL 查询驱动。由于报告的站点已经使用 Entity Framework 4.1,我想我会尝试使用 EF 和 LINQ 编写查询:

var q = from r in ctx.Responses
                    .Where(x => ctx.Responses.Where(u => u.UserId == x.UserId).Count() >= VALID_RESPONSES)
                    .GroupBy(x => new { x.User.AwardCity, x.Category.Label, x.ResponseText })
         orderby r.FirstOrDefault().User.AwardCity, r.FirstOrDefault().Category.Label, r.Count() descending
         select new
         {
             City = r.FirstOrDefault().User.AwardCity,
             Category = r.FirstOrDefault().Category.Label,
             Response = r.FirstOrDefault().ResponseText,
             Votes = r.Count()
         };

此查询统计投票,但仅来自已提交一定数量的所需最低投票数的用户。

从性能角度来看,这种方法完全是一场灾难,因此我们切换到 ADO.NET 并且查询运行得非常快。我确实使用 SQL Profiler 查看了 LINQ 生成的 SQL,虽然它看起来像往常一样糟糕,但我没有看到任何关于如何优化 LINQ 语句以使其更高效的线索。

这是直接的 TSQL 版本:

WITH ValidUsers(UserId)
AS
(
    SELECT UserId
    FROM Responses
    GROUP BY UserId
    HAVING COUNT(*) >= 103
)
SELECT d.AwardCity
    , c.Label
    , r.ResponseText
    , COUNT(*) AS Votes
FROM ValidUsers u
JOIN Responses r ON r.UserId = u.UserId
JOIN Categories c ON r.CategoryId = c.CategoryId
JOIN Demographics d ON r.UserId = d.Id
GROUP BY d.AwardCity, c.Label, r.ResponseText
ORDER BY d.AwardCity, s.SectionName, COUNT(*) DESC

我想知道的是:这个查询是不是太复杂以至于 EF 和 LINQ 无法有效处理,还是我错过了一个技巧?

【问题讨论】:

  • 我猜是所有的 FirstOrDefaults 造成的。您是否尝试在 groupby 之前添加let response = r.First()?或者交换 Select 和 OrderBy?像这样stackoverflow.com/a/5013740/736079
  • 是否有类似User.Responses的导航属性?
  • @jessehouwing 使用 let 响应有很大帮助,尽管 LINQ 版本仍然比 ADO.NET 慢得多。如果你输入这个作为答案,我至少会赞成它。我对 Jon Skeet 交换 select 和 order by 的策略有问题,主要是我不知道如何用这个结构来计算。
  • 我也很难理解查询。如果您将纯 SQL 分享给我们,可能会有所帮助。
  • @GertArnold 是的,我确实确认添加导航属性也有 3 倍的帮助。

标签: c# linq entity-framework optimization linq-to-entities


【解决方案1】:

使用 let 减少 r.First() 的数量可能会提高性能。可能还不够。

 var q = from r in ctx.Responses
                .Where()
                .GroupBy()
     let response = r.First()
     orderby response.User.AwardCity, response.Category.Label, r.Count() descending
     select new
     {
         City = response.User.AwardCity,
         Category = response.Category.Label,
         Response = response.ResponseText,
         Votes = r.Count()
     };

【讨论】:

  • 我将此标记为答案,因为它的效果大致相当于@GertArnold 提出的导航属性解决方案,但格特尚未将他的评论作为答案发布(对不起格特,我会给你点赞)。应该注意的是,即使应用了这两种优化,ADO.NET 仍然更快,但修改后的 LINQ 比原来快了几个数量级。
【解决方案2】:

也许这个改变提高了性能,删除了 where 子句中生成的嵌套 sql select

先获取每个用户的选票,放入Dictionary

var userVotes = ctx.Responses.GroupBy(x => x.UserId )
                             .ToDictionary(a => a.Key.UserId,  b => b.Count());

var cityQuery = ctx.Responses.ToList().Where(x => userVotes[x.UserId] >= VALID_RESPONSES)
               .GroupBy(x => new { x.User.AwardCity, x.Category.Label, x.ResponseText })
               .Select(r => new
                       {
                           City = r.First().User.AwardCity,
                           Category = r.First().Category.Label,
                           Response = r.First().ResponseText,
                           Votes = r.Count()
                       })
               .OrderByDescending(r => r.City, r.Category, r.Votes());

【讨论】:

  • 我已经尝试过这种方法。这里是错误:LINQ to Entities 不识别方法 'Int32 get_Item(Int32)' 方法,并且该方法无法翻译成存储表达式
  • 我想我错过了在 ctx.Responses 之后添加 ToList(),导致 ctx.Responses.ToList().Where(....
  • 我认为核心问题是 EF LINQ 驱动程序在构建实体 SQL 时不能使用对外部字典的引用。我很确定我从字面上复制并粘贴了您的解决方案,我所做的只是修复了第一个 ToDictionary lambda 的语法。
  • 我用内存对象对其进行了测试并工作了。我认为添加 ToList 会将您的所有记录带入内存,然后您就可以使用字典了。将尝试复制您的场景。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-04
  • 1970-01-01
  • 2021-10-27
相关资源
最近更新 更多