18

Entity Frameworkで興味深いパフォーマンス問題が発生しています。私はコードファーストを使用しています。Entity Frameworkパフォーマンスの問題

私のエンティティの構造は次のとおりです。

本は多くのレビューを持つことができます。 レビューは1つのブックに関連付けられています。 レビューには、1つまたは複数のコメントを付けることができます。 コメントは1つのレビューに関連付けられています。

public class Book 
{ 
    public int BookId { get; set; } 
    // ... 
    public ICollection<Review> Reviews { get; set; } 
} 

public class Review 
{ 
    public int ReviewId { get; set; } 
    public int BookId { get; set; } 
    public Book Book { get; set; } 
    public ICollection<Comment> Comments { get; set; } 
} 

public class Comment 
{ 
    public int CommentId { get; set; } 
    public int ReviewId { get; set; } 
    public Review Review { get; set; } 
} 

多くのデータがデータベースに格納され、適切なインデックスが追加されました。私は、このクエリを使用してそれに関する10,000のレビューを持っている単一の本を取得しようとしています:

var bookAndReviews = db.Books.Where(b => b.BookId == id) 
         .Include(b => b.Reviews) 
         .FirstOrDefault(); 

この本は10,000件のレビューがあります。このクエリのパフォーマンスは約4秒です。まったく同じクエリを(SQLプロファイラを介して)実行すると、実際にはまったく時間がかかりません。私は同じクエリとSqlDataAdapterとカスタムオブジェクトを使用してデータを取得し、500ミリ秒以下で発生します。

Equalsメソッドが50万回と呼ばれている:時間の大部分は、いくつかの異なるものをやって費やされているようにANTSパフォーマンスプロファイラの使用

はそれが見えます。

誰もがこの50万回を呼び出す必要がある理由を知っていますか、これに対してどのようにパフォーマンスを向上させることができますか?

+0

実際には、どのクエリが生成されているか、または最適なクエリであると想定していますか。 –

+1

EF Profilerを試してみてください。 –

+1

問題は私が述べたようなものではありません。私は、EFが生成している正確なクエリを使用して、通常のADO.netを使用してSQLデータアダプタで使用し、同じオブジェクトを手動でロードしました。それは1秒未満で実行されます。 – Dismissile

答えて

20

なぜ等価は50M回呼び出されますか?

かなり疑わしいですね。 Equalsへの10.000件のレビューと50.000.000件の通話があります。これはEFによって内部的に実装されたアイデンティティ・マップが原因であるとします。アイデンティティマップは、一意のキーを持つ各エンティティがコンテキストによって1回だけ追跡されるようにします。したがって、コンテキストがすでにデータベースからロードされたレコードと同じキーを持つインスタンスを持つ場合、新しいインスタンスはマテリアライズされず、代わりに既存のインスタンスが使用されます。これはどのようにそれらの数字と一致することができますか?私の恐ろしい推測:

============================================= 
1st  record read | 0  comparisons 
2nd  record read | 1  comparison 
3rd  record read | 2  comparisons 
... 
10.000th record read | 9.999 comparisons 

それぞれの新しいレコードがアイデンティティ・マップ内のすべての既存のレコードと比較されることを意味します。私たちは「算術シーケンス」と呼ばれるものを使用することができ、すべての比較の合計を計算するために数学を適用することにより:

a(n) = a(n-1) + 1 
Sum(n) = (n/2) * (a(1) + a(n)) 
Sum(10.000) = 5.000 * (0 + 9.999) => 5.000 * 10.000 = 50.000.000 

を私は仮定や計算にミスをしなかった願っています。待つ!これがうまく見えないので、私は間違いを犯したと思う。

変更トラッキングを無効にして、身分証明書の確認を無効にしてみてください。

これは扱いにくいことがあります。で開始:

var bookAndReviews = db.Books.Where(b => b.BookId == id) 
          .Include(b => b.Reviews) 
          .AsNoTracking() 
          .FirstOrDefault(); 

しかし、(それは変更の追跡によって処理されるため)あなたのナビゲーションプロパティが読み込まれないことを大きなチャンスがあります。このような場合は、次の方法を使用してください:

var book = db.Books.Where(b => b.BookId == id).AsNoTracking().FirstOrDefault(); 
book.Reviews = db.Reviews.Where(r => r.BookId == id).AsNoTracking().ToList(); 

とにかくどのオブジェクトタイプが等しいかどうかを確認できますか?私はそれがプライマリキーだけを比較すべきだと思うし、50M整数比較さえもそのような問題ではないはずです。

EFは遅いですが、それはよく知られています。エンティティを実体化するときに内部でリフレクションも使用するため、単純に10.000レコードには「ある程度の時間がかかる」ことがあります。すでに行っていない限り、動的プロキシの作成をオフにすることもできます(db.Configuration.ProxyCreationEnabled)。

+0

すばらしい分析!私がしばらく前に作ったテストでは、AsNoTrackingはマテリアライゼーションの時間を50%に短縮しました。追跡されているように読み込まれたエンティティのスナップショット作成は、アイデンティティマップで「Equals」を呼び出すよりも高価ですが、私は想像することができます。同じコンテキストで同じクエリを2回目(同じように)呼び出すと、速く(最初の呼び出しの1/10未満)、トラッキングなしでロードするよりもはるかに高速に戻ります。これは 'Equals'チェックアイデンティティマップでは比較的安いです。 – Slauma

+0

BTW: 'Include'は' AsNoTracking() 'でも動作し、ナビゲーションコレクションにデータが取り込まれます。 (逆のナビゲーション・プロパティー 'Review.Book'に値が設定されないことを意味しましたか?) – Slauma

1

例えば、私はこれはラメ音を知っていますが、他の方法で回避を試みている:あなたはこのようクエリに近づくと、私はEFから時々、より良いパフォーマンスを気づいた(私は持っていなかった

var reviewsAndBooks = db.Reviews.Where(r => r.Book.BookId == id) 
         .Include(r => r.Book); 

理由を理解する時間)。

+0

問題のためこれを個人的に避けるデッドロックを伴う – Skarsnik

関連する問題