エンティティフレームワークで追跡されたエンティティをクリアするにはどうすればよいですか


84

エンティティの大きな山を実行する修正コードを実行しています。速度が低下するにつれて、コンテキスト内の追跡されるエンティティの数が反復ごとに増加するためです。時間がかかる可能性があるため、最後に変更を保存しています。各反復の。各反復は独立しており、以前にロードされたエンティティを変更しません。

変更の追跡をオフにできることはわかっていますが、一括挿入コードではないため、オフにしたくありませんが、エンティティを読み込んでいくつかの計算を行い、数値が正しくない場合は、新しい数値を設定して更新/削除/作成しますいくつかの追加エンティティ。反復ごとに新しいDbContextを作成でき、おそらく同じインスタンスですべてを実行するよりも高速に実行できることはわかっていますが、もっと良い方法があるのではないかと考えています。

したがって、問題は次のとおりです。以前にdbコンテキストにロードされたエンティティをクリアする方法はありますか?


7
電話をかけるだけでcontext.Entry(entity).State = EntityState.Detached、その特定のエンティティの追跡が停止します。
ベンロビンソン

2
新しいコンテキストをインスタンス化してみませんか?非常に最適化されたコードが必要でない限り、大きなオーバーヘッドは実際にはありません。
エイドリアンナスイ2014

エンティティフレームワークは、変更されたエンティティに対してのみデータベースサーバーにアクセスします。これについては、パフォーマンスに関する懸念はありません。ただし、作業するテーブルのみで構成される新しいコンテキストを作成して、高速化することができます。
イスメットアルカン2014

1
@IsThatSo変更の検出には時間がかかるので、DbPerformanceについては心配していません。
hazimdikenli 2014

パフォーマンスのボトルネックを実際にデバッグして追跡しましたか、それとも単にこれを想定していますか?
イスメットアルカン2014

回答:


120

DbContextChangeTrackerを使用して、追加、変更、および削除されたすべてのエンティティをデタッチするメソッドを、または拡張メソッドに追加できます。

public void DetachAllEntities()
{
    var changedEntriesCopy = this.ChangeTracker.Entries()
        .Where(e => e.State == EntityState.Added ||
                    e.State == EntityState.Modified ||
                    e.State == EntityState.Deleted)
        .ToList();

    foreach (var entry in changedEntriesCopy)
        entry.State = EntityState.Detached;
}

5
「Where」の後に必ず「ToList」を呼び出してください。それ以外の場合は、System.InvalidOperationExceptionをスローします。 'コレクションが変更されました。列挙操作が実行されない場合があります。
mabead 2017

6
私の単体テストでは、エントリの状態は「変更されていません」です。これは、テストメソッドの最後にロールバックするトランザクションを使用しているためです。つまり、テストを一度に正しく実行するには、現在の状態を確認せずに、追跡されたエントリの状態を「切り離し」に設定する必要がありました。トランザクションをロールバックした直後に上記のコードを呼び出しましたが、ロールバックは確かに未変更の状態を意味します。
barbara.post 2017年

2
(また、実際のエンティティではなくエントリであるvar entity必要がありますvar entry
oatsoda 2018

2
@DavidSherretそうかもしれないと思った!私のテストアプリでは、1000個のアイテムを循環し、Detachedとしてマークするのに、既存のコードで約6000ミリ秒かかったため、これを見つけました。新しいもので約15ms :)
oatsoda 2018

3
e.State == EntityState.Unchangedも使用するべきではありませんか?エンティティは変更されていませんが、コンテキスト内で追跡され、DetectChanges中に考慮されるエンティティのセットの一部です。たとえば、新しいエンティティを追加し(状態は追加)、SaveChangesを呼び出すと、追加されたエンティティの状態はUnchangedになります(UnitOfWorkパターンに反しますが、opは尋ねました:各反復の終わりに変更を保存しています)。
jahav

28

1.可能性:エントリを切り離します

dbContext.Entry(entity).State = EntityState.Detached;

エントリをデタッチすると、変更トラッカーはエントリの追跡を停止します(パフォーマンスが向上するはずです)

参照:http//msdn.microsoft.com/de-de/library/system.data.entitystate(v = vs.110).aspx

2.可能性:独自のStatusフィールド+切断されたコンテキストで作業する

切断されたグラフを使用できるように、エンティティのステータスを個別に制御したい場合があります。エンティティステータスのプロパティを追加し、このステータスdbContext.Entry(entity).Stateを操作の実行時に変換します(これを行うにはリポジトリを使用します)

public class Foo
{
    public EntityStatus EntityStatus { get; set; }
}

public enum EntityStatus
{
    Unmodified,
    Modified,
    Added
}

例については、次のリンクを参照してください:https//www.safaribooksonline.com/library/view/programming-entity-framework/9781449331825/ch04s06.html


拡張メソッドを追加し、ChangeTracker内のすべてのエンティティを実行して、それらをデタッチすると機能するはずです。
hazimdikenli 2014

15

