Linq to Entity使用SelectMany跳过OrderBy

时间:2014-08-24 19:11:04

标签: c# linq entity-framework

我使用Linq to Entity(代码优先)遇到了一个非常奇怪的行为。

我的所有实体,上下文,数据库都运行良好,这是一个正在开发,更新和在线两年的持续项目。我正在使用.NET 4.5,EF 5.对于该特定查询,LazyLoading被禁用(而不是激活它会改变任何东西)。

我有以下表格(我只提及与此问题相关的内容):

  • 举办游戏的游戏
  • 按下,其中包含有关游戏的新闻文章
  • PressTypes,其中包含新闻文章的类别
  • Press_Games,它定义了Press to Games之间的一对多(一篇文章可以讲述很多游戏)

关于表及其匹配实体:

  • 游戏有很多领域,但让我们说它只有两个:ID(guid)和Name,游戏的名字。
  • Press也有很多字段,其中包括:ID(guid),CategoryID(guid)和UpdateDate(DateTime),表示文章上次更新的时间。
  • 按实体有一个类别对象和一​​个IEnumerable游戏。
  • 游戏实体没有按下导航属性(打算并且需要这种方式)
  • PressTypes是新闻文章类别的列表,并具有ID(guid)
  • Press_Games有两个字段,GameID(guid)和PressID(guid)

我需要获取与特定类别的最新新闻文章相关联的5款游戏的列表。我通过以下查询检索它们:

Context.Press
    .Where(press => press.Category.ID == MagicValues.ReviewGuid)) // MagicValues.ReviewGuid returns a Guid
    .OrderByDescending(press => press.UpdateDate)
    .SelectMany(press => press.Games)
    .Take(5);

结果列表会得到一个游戏列表,但不是符合预期的时间顺序文章顺序的列表。使用LINQPad我注意到生成的查询如下:

SELECT TOP (5) 
[Join1].[ID] AS [ID], 
[Join1].[Name] AS [Name], 
FROM  [dbo].[Press] AS [Extent1]
INNER JOIN  (SELECT [Extent2].[PressID] AS [PressID], [Extent3].[ID] AS [ID] /* lots of selected fields */
    FROM  [dbo].[Press_Games] AS [Extent2]
    INNER JOIN [dbo].[Games] AS [Extent3] ON [Extent3].[ID] = [Extent2].[GameID] ) AS [Join1] ON [Extent1].[ID] = [Join1].[PressID]
WHERE cast('b5c18183-14e2-4bf2-b4e1-641b56694c55' as uniqueidentifier) = [Extent1].[CategoryID]

没有ORDER BY。如果我略微改变查询以选择文章而不是游戏,我会按照预期的顺序获得正确的文章列表及其游戏:

Context.Press
    .Include(press => press.Games)
    .Where(press => press.Category.ID == MagicValues.ReviewGuid)) // MagicValues.ReviewGuid returns a Guid
    .OrderByDescending(press => press.UpdateDate)
    .Take(25);

生成的SQL查询变为:

SELECT 
[Project2].[C1] AS [C1], 
[Project2].[ID] AS [ID], 
[Project2].[Name] AS [Name], 
FROM ( 
    /* Lots of irrelevant stuff with JOINs and SELECTs */
)  AS [Project2]
ORDER BY [Project2].[UpdateDate] DESC, [Project2].[ID] ASC, [Project2].[C2] ASC

我收到了ORDER BY(因为它的工作或多或少是预期的)。

所以(最后)我的问题是:这种行为是预期还是错误?

我在想,既然我正在使用SelectMany,EF似乎认为只需要Games表,因此忽略了PressBy上的OrderBy。它可能有意义,但似乎有点违反直觉。

我会找到一种方法来规避问题(除非实际上有一个干净的解决办法),但我对这种行为和解释感到好奇。

1 个答案:

答案 0 :(得分:3)

这是关于EF的内部机制,所以我必须在这里猜测。看起来EF建立最经济的查询可能会让它在这里犯错。

查询1

让我们看一些简化的查询来证明会发生什么。

首先,准系统形式......

from p in Context.Press
from g in p.Games
select g

这相当于Context.Press.SelectMany(press => press.Games)。请注意,PressGame之间存在多对多关联,因为您已获得此联结表Press_Games。您可以将相同的Game分配给多个Press对象(尽管您可能不会这样做)。

EF的查询生成功能经常被诋毁,但至少它足够聪明,可以看到在这个准系统查询中只需要表GamesPress_Games来生成输出。 Press不在SQL查询中。

如果添加谓词...

from p in Context.Press
from g in p.Games
where p.Category.ID == guid
select g

...您将看到Press已加入以满足谓词。 SQL查询仅包含连接和谓词所必需的Press个字段。另一个优化是在SQL查询中使用Press.CategoryIDCategory未加入。

因此,EF在减少SQL查询中访问的表和字段数量方面投入了大量精力。这项工作似乎由输出驱动:未返回Press个数据,未选择Press个数据。

不要让我们在准系统查询中添加排序(忽略这里不重要的下降部分)......

from p in Context.Press
orderby p.UpdateDate
from g in p.Games
select g

这个orderby条款没有任何效果!您将看到生成的SQL无论是否相同。

我认为EF"原因"输出大约只有Games - from g in p.Games是被要求的 - 所以查询中的其他内容都是关于如何获取数据,而不是如何塑造输出。

但如果你这样做......

from p in Context.Press
from g in p.Games
orderby p.UpdateDate
select g

...排序遵循请求的输出并应用。同样,我必须猜测EF的内在逻辑,但我认为这是正在发生的事情。

我使用查询语法,因为在流利的情况下,后面的LINQ语句转换为更加冗长的SelectMany重载。但此查询会按照您要查找的方式返回数据。

在我看来,orderby的位置并不重要(正如你所说的那样,它是违反直觉的),但是,确实如此。

查询2

这里,Press是请求的输出,因此无论在查询中的位置如何,都应用任何排序都是有意义的。请注意,选择包含Press的{​​{1}}与查询1中发生的情况完全不同。