包含LINQ到实体的奇怪行为

时间:2011-10-18 13:44:53

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

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

例如,请参阅下面的两个代码段。我希望它们能够产生相同的输出(尽管第一个版本可能更有效,因为它避免了JOIN)。

// 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 />");
  }
}

但每个代码段的输出都不同:

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

第一个代码段正确输出{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 = 8没有Id = 9

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

编辑2 :我测试过以下内容并没有什么区别:

  1. var author = db.Authors.Include("Books.Editions").AsEnumerable().First();
  2. var author = db.Authors.Include("Books.Editions").Single(o => o.Id == 1);
  3. var author = db.Authors.Include("Books").Include("Books.Editions").First();
  4. 编辑3 :如果启用延迟加载,则以下工作(在代码段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字段都不可为空。

1 个答案:

答案 0 :(得分:1)

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

尝试将第二个代码段重写为:

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()方法。

编辑:你对第三次编辑做正确与你的代码片段1相同是正确的。我无法理解这种包含语句是如何严重绊倒的。我唯一能鼓励的是查看它生成的SQL查询:

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

编辑2:考虑到生成疯狂的sql输出,很可能问题出现在你的EF MySQL提供程序中。