web-dev-qa-db-ja.com

Entity Framework、DBContext、using()+ async?

Entity Frameworkについて長い間私を悩ませてきたことがあります。

昨年、EFを使用するクライアント向けに大きなアプリケーションを作成しました。そして開発中、すべてがうまくいきました。

8月にシステムを出荷しました。しかし、数週間後、本番サーバーで奇妙なメモリリークが発生し始めました。私のASP.NET MVC 4プロセスは、数日実行した後、マシンのすべてのリソースを消費していました(8 GB)。これは良くありませんでした。私はネットを検索しましたが、コンテキストを破棄できるように、すべてのEFクエリと操作をusing()ブロックで囲む必要があることがわかりました。

ある日、私はすべてのコードをusing()を使用するようにリファクタリングしました。これにより問題が解決しました。それ以来、プロセスは安定したメモリ使用量にとどまっています。

最初にクエリを囲まなかった理由は、Visual Studioに含まれているMicrosoft独自のスキャフォールドから最初のコントローラーとリポジトリを開始したためです。これらは、クエリを使用して囲みませんでした。代わりに、インスタンス変数としてDbContextがありました。コントローラ自体の。

まず第一に:コンテキストを破棄することが本当に重要である場合(奇妙ではないもの、dbconnectionを閉じる必要があるなど)、Microsoftはすべての例でこれを使用する必要があります!

今、私はすべての学習を頭の後ろに置いて新しい大きなプロジェクトに取り組み始め、.NET 4.5とEF 6の新機能asyncawaitを試しています。 EF 6.0には、これらすべての非同期メソッドがあります(SaveChangesAsyncToListAsyncなど)。

_public Task<tblLanguage> Post(tblLanguage language)
{
    using (var langRepo = new TblLanguageRepository(new Entities()))
    {
        return langRepo.Add(RequestOrganizationTypeEnum, language);
    }
}
_

クラスTblLanguageRepo内:

_public async Task<tblLanguage> Add(OrganizationTypeEnum requestOrganizationTypeEnum, tblLanguage language)
{
    ...
    await Context.SaveChangesAsync();
    return langaugeDb;
}
_

ただし、ステートメントをusing()ブロックで囲むと、クエリが戻る前に、例外_DbContext was disposed_が発生します。これは予想される動作です。クエリは非同期で実行され、usingブロックはクエリの前に完了します。しかし、ef 6のasync関数とawait関数を使用しているときに、コンテキストを適切に破棄するにはどうすればよいですか?

正しい方向に向けてください。

using()はEF 6で必要ですか?マイクロソフト独自の例がなぜこれを取り上げないのですか?非同期機能をどのように使用し、コンテキストを適切に破棄しますか?

25
Objective Coder

あなたのコード:

public Task<tblLanguage> Post(tblLanguage language)
{
    using (var langRepo = new TblLanguageRepository(new Entities()))
    {
        return langRepo.Add(RequestOrganizationTypeEnum, language);
    }
}

は、Taskを返す前にリポジトリを破棄しています。コードを作成した場合async

public async Task<tblLanguage> Post(tblLanguage language)
{
    using (var langRepo = new TblLanguageRepository(new Entities()))
    {
        return await langRepo.Add(RequestOrganizationTypeEnum, language);
    }
}

次に、Taskが完了する直前にリポジトリを破棄します。実際に発生するのは、awaitを押すと、メソッドは不完全なTaskを返します(この時点では、usingブロックはまだ「アクティブ」です)。次に、langRepo.Addタスクが完了すると、Postメソッドが実行を再開し、langRepoを破棄します。 Postメソッドが完了すると、返されたTaskが完了します。

詳細については、my async intro を参照してください。

24
Stephen Cleary

私は「リクエストごとに1つのDbContext」を選択し、リクエスト内でDbContextを再利用します。とにかく、すべてのタスクは要求の最後に完了する必要があるため、安全に再度処分できます。

次を参照してください: ASP.NET MVCのリクエストごとに1つのDbContext(IOCコンテナーなし)

他のいくつかの利点:

  • 一部のエンティティは、以前のクエリのDbContextで既にマテリアライズされている可能性があり、追加のクエリをいくつか保存しています。
  • あなたのコードを乱雑にするそれらの余分なusingステートメントがすべてあるわけではありません。
5
Dirk Boer

I @ Dirk Boerと同意する DbContextの有効期間を管理する最善の方法は、httpリクエストが完了するとコンテキストを破棄するIoCコンテナを使用することです。ただし、それが選択肢でない場合は、次のようなこともできます。

var dbContext = new MyDbContext();
var results = await dbContext.Set<MyEntity>.ToArrayAsync();
dbContext.Dispose();

usingステートメントは、コードブロックの最後にあるオブジェクトを破棄するための構文上の糖衣です。 .Disposeを自分で呼び出すだけで、usingブロックなしで同じ効果を得ることができます。

考えてみてください。usingブロック内でawaitキーワードを使用する場合、オブジェクト破棄例外を取得しないでください。

public async Task<tblLanguage> Post(tblLanguage language)
{
    using (var langRepo = new TblLanguageRepository(new Entities()))
    {
        var returnValue = langRepo.Add(RequestOrganizationTypeEnum, language);
        await langRepo.SaveChangesAsync();
        return returnValue;
    }
}
1
danludwig

適切なn層のプログラミングパターンを使用している場合、コントローラーは、データベース要求が行われていることさえ知らないはずです。これはすべて、サービス層で発生するはずです。

これを行うにはいくつかの方法があります。 1つは、クラスごとに2つのコンストラクターを作成することです。1つはコンテキストを作成し、もう1つは既存のコンテキストを受け入れます。このようにして、すでにサービスレイヤーにいる場合はコンテキストを渡したり、サービスレイヤーを呼び出すコントローラー/モデルの場合は新しいコンテキストを作成したりできます。

もう1つは、各メソッドの内部オーバーロードを作成し、そこでコンテキストを受け入れることです。

しかし、はい、これらを使用中にラップする必要があります。

理論的には、ガベージコレクションはこれらをラップせずにクリーンアップする必要があります(SHOULD)が、GCを完全に信頼しているわけではありません。

1
Scottie

メソッドの同期を維持したいが、非同期でDBに保存したい場合は、usingステートメントを使用しないでください。 @danludwigが言ったように、それは単なる構文上の砂糖です。 SaveChangesAsync()メソッドを呼び出し、タスクの完了後にコンテキストを破棄できます。これを行う1つの方法は次のとおりです。

//Save asynchronously then dispose the context after
context.SaveChangesAsync().ContinueWith(c => context.Dispose());

ContinueWith()に渡したラムダも非同期で実行されることに注意してください。

1
Ross Brigoli

私見、これも遅延読み込みの使用が原因の問題です。コンテキストを破棄すると、データベースサーバーへの基本的な接続が閉じられるため、プロパティをレイジーロードできなくなります。

遅延読み込みを有効にしていて、usingスコープの後に例外が発生する場合は、 https:/を参照してください。 /stackoverflow.com/a/21406579/870604

0
ken2k