2015-07-21 72 views
8

我对一个实体的简单分页LINQ查询:实体框架生成低效SQL对分页查询

var data = (from t in ctx.ObjectContext.Widgets 
      where t.CampaignId == campaignId && 
       t.CalendarEventId == calendarEventId 
       (t.RecurringEventId IS NULL OR t.RecurringEventId = recurringEventId) 
      select t); 

data = data.OrderBy(t => t.Id); 

if (page > 0) 
{ 
    data = data.Skip(rows * (page - 1)).Take(rows); 
} 

var l = data.ToList(); 

我预期产生类似SQL:

select top 50 * from Widgets w where CampaignId = xxx AND CalendarEventId = yyy AND (RecurringEventId IS NULL OR RecurringEventId = zzz) order by w.Id 

当我运行上面在SSMS中查询,它会很快返回(必须先重建我的索引)。

但是,生成的SQL是不同的。它包含一个嵌套查询,如下所示:

SELECT TOP (50) 
[Project1].[Id] AS [Id], 
[Project1].[CampaignId] AS [CampaignId] 
<redacted> 
FROM (SELECT [Project1].[Id] AS [Id], 
[Project1].[CampaignId] AS [CampaignId], 
<redacted>, 
row_number() OVER (ORDER BY [Project1].[Id] ASC) AS [row_number] 
    FROM (SELECT 
     [Extent1].[Id] AS [Id], 
     [Extent1].[CampaignId] AS [CampaignId], 
     <redacted> 
     FROM [dbo].[Widgets] AS [Extent1] 
     WHERE ([Extent1].[CampaignId] = @p__linq__0) AND ([Extent1].[CalendarEventId] = @p__linq__1) AND ([Extent1].[RecurringEventId] = @p__linq__2 OR [Extent1].[RecurringEventId] IS NULL) 
    ) AS [Project1] 
) AS [Project1] 
WHERE [Project1].[row_number] > 0 
ORDER BY [Project1].[Id] ASC 

小部件表是巨大的和内查询返回的记录100000s,引起超时。

有什么我可以做的改变世代?我做错了什么?

UPDATE

我终于成功地重构我的代码相对快速地返回结果:

var data = (from t in ctx.ObjectContext.Widgets 
      where t.CampaignId == campaignId && 
       t.CalendarEventId == calendarEventId 
       (t.RecurringEventId IS NULL OR t.RecurringEventId = recurringEventId) 
      select t)).AsEnumerable().Select((item, index) => new { Index = index, Item = item }); 

      data = data.OrderBy(t => t.Index); 

      if (page > 0) 
      { 
       data = data.Where(t => t.Index >= (rows * (page - 1))); 
      } 

      data = data.Take(rows); 

注意,page > 0逻辑仅仅是用来防止使用无效的参数;它没有优化。实际上,page > 1虽然有效,但不会为第1页提供任何明显的优化;因为Where不是一个缓慢的操作。

+2

您能显示查询计划?我不明白为什么内部查询会在这里完整检索。 SQL执行的方式有问题。 –

+1

将'order by'添加到查询中时速度有多快?即'选择顶部50 *的小部件,其中CampaignId = xxx和CalendarEventId = yyy按ID排序' – Aducci

+1

您的快速SQL没有ORDER BY。如果你添加了会发生什么? – hvd

回答

1

之前的SQL Server 2012中,生成的SQL代码是执行分页的最佳方式。是的,它是非常有效而且效率非常低的,但是即使是手工编写自己的SQL scritp也是最好的。网络上有大量的数字墨水。只是谷歌它。

在firt页面中,可以优化不做Skip,只是Take,但是在任何其他页面你都可以做到。

workarround可能会在持久性中生成自己的row_number(自动身份识别可以工作),只需在代码中执行where(widget.number > (page*rows)).Take(rows)。如果您的widget.number中有一个好的索引,查询应该非常快。 但是,这打破了动态orderBy

但是,我可以在您的代码中看到您总是按widget.id排序;所以,如果动态orderBy不是必需的,这可能是一个有效的解决方法。

你会服用自己的药吗?

你能问我吗。

不,我不会。处理这个问题的最好方法是使用持久性读取模型,甚至可以为每个窗口小部件orderBy字段使用自己的widget.number。问题在于,为这个问题建立一个持久性读模型的系统太疯狂了。阅读模型是系统总体设计的一部分,需要从设计和开发系统的一开始就考虑到它。

+0

我认为LINQ中的自动标识就是答案。将发布更新,如果它的工作。 – Kev

1

生成的查询非常复杂并且嵌套,因为您使用了Skip方法。在T-SQL Take中,只需使用Top即可轻松实现,但Skip不是这种情况 - 要应用它,您需要row_number,这就是为什么存在嵌套查询的原因 - inner返回row_number行,外层过滤它们以获取正确行数。您的查询:

select top 50 * from Widgets w where CampaignId = xxx AND CalendarEventId = yyy AND (RecurringEventId IS NULL OR RecurringEventId = zzz) order by w.Id 

缺少跳过初始行。为了保持查询的效率,最好不要使用Take和Skip来通过Id条件保持分页,因为您正在基于该字段命令行进行分页:

var data = (from t in ctx.ObjectContext.Widgets 
     where t.CampaignId == campaignId && 
      t.CalendarEventId == calendarEventId 
      (t.RecurringEventId IS NULL OR t.RecurringEventId = recurringEventId) 
     select t); 

data = data 
    .OrderBy(t => t.Id); 
    .Where(t => t.Id >= rows * (page - 1) && t.Id < rows * page) 
    .ToList(); 
+0

我不认为这会有所帮助。匹配的Widgets的ID不会从1开始,并且Ids中可能存在空白。 – Kev