2017-08-24 10 views
3

私は、デスクトップアプリケーションでエンティティフレームワークを使用してmysqlデータベースにアクセスしています。データベースは同じシステムにインストールされていますが、今ではそれを使用しているのは私だけです。DBSet.Findが遅くなり、遅くなるのはなぜですか?

特定のコレクションdocsのオブジェクトがデータベースにすでに存在するかどうかをチェックするメソッドがあります。データベースに追加されていない場合はDBContextのsaveメソッドが最後に実行されます。メソッドは私のプログラムのループで何度も実行されます。

検索するオブジェクトの数がかなり一定(毎回約500回)であっても、このメソッドを実行するたびに検索速度が遅くなり、遅くなることに気付きました。理由は何でしょうか?

コードは、多かれ少なかれ、次のようになります。

TimeSpan timeToFind = new TimeSpan(); 

foreach (var docFromResult in docs) 
{ 
    DateTime operationStart = DateTime.Now; 
    var existingDocument = 
     db.VaStDocuments.Find(docFromResult.Id, docFromResult.OwnerId, docFromResult.Year); 
    timeToFind += DateTime.Now - operationStart; 
    if (existingDocument != null && docFromResult.Hash.Equals(existingDocument.Hash)) 
    { 
     continue; 
    } 
    if (existingDocument == null) 
    { 
     db.VaStDocuments.Add(docFromResult); 
    } 
    else if (!docFromResult.Hash.Equals(existingDocument.Hash)) 
    { 
     existingDocument.Hash = docFromResult.Hash; 
     existingDocument.IsNew = true; 
     existingDocument.Text = null; 
    } 
    docsForNotification.Add(docFromResult); 
} 
if (docsForNotification.Any()) 
{ 
    db.SaveChangesDisplayValidationErrors(); 
} 

だから、文書はとても基本的に、ここでの唯一のデータベース・アクティビティがFindメソッドでデータベースに記載されていないということは非常にまれなケースです。
timetoFind一般に、は、このメソッドの実行ごとに増加します。おそらく20〜30回のループ後に120秒に達する0.5-2秒で始まる。

+3

トラッキングされたエンティティのリストが増えているので、それを避けることができます: 'db.VaStDocuments.AsNoTracking()。Find(...)' – DavidG

+0

@DavidG - 副作用はありますか? – Greg

+0

このコードではパフォーマンスの向上以外は何もありません。これらのエンティティを他の場所で使用していた場合、少し苦しむかもしれませんが、私はそれを非常に疑っています。 – DavidG

答えて

1

DbContextは、取得したアイテムとこれらの取得したアイテムの変更を追跡します。

これは、アクションごとにデータベースと通信することなく、これらのアイテムを追加/更新/削除できるようにするためです。 SaveChangesが呼び出された場合のみ、変更はデータベースと通信されます。

これにより、これらの変更がデータベースにコミットされる前に、新しく追加または変更されたオブジェクトを使用することができます。

1対多の関係船を考える顧客 - 注文:顧客はゼロ以上の注文を持ち、すべての注文は厳密に1つの顧客に属します。

今すぐ最初に新しい顧客を紹介することができます。

using (var dbContext = new MyDbContext(...)) 
{ // Introduce a Customer: 
    Customer customerToAdd = GetCustomerData(); 
    var addedCustomer = dbContext.Customers.Add(customerToAdd); 

    // Introduce an order for this Customer 
    Order addedOrder = dbContext.Orders.Add(new Order() 
    { 
     // addedCustomer has no Id yet, we can't use addedCustomer.Id 
     Order.Customer = addedCustomer; 
     ... 
    }); 
    dbContext.SaveChanges(); 
} 

DbContextを追加し、顧客のトラックを保持するので、あなたが変更が保存される前に、それを使用することができます:お客様がまだIDを持っていないしながら、その後、あなたは、注文を紹介することができます。

また、お客様第一を追加することなく、秩序を追加することができます:

var notAddedCustomer = new Customer() {...} 
var order1= dbContext.Orders.Add(new Order() 
{ 
    // this order belongs to a new customer that has not been added yet: 
    Customer = notAddedCustomer 
} 

dbContextは、顧客がまだ追加されていないことを検出し、それ自体を追加します。同じnotAddedCustomerの別の注文を作成する場合、dbContextは最初の注文の導入中にこの顧客が追加されたことを検出できなければなりません。

また、Customerを削除して新しいOrderを提供しようとすると、dbContextはこのCustomerが削除されたこと、および新しいOrderを追加できないことを検出する必要があります。同様に、いくつかのOrdersを追加した後にCustomerを削除すると、dbContextはこれを検出するはずです。

これは、dbContextが変更を追跡する必要がある理由です。あなたが変更されたオブジェクトをたくさん持っている場合、これは、この使用法の速度を落とします。 dbContextのほとんどのユーザーは、わずかな変更だけでかなり短い時間しか生きられません。

SaveChangesを実行する前に多くの変更を行う必要があり、実際のSaveChangesを実行する前にそれらを検出する必要がないことが確かな場合は、Queryable.AsNoTrackingを使用してトラッキングをオフにすることができます。これにより、クエリの速度が飛躍的に向上し、アイテムが追加されます。欠点は、関連オブジェクトを使用するときにオブジェクトの代わりにIDを使用する必要があることです。

+0

あなたの答えに感謝します。私は2つのことに気がつきました。たとえ "SaveChanges"をよく呼び出すとしても、AsNoTrackingを使用しないとパフォーマンスはまだまだ遅いです。他のOwnerエンティティを参照しているので、私はdocFromResultを保存してAsNoTrackingを使用します。オーナーAsNoTrackingも取得すると、保存時にエラーが発生します。所有者はすでにdbにあり、追加する必要はありません。多分私はそれのために別の問題を作るべきです。 – Greg

+1

SaveChangesは、追跡されたアイテムを削除しません。利点:同じアイテムをもう一度尋ねれば、データベースからクエリを受ける必要はありません。使用を終了してdbContextを破棄し、新しいDbContextオブジェクトを再作成するとタイミングがどうなるかを確認してください。 –

+0

は、各ループでdbContextを再初期化/廃棄することで、40-50倍のように見えます。いくつかの同期で少しでも苦労しましたが、すべてを追跡するよりもはるかに少ない労力です。 – Greg

関連する問題