web-dev-qa-db-ja.com

パフォーマンスを維持するために、EntityFrameworkのObjectSetで使用できるインクルードはいくつありますか?

プロフィールページに次のLINQクエリを使用しています。

var userData = from u in db.Users
                        .Include("UserSkills.Skill")
                        .Include("UserIdeas.IdeaThings")
                        .Include("UserInterests.Interest")
                        .Include("UserMessengers.Messenger")
                        .Include("UserFriends.User.UserSkills.Skill")
                        .Include("UserFriends1.User1.UserSkills.Skill")
                        .Include("UserFriends.User.UserIdeas")
                        .Include("UserFriends1.User1.UserIdeas")
                               where u.UserId == userId
                               select u;

長いオブジェクトグラフがあり、多くのインクルードを使用します。現在は完全に動作していますが、サイトに多くのユーザーがいる場合、パフォーマンスに大きな影響を与えますか?

他の方法でやるべきですか?

47
teenup

インクルードを含むクエリは単一の結果セットを返し、インクルードの数は、データベースサーバーからWebサーバーへのビッグデータセットの転送方法に影響します。例:

エンティティCustomer (Id, Name, Address)とエンティティOrder (Id, CustomerId, Date)があるとします。次に、注文を顧客に照会します。

var customer = context.Customers
                      .Include("Orders")
                      .SingleOrDefault(c => c.Id == 1);

結果のデータセットは次の構造になります。

 Id | Name | Address | OrderId | CustomerId | Date 
---------------------------------------------------
  1 |  A   |   XYZ   |    1    |     1      | 1.1.
  1 |  A   |   XYZ   |    2    |     1      | 2.1.

つまり、CutomersデータはOrderごとに繰り返されます。次に、例を別のエンティティで拡張しましょう-'OrderLine(Id、OrderId、ProductId、Quantity)andProduct(Id、Name) `。次に、注文、注文ライン、および製品を顧客に照会します。

var customer = context.Customers
                      .Include("Orders.OrderLines.Product")
                      .SingleOrDefault(c => c.Id == 1);

結果のデータセットは次の構造になります。

 Id | Name | Address | OrderId | CustomerId | Date | OrderLineId | LOrderId | LProductId | Quantity | ProductId | ProductName
------------------------------------------------------------------------------------------------------------------------------
  1 |  A   |   XYZ   |    1    |     1      | 1.1. |     1       |    1     |     1      |    5     |    1      |     AA
  1 |  A   |   XYZ   |    1    |     1      | 1.1. |     2       |    1     |     2      |    2     |    2      |     BB
  1 |  A   |   XYZ   |    2    |     1      | 2.1. |     3       |    2     |     1      |    4     |    1      |     AA
  1 |  A   |   XYZ   |    2    |     1      | 2.1. |     4       |    2     |     3      |    6     |    3      |     CC

ご覧のとおり、データはかなり多く複製されています。一般的に、参照ナビゲーションプロパティへの各インクルード(例ではProduct)は新しい列を追加し、コレクションナビゲーションプロパティへの各インクルード(例ではOrdersおよびOrderLines)は新しい列を追加し、含まれているコレクションの各行に作成済みの行を複製します。

これは、あなたの例が何百もの列と何千もの行を簡単に持つ可能性があることを意味します。正しいアプローチは、パフォーマンステストを作成することです。結果が期待を満たさない場合は、クエリを変更し、独自のクエリまたはLoadPropertyメソッドによってナビゲーションプロパティを個別に読み込むことができます。

個別のクエリの例:

var customer = context.Customers
                      .Include("Orders")
                      .SingleOrDefault(c => c.Id == 1);
var orderLines = context.OrderLines
                        .Include("Product")
                        .Where(l => l.Order.Customer.Id == 1)
                        .ToList();

LoadPropertyの例:

var customer = context.Customers
                      .SingleOrDefault(c => c.Id == 1);
