链接到实体跳过顺序与SelectMany

本文关键字:顺序 SelectMany 实体 链接 | 更新日期: 2023-09-27 18:19:25

我遇到了相当奇怪的行为使用Linq实体(代码优先)。

我所有的实体,上下文,数据库都工作得很好,这是一个正在进行的项目,开发,更新和在线两年。我使用的是。net 4.5, EF 5。对于这个特定的查询,LazyLoading是被禁用的(并不是说激活它会改变什么)。

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

  • Games,保存游戏
  • Press,保存有关游戏的新闻文章
  • prestypes,保存新闻文章的类别
  • Press_Games,它定义了Press to Games之间的一对多关系(一篇文章可以涉及多款游戏)

关于表及其匹配实体:

  • Games有许多字段,但我们假设它只包含两个:ID (guid)和Name(游戏名称)。
  • Press也有许多字段,其中我们有:ID (guid), CategoryID (guid)和UpdateDate (DateTime),表示文章的最后一次更新。
  • Press Entity有一个Category Object,和一个IEnumerable Games。
  • 游戏实体没有按下导航属性(这是有意的,也是需要的)
  • prestypes是新闻文章类别的列表,并有一个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]

No 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返回(这或多或少是预期的,因为它正在工作)。

所以(最后)我的问题是:这个行为是预期的还是一个bug ?

我认为,因为我使用SelectMany, EF似乎认为只有Games表是必需的,因此忽略了order on Press。这可能有道理,但似乎有点违反直觉。

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

链接到实体跳过顺序与SelectMany

这是关于EF的内部机制,所以我不得不在这里猜测。看起来EF构建最经济查询的聪明才智导致它在这里犯了错误。

查询1

让我们看一些简化的查询来演示发生了什么。

首先,barebone形式…

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.CategoryID,不连接Category

因此EF在最小化SQL查询中访问的表和字段的数量上投入了大量的努力。这个努力似乎是由输出驱动的:没有返回Press数据,没有选择Press数据。

不,让我们为barebone查询添加排序(忽略降序部分,这里不是必需的)…

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

这个orderby子句没有任何效果!您将看到生成的SQL无论是否使用它都是相同的。

我认为这是"原因"。输出仅关于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是请求的输出,所以应用任何排序都是有意义的,而不管它在查询中的位置。注意,选择包含GamesPress与查询1中的情况完全不同。