毎分値を更新するWindowsサービスを実行していますが、同じ問題が発生しました。@DavidSherretsソリューションを実行してみましたが、数時間後にはこれも遅くなりました。私の解決策は、新しい実行ごとにこのような新しいコンテキストを作成することでした。シンプルですが、機能します。

_dbContext = new DbContext();


5
これは「シンプルですが機能する」ソリューションではありません。それが唯一の正しいものです。コンテキストは可能な限り少なくする必要があります。1つのトランザクションごとに1つのコンテキストが最適なオプションです。
Yegor Androsov 2018

2
@pwrigshihanomoronimoに同意すると、コンテキストはUnitOfWorkデザインパターンに従います。Martin Fowlerによって定義されているように:>ビジネストランザクションの影響を受けるオブジェクトのリストを維持し、>変更の書き込みと同時実行性の問題の解決を調整します。
ミシェル

これは私にとってはうまくいくようでした。私はデータを同期しており、数百万行のテーブルに約1億のトランザクション(挿入と更新)があります。そのため、しばらくして(またはいくつかの操作で)OutOfMemoryExceptionに苦労しました。これは、コンテキストを再インスタンス化するための自然な場所であるループ数Xあたりの新しいDbContextを作成したときに解決されました。これにより、EFで発生する可能性のあるメモリリークや長時間実行される操作について考える必要がなく、GCがより適切にトリガーされた可能性があります。ありがとうございました!
マットマグネム

これが依存性注入で機能するかどうかわからない
SabrinaLeggett20年

@SabrinaLeggettコンテキストがどこから発生したかに関係なく機能するはずです
Ogglas 2010

4

私はちょうどこの問題に遭遇し、最終的には典型的な.NETCore依存性注入を使用している人のためのより良い解決策に出くわしました。操作ごとにスコープ付きDbContextを使用できます。これはリセットされるDbContext.ChangeTrackerため、SaveChangesAsync()過去の反復からエンティティをチェックするのに行き詰まることはありません。ASP.NET CoreControllerメソッドの例を次に示します。

    /// <summary>
    /// An endpoint that processes a batch of records.
    /// </summary>
    /// <param name="provider">The service provider to create scoped DbContexts.
    /// This is injected by DI per the FromServices attribute.</param>
    /// <param name="records">The batch of records.</param>
    public async Task<IActionResult> PostRecords(
        [FromServices] IServiceProvider provider,
        Record[] records)
    {
        // The service scope factory is used to create a scope per iteration
        var serviceScopeFactory =
            provider.GetRequiredService<IServiceScopeFactory>();

        foreach (var record in records)
        {
            // At the end of the using block, scope.Dispose() will be called,
            // release the DbContext so it can be disposed/reset
            using (var scope = serviceScopeFactory.CreateScope())
            {
                var context = scope.ServiceProvider.GetService<MainDbContext>();

                // Query and modify database records as needed

                await context.SaveChangesAsync();
            }
        }

        return Ok();
    }

ASP.NET Coreプロジェクトは通常DbContextPoolを使用するため、これによってDbContextオブジェクトが作成/破棄されることはありません。(あなたが興味を持っていた場合は、DbContextPoolが実際に呼び出すDbContext.ResetState()DbContext.Resurrect()が、彼らはおそらく、将来のリリースで変更されますように私は、あなたのコードから直接それらを呼び出すことをお勧めしません。) https://github.com/aspnet/EntityFrameworkCore/blob/v2 .2.1 / src / EFCore / Internal / DbContextPool.cs#L157


1

EF Core 3.0から、ChangeTrackerをリセットできる内部APIがあります。これを本番コードで使用しないでください。シナリオによってはテストに役立つ可能性があるため、言及します。

using Microsoft.EntityFrameworkCore.Internal;

_context.GetDependencies().StateManager.ResetState();

コードのコメントが言うように;

これは、Entity Framework Coreインフラストラクチャをサポートする内部APIであり、パブリックAPIと同じ互換性標準の対象ではありません。いかなるリリースにおいても、予告なしに変更または削除される場合があります。細心の注意を払ってコード内で直接使用する必要があります。そうすると、新しいEntity FrameworkCoreリリースに更新するときにアプリケーションが失敗する可能性があることを認識してください。



-2

私の意見では、私の経験では、EF、または任意のオームであることは、過度のプレッシャーや複雑なモデルではうまく機能しないということです。

追跡したくない場合は、本当にormを実行するのはなぜですか?

速度が主な力である場合、ストアドプロシージャと優れたインデックス作成に勝るものはありません。

さらに、クエリが常にIDごとである場合は、nosqlまたはおそらくkeyとjsonだけのsqlを使用することを検討してください。これにより、クラスとテーブル間のインピーダンスの問題を回避できます。

あなたのケースシナリオでは、そのようにオブジェクトに物をロードするのは私には非常に遅いようです。実際の場合、ネットワークを介したデータの転送を回避できるため、ストアドプロシージャの方が優れています。また、SQLははるかに高速で、集計などを管理するために最適化されています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.