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

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

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

  • Games,其中包含游戏
  • Press,其中包含有关游戏的新闻文章
  • PressTypes,保存新闻文章的类别
  • Press_Games,它定义了 Press to Games 之间的一对多(一篇文章可以是关于多个游戏的)

  • 关于表及其匹配实体:
  • Games 有很多字段,但假设它只有两个:ID (guid) 和 Name,游戏的名称。
  • Press 也有很多字段,其中我们有:ID(guid)、CategoryID(guid)和UpdateDate(DateTime),代表文章最后一次更新的时间。
  • Press Entity 有一个 Category 对象和一个 IEnumerable Games。
  • 游戏实体没有 Press 导航属性(以这种方式设计和需要)
  • 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]
    

    没有订单。如果我稍微改变查询以选择文章而不是游戏,我会按预期顺序获得正确的文章列表及其游戏:
    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) 。顺便说一下,请注意 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 数据。
    不,让我们向准系统查询添加排序(忽略这里不重要的降序部分)...
    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 是请求的输出,因此应用任何排序都是有意义的,无论它在查询中的位置如何。请注意,选择包含 PressGames 与查询 1 中发生的情况完全不同。

    关于c# - Linq to Entity 使用 SelectMany 跳过 OrderBy,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/25475326/

    10-11 01:53