我在使用 Linq to Entity(代码优先)时遇到了一个非常奇怪的行为。
我所有的实体、上下文、数据库都运行良好,这是一个持续开发、更新和在线两年的项目。我正在使用 .NET 4.5、EF 5。对于那个特定的查询,LazyLoading 被禁用(不是激活它会改变任何东西)。
我有以下表格(我只是提到与问题相关的内容):
关于表及其匹配实体:
我需要获取链接到特定类别的最新新闻文章的 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]
没有订单。如果我稍微改变查询以选择文章而不是游戏,我会按预期顺序获得正确的文章列表及其游戏:
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 表,因此忽略 OrderBy on Press。这可能是有道理的,但似乎有点违反直觉。
我会找到一种方法来规避这个问题(除非实际上有一个干净的解决方案),但我对行为和解释很好奇。
最佳答案
这是关于EF的内部机制,所以我不得不在这里猜测。看起来 EF 聪明地构建最经济的查询可能导致它在这里出错。
查询 1
让我们看一些演示会发生什么的简化查询。
首先,准系统形式...
from p in Context.Press
from g in p.Games
select g
这相当于 Context.Press.SelectMany(press => press.Games)
。顺便说一下,请注意 Press
和 Game
之间存在多对多的关联,因为您有这个连接表 Press_Games
。您可以将相同的 Game
分配给多个 Press
对象(尽管您可能不会这样做)。EF 的查询生成功能经常被贬低,但至少它足够聪明地看到,在这个准系统查询中,它只需要表
Games
和 Press_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.CategoryID
,没有加入Category
。因此,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 的内在逻辑,但我认为这就是正在发生的事情。我使用查询语法,因为在 fluent 中,后一个 LINQ 语句转换为更加冗长的
SelectMany
重载。但是 这个查询会以你想要的方式返回数据。 在我看来,
orderby
的位置应该无关紧要(如您所说,这是违反直觉的),但是,确实如此。查询 2
在这里,
Press
是请求的输出,因此应用任何排序都是有意义的,无论它在查询中的位置如何。请注意,选择包含 Press
的 Games
与查询 1 中发生的情况完全不同。关于c# - Linq to Entity 使用 SelectMany 跳过 OrderBy,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/25475326/