Странное поведение с LINQ-to-Entities - PullRequest
2 голосов
/ 18 октября 2011

Я понял, что не полностью понимаю метод Include в LINQ-to-Entities.

Например, возьмем два фрагмента кода ниже.Я ожидал бы, что они будут производить тот же вывод (хотя первая версия может быть более эффективной, потому что он избегает 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 } }

Обратите внимание, что Edition с Id = 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();

РЕДАКТИРОВАТЬ 3 : Если я включаю отложенную загрузку, будут работать следующие функции (в фрагменте 2):

var author = db.Authors.First();

(Thisпо сути, делает то же самое, что и Сниппет 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 Ответ

1 голос
/ 18 октября 2011

Возможно, ошибка в компиляторе провайдера Entity Framework выражения First() в операторе LINQ. Иногда бывает странно, когда 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: Вполне возможно, что проблема в вашем провайдере MySQL для EF, учитывая сумасшедший сгенерированный вывод sql.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...