【问题标题】:Strange behaviour with LINQ-to-Entities IncludeLINQ-to-Entities Include 的奇怪行为
【发布时间】:2026-01-25 16:30:01
【问题描述】:

我意识到我并不完全理解 LINQ-to-Entities 中的 Include 方法。

以下面两个代码sn-ps为例。我希望它们产生相同的输出(尽管第一个版本可能更有效,因为它避免了JOINs)。

// Snippet 1
using (var db = new Db()) {
  var author = db.Authors.First();
  db.LoadProperty<Author>(author, o => o.Books);
  foreach (var book in author.Books) {
    db.LoadProperty<Book>(book, o => o.Editions);
    foreach (var edition in book.Editions)
      Response.Write(edition.Id + " - " + edition.Title + "<br />");
  }
}

Response.Write("<br />");

// Snippet 2
using (var db = new Db()) {
  var author = db.Authors.Include("Books.Editions").First();
  foreach (var book in author.Books) {
    foreach (var edition in book.Editions)
      Response.Write(edition.Id + " - " + edition.Title + "<br />");
  }
}

但是每个sn-p的输出是不同的:

1 - Some Book First Edition
2 - Another Book First Edition
3 - Another Book Second Edition
4 - Another Book Third Edition

8 - Some Book First Edition
9 - Another Book First Edition

第一个 sn-p 正确输出 {Edition Id} - {Edition Title},而第二个意外打印 {Book Id} - {Edition Title} 并且只给出每本书的第一版。

发生了什么事?有没有办法使用Include 实现所需的输出?

编辑 1:MySql 数据看起来像(更正):

Authors         = { { Id = 1, Name = "Some Author" } }

Books           = { { Id = 8, AuthorId = 1 },
                    { Id = 9, AuthorId = 1 } }

Editions        = { { Id = 1, Title = "Some Book First Edition" },
                    { Id = 2, Title = "Another Book First Edition" },
                    { Id = 3, Title = "Another Book Second Edition" },
                    { Id = 4, Title = "Another Book Third Edition" } }

EditionsInBooks = { { BookId = 8, EditionId = 1 },
                    { BookId = 9, EditionId = 2 },
                    { BookId = 9, EditionId = 3 },
                    { BookId = 9, EditionId = 4 } }

请注意,EditionId = 8Id = 9 是没有关系的。

上面的代码是我的完整代码,在Page_Load 中用于一个空的测试页面。

编辑 2:我已经测试了以下内容,但它们没有任何区别:

  1. var author = db.Authors.Include("Books.Editions").AsEnumerable().First();
  2. var author = db.Authors.Include("Books.Editions").Single(o =&gt; o.Id == 1);
  3. var author = db.Authors.Include("Books").Include("Books.Editions").First();

编辑 3:如果我启用延迟加载,以下工作(在 Snippet 2 中):

var author = db.Authors.First();

(我想这与 Snippet 1 基本相同。)

但是,不管延迟加载如何,这仍然会返回奇怪的输出:

var author = db.Authors.Include("Books.Editions").First();

编辑 4:非常抱歉,我歪曲了上面的表格结构。 (我有一个这样的日子。)现在更正了,以显示多对多的关系。请参阅编辑 1。

也是输出

((ObjectQuery)db.Authors.Include("Books.Editions").AsEnumerable())
  .ToTraceString()

SELECT
  `Project1`.`Id`,
  `Project1`.`Name`,
  `Project1`.`C2` AS `C1`,
  `Project1`.`id1`,
  `Project1`.`AuthorId`,
  `Project1`.`C1` AS `C2`,
  `Project1`.`id2`,
  `Project1`.`Title`
