18

EntityFrameworkで興味深いパフォーマンスの問題が発生しています。私はCodeFirstを使用しています。

これが私のエンティティの構造です:

ブックには多くのレビューを含めることができます。レビューは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件のレビューがある1冊の本を取得しようとしています。

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

この特定の本には10,000件のレビューがあります。このクエリのパフォーマンスは約4秒です。(SQLプロファイラーを介して)まったく同じクエリを実行すると、実際にはすぐに返されます。同じクエリとSqlDataAdapterおよびカスタムオブジェクトを使用してデータを取得しましたが、500ミリ秒未満で発生します。

ANTS Performance Profilerを使用すると、時間の大部分がいくつかの異なることを行うために費やされているように見えます。

Equalsメソッドは5000万回呼び出されています。

なぜこれを5000万回呼び出す必要があるのか​​、そしてどうすればこれのパフォーマンスを向上させることができるのか誰かが知っていますか?

4

2 に答える 2

20

Equalsが5000万回呼び出されるのはなぜですか?

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

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

つまり、新しいレコードはそれぞれ、IDマップ内の既存のすべてのレコードと比較されます。すべての比較の合計を計算するために数学を適用することにより、「等差数列」と呼ばれるものを使用できます。

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

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

変更の追跡をオフにしてみてください=できればIDマップのチェックをオフにしてください。

注意が必要な場合があります。皮切りに:

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

とにかく、どのオブジェクトタイプがEqualsに渡されるかを確認できますか?主キーのみを比較する必要があり、50Mの整数比較でさえそのような問題にはならないはずです。

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

于 2011-09-13T16:04:05.483 に答える
1

私はこれがつまらないように聞こえることを知っていますが、あなたは逆の方法を試しましたか、例えば:

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

この方法でクエリにアプローチすると、EFのパフォーマンスが向上することがあります(ただし、その理由を理解する時間がありませんでした)。

于 2011-09-13T14:52:54.867 に答える