context.LoadProperty(customer, c => c.Orders);

また、常に本当に必要なデータのみをロードする必要があります。

編集:作成したばかり Data UserVoiceに関する提案 イーガーロードされたデータが追加の結果で渡される追加のイーガーロード戦略をサポートするセット(同じデータベースラウンドトリップ内の個別のクエリによって作成されます)。この改善がおもしろいと思ったら、提案に投票することを忘れないでください。

84
Ladislav Mrnka

多くのインクルードのパフォーマンスを向上させることができます2つ以上の小さなデータリクエストを作成することにより以下のようなデータベースから。

私の経験によると、以下のようにクエリごとに最大2つ含まれるしか与えられません。それ以上になると、パフォーマンスが非常に悪くなります。

var userData = from u in db.Users
                        .Include("UserSkills.Skill")
                        .Include("UserIdeas.IdeaThings")
                        .FirstOrDefault();

 userData = from u in db.Users
                    .Include("UserFriends.User.UserSkills.Skill")
                    .Include("UserFriends1.User1.UserSkills.Skill")
                    .FirstOrDefault();

上記は、データベースへの移動を増やすことにより、データベースから小さなデータセットをもたらします。

私自身の経験を生かして、この上にブログ記事を書きました。 ここ

これがお役に立てば幸いです。

15
Sampath

はい、そうなります。マスターテーブル行の複数の詳細行を展開する場合は、インクルードを使用しないでください。

EFはクエリを複数のクエリではなく1つの大きな結合に変換すると思います。したがって、詳細テーブルのすべての行にマスターテーブルデータを複製することになります。

例:マスター->詳細。たとえば、マスターには100行、詳細には5000行(マスターごとに50行)とします。

詳細を遅延ロードすると、100行(サイズ:マスター)+ 5000行(サイズ:詳細)が返されます。

.Include( "Details")を使用すると、5000行(サイズ:マスター+詳細)が返されます。基本的に、マスター部分は50回以上複製されます。

複数のテーブルを含めると、上方向に増加します。

EFによって生成されたSQLを確認します。

8
Stephen Chung

負荷テストを実行し、ストレス下でのサイトのパフォーマンスを測定することをお勧めします。各リクエストで複雑なクエリを実行している場合は、いくつかの結果をキャッシュすることを検討してください。

3
Darin Dimitrov

includeの結果は変わる可能性があります:includeメソッドを呼び出すエンティティによって異なります。

Ladislav Mrnkaから提案された例のように、エンティティがあるとします。

顧客(ID、名前、住所)

このテーブルへのマップ:

Id  |  Name   | Address
-----------------------
C1  |  Paul   |   XYZ   

エンティティOrder(Id、CustomerId、Total)

このテーブルへのマップ:

Id |  CustomerId  | Total
-----------------------
O1 |      C1      |  10.00
O2 |      C1      |  13.00

関係は1人の顧客から多くの注文


サンプル1:顧客=>注文

var customer = context.Customers
                      .Include("Orders")
                      .SingleOrDefault(c => c.Id == "C1");

Linqは非常に複雑なSQLクエリで変換されます。

この場合、クエリは2つのレコードを生成し、顧客に関する情報が複製されます。

 Customer.Id   |   Customer.Name |    Order.Id |  Order.Total
-----------------------------------------------------------
     C1        |       Paul      |       O1    |    10.00     
     C1        |       Paul      |       O2    |    13.00   

サンプル2:注文=>顧客

var order = context.Orders
                      .Include("Customers")
                      .SingleOrDefault(c => c.Id == "O1");

Linqは単純なSQL結合に変換されます。

この場合、クエリは1つのレコードのみを生成し、情報の重複はありません。

 Order.Id |  Order.Total |  Customer.Id   |   Customer.Name
-----------------------------------------------------------
     O1   |    10.00     |      C1        |       Paul    
2
Marco Staffoli