C# Linq到实体使用SelectMany跳过OrderBy
我在使用LINQtoEntity(代码优先)时遇到了一种非常奇怪的行为 我所有的实体、上下文和数据库都运行良好,这是一个持续进行的项目,开发、更新和在线两年。我使用的是.NET4.5,EF5。对于该特定查询,LazyLoading被禁用(激活它不会改变任何事情) 我有以下表格(我只提到与该问题相关的内容):C# Linq到实体使用SelectMany跳过OrderBy,c#,linq,entity-framework,C#,Linq,Entity Framework,我在使用LINQtoEntity(代码优先)时遇到了一种非常奇怪的行为 我所有的实体、上下文和数据库都运行良好,这是一个持续进行的项目,开发、更新和在线两年。我使用的是.NET4.5,EF5。对于该特定查询,LazyLoading被禁用(激活它不会改变任何事情) 我有以下表格(我只提到与该问题相关的内容): 游戏,哪个举行游戏 Press,其中包含有关游戏的新闻文章 PressTypes,用于保存新闻文章的类别 Press_Games,它定义了Press to Games之间的一对多(一篇文章
- 游戏,哪个举行游戏
- Press,其中包含有关游戏的新闻文章
- PressTypes,用于保存新闻文章的类别
- Press_Games,它定义了Press to Games之间的一对多(一篇文章可以是关于多个游戏的)
- 游戏有很多字段,但假设它只包含两个字段:ID(guid)和Name,即游戏的名称李>
- Press还有许多字段,其中我们有:ID(guid)、CategoryID(guid)和UpdateDate(DateTime),它们表示文章最后一次更新的时间
- Press实体有一个Category对象和一个IEnumerable对象
- 游戏实体没有按键导航属性(以这种方式设计和需要)
- PressTypes是新闻文章类别的列表,具有ID(guid)
- Press_游戏有两个字段,GameID(guid)和PressID(guid)
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
我从后面拿到了订单(这或多或少是它工作时所期待的)
所以(最后)我的问题是:这是预期的行为还是一个bug?
我在想,因为我使用的是SelectMany,EF似乎认为只需要游戏表,因此忽略了新闻上的OrderBy。这可能有道理,但似乎有点违反直觉
我会找到一种绕过这个问题的方法(除非有一个干净的解决方案),但我对这个行为和解释很好奇 这是关于EF的内部机制,所以我不得不在这里猜测。看来英孚聪明地构建了最经济的查询可能导致它在这里出错 问题1 让我们看看一些简化的查询,它们演示了发生了什么 首先,裸骨形态
from p in Context.Press
from g in p.Games
select g
这相当于Context.Press.SelectMany(Press=>Press.Games)
。顺便说一句,请注意,Press
和Game
之间存在多对多关联,因为您有一个连接表Press\u Games
。您可以将相同的游戏
分配给多个按键
对象(尽管您可能不这样做)
EF的查询生成功能经常被贬低,但至少它足够聪明,可以看到在这个简单的查询中,它只需要表格Games
和Press\u Games
来生成输出<代码>按不在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查询中访问的表和字段的数量方面投入了大量精力。这项工作似乎是由输出驱动的:否按
返回数据,否按
选择数据
不,让我们将顺序添加到基本查询中(忽略降序部分,这在这里不是必需的)
此orderby
子句没有任何效力!您将看到,生成的SQL无论有无都是相同的
我认为EF“认为”输出仅与Games
有关–《p.Games中g的是被要求的–因此查询中的所有其他内容都是关于如何获取数据,而不是如何塑造输出
但是如果你这样做了
from p in Context.Press
from g in p.Games
orderby p.UpdateDate
select g
…排序遵循请求的输出并应用。再一次,我不得不猜测英孚的内在逻辑,但我认为这就是发生的事情
我使用查询语法,因为在fluent中,后一个LINQ语句会转换为一个更为详细的SelectMany
重载。但是此查询返回您所查找的数据。
在我看来,orderby
的位置应该无关紧要(正如您所说,这是违反直觉的),但是,好吧,它确实如此
问题2
这里,Press
是请求的输出,因此应用任何排序都是有意义的,不管它在查询中的位置如何。请注意,选择包含Games
的Press
与查询1中发生的情况有很大不同。一些有类似问题的旧MSDN线程-感谢您提供此详细答案。你的建议也很有效。。。直到我在混合中加入一个Distinct():var games=from p in Press。。。游戏。独特()。采取(5);但在这个问题上,我认为这更多的是来自SQL Server的约束,因为它不能在不属于SELECT DISTINCT的字段上进行排序。然而,如果我“强制”EF对这个进行第一批处理,并对这个应用Distinct(),我会得到预期的结果:games.take(100.Distinct().take(5);生成的SQL查询与我在集合上运行SqlQuery时编写的非常相似。它也是合乎逻辑的:D
from p in Context.Press
from g in p.Games
orderby p.UpdateDate
select g