我使用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]
没有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。它可能有意义,但似乎有点违反直觉。
我会找到一种方法来规避问题(除非实际上有一个干净的解决办法),但我对这种行为和解释感到好奇。
答案 0 :(得分:3)
这是关于EF的内部机制,所以我必须在这里猜测。看起来EF建立最经济的查询可能会让它在这里犯错。
让我们看一些简化的查询来证明会发生什么。
首先,准系统形式......
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的内在逻辑,但我认为这是正在发生的事情。
我使用查询语法,因为在流利的情况下,后面的LINQ语句转换为更加冗长的SelectMany
重载。但此查询会按照您要查找的方式返回数据。
在我看来,orderby
的位置并不重要(正如你所说的那样,它是违反直觉的),但是,确实如此。
这里,Press
是请求的输出,因此无论在查询中的位置如何,都应用任何排序都是有意义的。请注意,选择包含Press
的{{1}}与查询1中发生的情况完全不同。