2011-10-18 4 views
2

LINQ-to-EntitiesのIncludeメソッドを完全に理解していないことに気付きました。LINQ-to-Entitiesでの奇妙な動作が含まれています

たとえば、次の2つのコードスニペットを使用します。私はそれらが同じ出力を生成すると期待します(最初のバージョンは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 

秒1が予期せず{Book Id} - {Edition Title}を印刷し、唯一の各書籍の初版を与えるのに対し、最初のスニペットが正しく、{Edition Id} - {Edition Title}を出力します。

何が起こっているのですか?そして、Includeを使用して希望の出力を達成する方法がありますか?

EDIT 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 } } 

注意をId = 8か​​とはEditionがないこと。

上記のコードは完全なコードです(空のテストページの場合はPage_Load)。

EDIT 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();

EDIT 3を:レイジーローディングを有効にすると、次のように動作します(スニペット2):

var author = db.Authors.First(); 

(これは基本的に私が思うスニペット1、と同じようにやっている。)

しかし、これはまだ遅延読み込みの関係なく、奇妙な出力を返します:

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

EDIT 4私は本当に申し訳ありませんが、私は上記のテーブル構造を誤解しました。 (私はその日のうちの1つを持っています)多対多の関係を示すために修正されました。編集1.

はまた

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

の出力はCASE文は私のMySQLのフィールドのどれもがNULL可能でないことを考えると、面白いです

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 

でご覧ください。

+2

著者、書籍、エディションのすべてのコンテンツを表示できますか?私はあなたの目に会うよりも多くのデータを持っていると思います。情報を重複させる可能性もあります。インクルードステートメントはそれを並べ替えて、 'First()'を別の著者に返します。 – doctorless

+0

ここで@d_r_wと同意しなければなりません。著者だけでなく書籍も出力できますか? –

+0

@d_r_w @John私の編集を参照してください。また、 'edition.Id +" - "+ book.Id +" - "+ author.Id'を出力すると、book IDとauthor IDは正しくなりますが、edition IDはbook IDと同じに出力されます。 – James

答えて

1

Entity FrameworkのプロバイダーがLINQステートメントのFirst()式をコンパイルする際にバグがある可能性があります。 Includeが関与している時に時折すごみがあります:http://wildermuth.com/2008/12/28/Caution_when_Eager_Loading_in_the_Entity_Framework

はされる第2のスニペットを書き換えてみます

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

EDIT:あなたの第三編集スニペット1として、私は文がひどく物事をトリップさなどがその方法を測深することはできません同じことをやっに関する正しいです。私が励ますことができる唯一のことは、それが生成のSQLクエリを見ることである。

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

EDIT 2:非常によく問題が狂ったSQL出力を考えると、EFのためのMySQLのプロバイダーであることを可能かもしれません生成される。

+0

あなたの提案をありがとうが、私は出力が同じであることを恐れています。 'First()'を 'Single(o => o.Id == 1) 'に置き換えた場合も同じです。 – James

+0

@Jamesレイジーローディングを再度有効にしましたか? – doctorless

+0

ちょうどそれを試してみました。私の編集を参照してください。 – James