.Take()的位置对生成的SQL没有影响

时间:2014-02-28 20:12:03

标签: c# .net linq entity-framework

我注意到.Take()的位置对生成的SQL没有影响。例如,说我有这个问题:

IQueryable<Item> query = 
    db.Items.Where(i => i.CategoryID == categoryID && i.Deleted == false)
            .OrderByDescending(i => i.ItemID).Skip(0);

然后执行以下两个查询:

IQueryable<ItemViewModel> query1 = 
    query.Take(20).Select(i => new ItemViewModel { ItemID = i.ItemID });
IQueryable<ItemViewModel> query2 = 
    query.Select(i => new ItemViewModel { ItemID = i.ItemID }).Take(20);

实体框架将为两个查询生成以下相同的SQL:

SELECT TOP (20) 
  [Project1].[ItemID] AS [ItemID], 
FROM ( SELECT [Project1].[ItemID] AS [ItemID],
   row_number() OVER (ORDER BY [Project1].[ItemID] DESC) AS [row_number]
    FROM ( SELECT 
        [Filter1].[ItemID] AS [ItemID], 
        FROM   (SELECT [Extent1].[ItemID] AS [ItemID], 
    [Extent1].[CategoryID] AS [CategoryID]
    FROM  [dbo].[Items] AS [Extent1]
            WHERE 0 = [Extent1].[Deleted] ) AS [Filter1]
        WHERE ([Filter1].[CategoryID] = @p__linq__0) AND (@p__linq__0 IS NOT NULL)
    )  AS [Project1]
)  AS [Project1]
WHERE [Project1].[row_number] > 0
ORDER BY [Project1].[ItemID] DESC

为什么这样做?它不应该在子查询上使用TOP 20为query1生成以下内容吗?

SELECT 
    [Project1].[ItemID] AS [ItemID]
    FROM ( SELECT [Project1].[ItemID] AS [ItemID]
        FROM ( SELECT TOP (20)
            [Extent1].[ItemID] AS [ItemID], 
            row_number() OVER (ORDER BY [Extent1].[ItemID] DESC) AS [row_number]
            FROM [dbo].[Items] AS [Extent1]
            WHERE ([Extent1].[CategoryID] = @p__linq__0) AND (@p__linq__0 IS NOT NULL)  
            AND (0 = [Extent1].[Deleted]) AND [Extent1].[row_number] > 0
            ORDER BY [Extent1].[ItemID] DESC
        )  AS [Project1]
    )  AS [Project1]

我注意到,在我的实际查询中,移动TOP 20会将时间从7秒增加到立即响应,因为它不会在TOP 20之前进行投影。

编辑:遗憾的是,自query.Take(20).Select(i => new { ... }).ToString() == query.Select(i => new { .. }).Take(20).ToString()以来,我似乎无法找到强制实体框架执行此操作的方法。也许这是EF中的一个错误?

2 个答案:

答案 0 :(得分:4)

在您提到的特定情况下,您提供的两个LINQ查询在功能上是等效的,因为无论TakeSelect的顺序如何,结果集将始终相同。

至于性能问题,两个查询都将由数据库平台进行优化。我会非常惊讶地发现两者之间存在显着差异。我不希望第二个查询,例如,对它知道不会超过TOP的一大堆项目执行投影。 LINQ查询提供程序倾向通常不关注优化,因为当SQL被转换为实际可执行代码时,该步骤往往发生在数据库级别。如今,数据库花费了大量精力来优化SQL代码的编译,因此查询提供者根本不需要复制这些工作。

但是,当您说过滤或排序时,它会更改查询实际返回的内容。

query.Take(10).Where(someFilter);

不一定(必然)返回相同的内容:

query.Where(someFilter).Take(10);

第一项需要10项并且返回但是其中许多项目都通过了过滤器。

第二个查询最多返回10个全部通过过滤器的项目。

对于您显示的两个SQL查询,它们在功能上是不同的,因为一个是在em表中中的每一个项目然后 em>抓取20个项目,另一个抓住前20个项目,然后订购它们,这比操作更快

案例中,将Take的语义顺序正确地转换为SQL非常重要。如果查询提供者可以证明两个给定操作的顺序并不重要,那么根据需要重新排序它们不是问题。

答案 1 :(得分:1)

答案其实非常简单。你基本上是在比较这两个问题:

select top 20 ItemId
from Items
order by ItemId desc

为:

select ItemId
from (
   select top 20 ItemId
   from Items
) p
order by ItemId descending

后者将随机挑选20行,而不是最高ItemIds的20行。 是你期望但没有获得的查询更快的原因:它不正确并且更容易由数据库运行。