FROM (SELECT
  `Extent1`.`Id`,
  `Extent1`.`Name`,
  `Join2`.`Id` AS `id1`,
  `Join2`.`AuthorId`,
  `Join2`.`Id` AS `id2`,
  `Join2`.`Title`,
   CASE WHEN (`Join2`.`Id` IS NULL) THEN (NULL)
        WHEN (`Join2`.`BookId` IS NULL) THEN (NULL)
        ELSE (1) END AS `C1`,
   CASE WHEN (`Join2`.`Id` IS NULL) THEN (NULL)
        ELSE (1) END AS `C2`
   FROM `authors` AS `Extent1`
     LEFT OUTER JOIN (SELECT
       `Extent2`.`Id`,
       `Extent2`.`AuthorId`,
       `Join1`.`BookId`,
       `Join1`.`EditionId`,
       `Join1`.`Id` AS `Id1`,
       `Join1`.`Title`
       FROM `books` AS `Extent2`
       LEFT OUTER JOIN (SELECT
         `Extent3`.`BookId`,
         `Extent3`.`EditionId`,
         `Extent4`.`Id`,
         `Extent4`.`Title`
         FROM `editionsinbooks` AS `Extent3`
         INNER JOIN `editions` AS `Extent4`
           ON `Extent4`.`Id` = `Extent3`.`EditionId`) AS `Join1`
       ON `Extent2`.`Id` = `Join1`.`BookId`) AS `Join2`
     ON `Extent1`.`Id` = `Join2`.`AuthorId`) AS `Project1`
   ORDER BY
     `Project1`.`Id` ASC,
     `Project1`.`C2` ASC,
     `Project1`.`id1` ASC,
     `Project1`.`C1` ASC

CASE 语句很有趣,因为我的 MySql 字段都不能为空。

【问题讨论】:

  • 能否显示作者、书籍和版本表的全部内容?我猜你那里的数据比你看到的要多,甚至可能是重复的信息,并且 include 语句正在重新排序它,使 First() 返回不同的作者。
  • 我在这里必须同意@d_r_w。您可以尝试输出作者和书籍吗?
  • @d_r_w @John 查看我的编辑。另外,如果我输出edition.Id + " - " + book.Id + " - " + author.Id,它会得到正确的图书 ID 和作者 ID,但输出的版本 ID 与图书 ID 相同。
  • 您是否尝试过添加 .Include("Books") 以及当前包含?
  • 这正是我所需要的。实体框架的 MySQL 提供程序似乎充满了错误,尤其是在涉及联接时。我对 MySQL 和 EF 或它的任何缺点都不是很熟悉,但如果你想保证你的属性被正确导航并且你正在使用 MySQL,那么你将不得不在第一个 sn 时做它-p 的方式。

标签: c# .net mysql join linq-to-entities


【解决方案1】:

Entity Framework 的提供程序在编译 LINQ 语句中的 First() 表达式时可能存在错误。涉及Include 时偶尔会出现奇怪的现象:http://wildermuth.com/2008/12/28/Caution_when_Eager_Loading_in_the_Entity_Framework

尝试将第二个 sn-p 重写为:

using (var db = new Db()) {
    var author = db.Authors.Include("Books.Editions").AsEnumerable().First();
    foreach (var book in author.Books)
    {
        foreach (var edition in book.Editions)
        {
            Response.Write(edition.Id + " - " + edition.Title + "<br />");
        }
    }
}

如果这修复了您的输出,那么肯定是 First() 方法。

编辑:您的第三次编辑与 sn-p 1 做同样的事情是正确的。我无法理解包含语句是如何使事情如此糟糕的。我唯一可以鼓励的是查看它生成的 SQL 查询:

var sql = ((System.Data.Objects.ObjectQuery)db.Authors.Include("Books.Editions").AsEnumerable().First()).ToTraceString();

编辑 2:很可能问题出在您的 EF 的 MySQL 提供程序中,因为生成了疯狂的 sql 输出。

【讨论】:

  • 感谢您的建议,但恐怕输出相同。如果我用Single(o =&gt; o.Id == 1)替换First()也是一样的。
  • @James 你能看看我的编辑并告诉我们该语句的输出是什么吗?
  • 查看我的编辑。我还错误地指定了我的表结构,我忘记了存在多对多关系。现在已更正。对不起。 ://