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

Kev*_*Kev 8 c# linq sql-server 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(); 
Run Code Online (Sandbox Code Playgroud)

我希望它生成类似于的SQL:

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

当我在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
Run Code Online (Sandbox Code Playgroud)

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

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

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);
Run Code Online (Sandbox Code Playgroud)

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

jlv*_*ero 1

在 SQL Server 2012 之前,生成的 SQL 代码是执行分页的最佳方式。是的,它很糟糕而且效率很低,但即使是手工编写自己的 SQL 脚本,这也是你能做的最好的事情。网络上有大量关于此的数字墨水。只需谷歌一下即可。

在第一个页面中,这可以不做优化SkipTake 但在任何其他页面中你都可以优化。

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

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

你会自己吃药吗?

你能问我吗?

不我不会。处理这个问题的最佳方法是拥有一个持久性读取模型,在该模型中,您甚至可以为每个小部件 orderBy 字段拥有一个表及其自己的widget.number. 问题在于,仅针对此问题使用持久性读取模型对系统进行建模太疯狂了。拥有读取模型是系统整体设计的一部分,需要从系统设计和开发的一开始就考虑到这一点。