web-dev-qa-db-ja.com

DbContextスレッドセーフですか?

DbContextクラスがスレッドセーフかどうか疑問に思っていましたが、アプリケーションでDbContextにアクセスする並列スレッドを実行しており、ロック例外のホストを取得しているため、そうではないと想定しています。その他、スレッドに関連しているように見えるもの。

最近までエラーは発生していませんでしたが、最近までスレッドのDbContextにアクセスしていませんでした。

私が正しければ、人々は解決策として何を提案しますか?

53
jcvandan

スレッドセーフではありません。スレッドでDbContextの新しいインスタンスを作成するだけです。

59
DanielB

いいえ、スレッドセーフではありません。EFコンテキストは決して共有されるべきではないため、EF全体はスレッドセーフではありません。

27
Ladislav Mrnka

編集済み-以下の古い回答。

現在、私は常にこのパターンをDbContextで使用しています。

using(var db = new LogDbContext())
{
    // Perform work then get rid of the thing
}

リクエストスレッドごとに1つという私のアプローチでは、他のDbContextインスタンスがその背後にある実際のデータベースに新しい値を書き込んでいる間にも、DbContextのキャッシュされたオブジェクトが残って古くなってしまいます。これにより、たとえば、挿入を実行する1つの要求と、そのクエリのデータのキャッシュされた古いリストを持つ別のスレッドに着信するリストの次の要求など、奇妙な問題が発生します。

以下を機能させ、多数の読み取り/少数の書き込みスタイルのアプリのパフォーマンスを向上させるアプローチもありますが、上記のはるかに単純なパターンよりも多くの設計と戦略が必要です。

更新

また、呼び出しのログなど、ライブラリメソッドに便利なヘルパーメソッドを使用します。ヘルパーメソッドは次のとおりです。

    public static async Task Using(Db db, Func<Db, Task> action)
    {
        if (db == null)
        {
            using (db = new Db())
            {
                await action(db);
            }
        }
        else
        {
            await action(db);
        }
    }

これにより、呼び出される方法に応じて、オプションの既存のDbContextを使用するコード、または使用中のコンテキスト内でインスタンス化するコードを簡単に作成できます。

たとえば、DbContextで作業しているときに、データを読み込んで情報をログに記録し、そのデータを保存する場合があります。パフォーマンスの観点から、同じDbContextですべてを実行するのが最善です。一方、単純なアクションに応じて何かを記録し、他のデータをロードしたり書き込んだりすることもできます。上記の方法を活用することで、既存のDbContext内で作業するかどうかに関係なく機能する1つのロギングメソッドを作成できます。

public async Task WriteLine(string line, Db _db = null)
{
    await Db.Using(_db, db => {
        db.LogLines.Add(new LogLine(line));
        await db.SaveChangesAsync();
    });
}

現在、このメソッド呼び出しは、既存のDbContextの内部または外部で呼び出すことができ、この2つのバージョンと他のすべての便利なロギングメソッドまたは他のユーティリティメソッドを持っているのではなく、正しい方法で動作し、彼らまたは彼らの発信者にこれまでに行われるすべての呼び出しのコンテキストを計画します。これは基本的に、心配する必要のあるユーティリティ呼び出しでデータベースが正確に開かれたときに心配する必要がない、以下のスレッド静的戦略の利点の1つを私に返します。

古い答え

私は通常、EF DbContextでスレッドセーフを次のように処理します。

public class LogDbContext : DbContext
{
    . . .

    [ThreadStatic]
    protected static LogDbContext current;

    public static LogDbContext Current()
    {
        if (current == null)
            current = new LogDbContext();

        return current;
    }

    . . .
}

これを設定すると、このスレッドのDbContextを次のように取得できます。

var db = LogDbContext.Current();

各DbContextが独自のローカルキャッシュを保持しているため、各スレッドはエンティティオブジェクトの独自の個別のキャッシュを持っていることに注意することが重要です。ただし、新しいDbContextオブジェクトの作成は高価になる可能性があり、このアプローチはそのコストを最小限に抑えます。

12
Chris Moschini