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

C# .Take()的位置对生成的SQL没有影响,c#,.net,linq,entity-framework,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); 为什么会这样?它不应该在子查询中使用Top20为query1生成以下

我注意到,
.Take()
的位置对生成的SQL没有影响。例如,假设我有以下查询:

IQueryable<Item> query = 
    db.Items.Where(i => i.CategoryID == categoryID && i.Deleted == false)
            .OrderByDescending(i => i.ItemID).Skip(0);
为什么会这样?它不应该在子查询中使用
Top20
为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]
我注意到,在我的实际查询中,将
Top20
移动到内部会将时间从7秒增加到立即响应,因为它不会在
Top20
之前进行投影


编辑:不幸的是,我似乎找不到强制实体框架这样做的方法,因为
query.Take(20).Select(I=>new{…}).ToString()==query.Select(I=>new{…}).Take(20).ToString()
。可能这是EF中的一个bug?

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

至于性能方面,数据库平台将对这两个查询进行优化。我会非常惊讶地看到两者之间有任何显著的差异。例如,我不希望第二个查询对它知道不会通过
TOP
的所有项执行投影。一般来说,LINQ查询提供程序往往不关注优化,这仅仅是因为当SQL被转换为实际的可执行代码时,该步骤往往发生在数据库级别。如今,数据库已经花费了大量精力来优化SQL代码的编译,因此查询提供程序根本不需要重复这些工作

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

query.Take(10).Where(someFilter);
不(必然)返回与以下内容相同的内容:

query.Where(someFilter).Take(10);
第一个接受10个项目,然后返回,不管这些项目中有多少通过了过滤器

第二个查询返回最多10个都通过过滤器的项

在您展示的两个SQL查询中,它们在功能上是不同的,因为一个是对while表中的每个项目进行排序,然后获取20个项目,另一个是获取前20个项目,然后对它们进行排序,这比操作快得多


在这种情况下,将
Take
的语义顺序正确地翻译成SQL是很重要的。如果查询提供程序可以证明两个给定操作的顺序并不重要,那么按照自己的意愿对它们重新排序并不是问题。

答案其实很简单。您基本上是在比较这两个查询:

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);
select top 20 ItemId
from Items
order by ItemId desc
致:


后者将随机选取20行,而不是具有最高
ItemIds
的20行。这就是为什么您期望但没有得到的查询速度更快:它是不正确的,而且由数据库运行要简单得多。

@dhsto如果您在查询结果的顶部,则不应该对整个数据库执行选择。老实说,如果是这样的话,我会怀疑查询提供程序有问题。如果由于任何原因未能优化,那么除了不使用LINQ和自己编写SQL之外,这并不会给您留下太多选择。@dhsto在ManagementStudio中运行SQL并查看。您将看到实际发生的排序,这是Servy所说的数据库引擎代表您执行的重新排序。看看在where子句中添加
会改变执行计划并导致转换错误的位置。@dhsto EF不负责进行此类优化。这是实际数据库的责任。我完全希望它能执行这样的优化。如果没有(这真的很令人震惊),那么这就是问题所在。@dhsto但事实并非如此。SQL提供程序没有理由不能优化查询,从而只对其中20个项执行投影。这当然是一个应该在DB上进行的优化,而不是EF,因为给定的SQL查询在逻辑上是等价的。@dhsto但关键是您的数据库未能优化查询,而不是EF。当我将
Top20
移动到生成的SQL的子查询中时,我忘了按顺序移动。在实际的C#代码中,我在执行
Take()
之前执行
.OrderByDescending()
。我同意您不会让实体框架将
top
order by
放在子查询中,因为这是等效的,在这种情况下,它们放在哪里都不重要。一旦这两种方法做了相同的事情,我非常怀疑您是否会看到它们在性能上的差异。当我也将
orderby
移动到生成的子查询中时,它仍然会立即发生。我不认为在整个表上按主键排序是个问题。我认为问题在于投影是在子查询中的整个表上完成的。对不起,John,我原来的“我认为数据库应该生成什么”是错误的。不管怎样,我认为它应该生成的速度还是快得多,这个答案是不正确的。它仍然快得多,这太疯狂了,哦,好吧!我猜查询优化器中总是有一些边缘情况,它们实际上应该是相同的(它不应该对整个表执行投影,不管它是如何编写的)。不管怎样,很高兴你明白了!
select top 20 ItemId
from Items
order by ItemId desc
select ItemId
from (
   select top 20 ItemId
   from Items
) p
order by ItemId descending