实体框架为分页查询生成低效的SQL

时间:2015-07-21 21:05:34

标签: c# sql-server linq entity-framework

我对一个实体有一个简单的分页linq查询:

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

Widgets表非常庞大,内部查询返回100000条记录,导致超时。

我能做些什么来改变这一代人?我做错了什么?

更新

我终于设法重构我的代码以相对快速地返回结果:

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虽然有效,却没有为第一页提供任何明显的优化;因为Where不是一个缓慢的操作。

3 个答案:

答案 0 :(得分:1)

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

在第一页中,这可以优化而不是Skip而只是Take,但在任何其他页面中你都可以。{/ p>

workarround可以在持久性中生成您自己的row_number(自动标识可以工作),并在代码中执行where(widget.number > (page*rows) ).Take(rows)。如果您的widget.number中有一个好的索引,那么查询应该非常快。 ,这会破坏动态orderBy

但是,我可以在您的代码中看到您始终按widget.id排序;因此,如果动态orderBy不是必需的,那么这可能是一种有效的解决方法。

  

你会自己吃药吗?

你可以问我吗。

不,我不会。解决此问题的最佳方法是使用持久性读取模型,即使每个窗口小部件具有一个表格,也可以使用自己的widget.number。问题是为这个问题建立一个具有持久性读取模型的系统是太疯狂了。拥有读模型是系统整体设计的一部分,需要从系统设计和开发的最初阶段就考虑到它。

答案 1 :(得分:1)

生成的查询非常复杂且嵌套,因为您使用了Skip方法。在T-SQL中,只需使用Top就可以轻松实现Take,但Skip不是这样 - 要应用它你需要row_number,这就是为什么有一个嵌套查询 - 内部返回row_number和外部过滤它们以获得正确行数。您的查询:

proxy: {
    type: 'ajax',
    // ....
    listeners: {
        'exception': function(proxy, request) {
            if (request.status === 0) {
                timeoutCallback();
            }
        }
    }
}

缺少跳过初始行。为了保持查询非常有效,最好不要使用Take和Skip来按ID继续按条件进行分页,因为您要根据该字段对行进行分页:

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

答案 2 :(得分:0)

AFAIK您无法更改实体生成的查询。虽然您可以强制实体运行原始SQL查询:

https://msdn.microsoft.com/en-us/data/jj592907.aspx

您还可以使用存储过程:

https://msdn.microsoft.com/en-us/data/gg699321.aspx

即使有机会改变生成的查询IMO,它也会随波逐流。我敢打赌,更容易自己编写SQL查询。