NOLOCKを使用したエンティティフレームワーク


138

NOLOCKEntity Frameworkで関数を使用するにはどうすればよいですか?これを行う唯一の方法はXMLですか?

回答:


207

いいえ。ただし、トランザクションを開始し、分離レベルをコミットされていない読み取りに設定できます。これは基本的にNOLOCKと同じですが、テーブルごとに行うのではなく、トランザクションのスコープ内のすべてに対して行います。

それがあなたの望むように聞こえるなら、これはあなたがそれをやり始める方法です...

//declare the transaction options
var transactionOptions = new System.Transactions.TransactionOptions();
//set it to read uncommited
transactionOptions.IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted;
//create the transaction scope, passing our options in
using (var transactionScope = new System.Transactions.TransactionScope(
    System.Transactions.TransactionScopeOption.Required, 
    transactionOptions)
)

//declare our context
using (var context = new MyEntityConnection())
{
    //any reads we do here will also read uncomitted data
    //...
    //...
    //don't forget to complete the transaction scope
    transactionScope.Complete();
}

優れた@DoctaJonezこのためにEF4で新しく導入されたものはありますか?
FMFF 2012

@FMFF EF4に新しいものが導入されたかどうかはわかりません。上記のコードはEFv1以降でも動作することは知っています。
ドクタージョーンズ

結果はどうなりますか?上記のブロックでtransactionScope.Complete()を省略した場合 これについて別の質問を提出する必要があると思いますか?
Eakan Gopalakrishnan

@EakanGopalakrishnanこのメソッドの呼び出しに失敗すると、トランザクションマネージャがこれをシステム障害またはトランザクションのスコープ内でスローされた例外として解釈するため、トランザクションが中止されます。(MSDNのからの引用msdn.microsoft.com/en-us/library/...
ドクター・ジョーンズ

1
@JsonStathamこれは、このプルリクエストに追加されました。これは、マイルストーン2.1.0のためです
Doctor Jones

83

拡張メソッドはこれを簡単にすることができます

public static List<T> ToListReadUncommitted<T>(this IQueryable<T> query)
{
    using (var scope = new TransactionScope(
        TransactionScopeOption.Required, 
        new TransactionOptions() { 
            IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted }))
    {
        List<T> toReturn = query.ToList();
        scope.Complete();
        return toReturn;
    }
}

public static int CountReadUncommitted<T>(this IQueryable<T> query)
{
    using (var scope = new TransactionScope(
        TransactionScopeOption.Required, 
        new TransactionOptions() { 
            IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted }))
    {
        int toReturn = query.Count();
        scope.Complete();
        return toReturn;
    }
}

これをプロジェクトで使用すると、接続プールが完全に利用され、例外が発生します。理由がわかりません。他にこの問題がある人はいますか?助言がありますか?
Ben Tidman、2014年

1
問題はありません、ベン、接続コンテキストを常に破棄することを忘れないでください。
アレクサンドル

問題を絞り込み、トランザクションのスコープを考えられる原因から除外できました。ありがとう。コンストラクターにあった接続の再試行に関することと関係がありました。
Ben Tidman、2014年

私は、スコープがTransactionScopeOption.Suppressあるべきと考えている
CodeGrue

@Alexandre別のReadCommittedトランザクション内でこれを行うとどうなりますか?たとえば、データの保存を開始するためにトランザクションを生成しましたが、今はより多くのデータをクエリしているため、ReadUncommittedトランザクションを生成していますか?これを「完了」と呼んでも外部トランザクションは完了しますか?親切にアドバイスしてください:)
Jason Loki Smith

27

何か一般的なものが必要な場合、毎回実際にトランザクションスコープを開始するよりも煩わしくないことがわかった最良の方法は、次の簡単なコマンドを実行してオブジェクトコンテキストを作成した後で、接続にデフォルトのトランザクション分離レベルを設定することです。

this.context.ExecuteStoreCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;");

http://msdn.microsoft.com/en-us/library/aa259216(v=sql.80).aspx

この手法により、コンテキストを作成し、すべてのコンテキストに対して毎回このコマンドを実際に実行する単純なEFプロバイダーを作成できるため、デフォルトでは常に「コミットされていない読み取り」になります。


2
トランザクション分離レベルのみを設定しても効果はありません。実際にトランザクションを実行するには、トランザクション内で実行する必要があります。READ UNCOMMITTED状態のMSDNドキュメントTransactions running at the READ UNCOMMITTED level do not issue shared locks。これは、利益を得るためにトランザクション内で実行している必要があることを意味します。(msdn.microsoft.com/en-gb/library/ms173763.aspxから取得)。あなたのアプローチはそれほど邪魔にならないかもしれませんが、トランザクションを使用しないと何も達成しません。
ジョーンズ博士

3
MSDNのドキュメントには、「SQL Serverへの接続によって発行されたTransact-SQLステートメントのロックと行のバージョン管理の動作を制御する」と記載されています。および「ステートメントが他のトランザクションによって変更されたがまだコミットされていない行を読み取ることができることを指定します。」私が書いたこのステートメントは、トランザクション内かどうかに関係なく、すべてのSQLステートメントに影響します。私はオンラインで人と矛盾するのは好きではありませんが、大規模な実稼働環境でのこの声明の使用に基づくと、あなたはそれを明らかに間違っています。物事を想定しないで、それらを試してください!
Frank.Germain 2013年

私はそれらを試しましたが、これらのトランザクションスコープの1つ(および一致するトランザクション)内でクエリを実行しないとデッドロックが発生する高負荷環境があります。私の観察はSQL 2005サーバーで行われたので、動作が変更されたかどうかはわかりません。したがって、これをお勧めします。コミットされていない読み取りの分離レベルを指定してもデッドロックが発生し続ける場合は、クエリをトランザクション内に配置してみてください。トランザクションを作成せずにデッドロックが発生しない場合は、十分に公平です。
ドクタージョーンズ

3
@DoctorJones-Microsoft SQL Serverに関しては、すべてのクエリは本質的にトランザクションです。明示的なトランザクションを指定することは、2つ以上のステートメントを同じトランザクションにグループ化して、それらがアトミックな作業単位と見なせるようにするための単なる手段です。SET TRANSACTION ISOLATION LEVEL...コマンドは、接続レベルのプロパティに影響を及ぼし、したがってクエリヒントによって上書きされない限り、前方に(即ち接続)、その時点から作られたすべてのSQL文に影響を与えます。この動作は、少なくともSQL Server 2000以降、およびおそらく以前から存在しています。
ソロモンルッツキー2014年

5
@DoctorJones-チェックアウト:msdn.microsoft.com/en-us/library/ms173763.aspx。これがテストです。SSMSでクエリを開き(#1)、次を実行しますCREATE TABLE ##Test(Col1 INT); BEGIN TRAN; SELECT * FROM ##Test WITH (TABLOCK, XLOCK);。別のクエリを開き(#2)、次を実行しますSELECT * FROM ##Test;。SELECTは、排他ロックを使用しているタブ#1でまだ開いているトランザクションによってブロックされているため、戻りません。#2でSELECTをキャンセルします。SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTEDタブ#2で1回実行します。タブ#2でSELECTだけをもう一度実行すると、戻ってきます。必ずROLLBACKタブ#1で実行してください。
ソロモンルツキー2014年

21

コミットされていない読み取りトランザクション分離レベルを使用するのが最善の選択であることに私は完全に同意しましたが、マネージャーまたはクライアントの要求によってNOLOCKヒントを使用せざるを得ず、これに対する理由は受け入れられませんでした。

Entity Framework 6では、次のように独自のDbCommandInterceptorを実装できます。

public class NoLockInterceptor : DbCommandInterceptor
{
    private static readonly Regex _tableAliasRegex = 
        new Regex(@"(?<tableAlias>AS \[Extent\d+\](?! WITH \(NOLOCK\)))", 
            RegexOptions.Multiline | RegexOptions.IgnoreCase);

    [ThreadStatic]
    public static bool SuppressNoLock;

    public override void ScalarExecuting(DbCommand command, 
        DbCommandInterceptionContext<object> interceptionContext)
    {
        if (!SuppressNoLock)
        {
            command.CommandText = 
                _tableAliasRegex.Replace(command.CommandText, "${tableAlias} WITH (NOLOCK)");
        }
    }

    public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        if (!SuppressNoLock)
        {
            command.CommandText = 
                _tableAliasRegex.Replace(command.CommandText, "${tableAlias} WITH (NOLOCK)");
        }
    }
}

このクラスを配置すると、アプリケーションの開始時にそれを適用できます。

DbInterception.Add(new NoLockInterceptor());

またNOLOCK、現在のスレッドのクエリへのヒントの追加を条件付きでオフにします。

NoLockInterceptor.SuppressNoLock = true;

私は、このソリューションのように私はわずかに正規表現を変えたものの:
ラス

2
(?<tableAlias>] AS [Extent \ d +](?! WITH(NOLOCK)))は、エラーを引き起こす派生テーブルにnolockを追加しないようにします。:)
Russ

スレッドレベルでSuppressNoLockを設定するのは便利な方法ですが、ブール値を設定解除するのを忘れるのは簡単です。IDisposableを返す関数を使用する必要があります。Disposeメソッドは、ブール値を再びfalseに設定するだけです。また、ThreadStaticは非同期/のawaitと本当に互換性がありません:stackoverflow.com/questions/13010563/...
ヤープ

または、ISOLATION LEVELを使用する場合: public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { if (!SuppressNoLock) command.CommandText = $"SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;{Environment.NewLine}{command.CommandText}"; base.ReaderExecuting(command, interceptionContext); }
Adi

データベース関数にもnolockを追加しています。関数を回避する方法は?
Ivan Lewis

9

強化ドクター・ジョーンズの受け入れ答えと使用してPostSharpを

最初の「ReadUncommitedTransactionScopeAttribute

[Serializable]
public class ReadUncommitedTransactionScopeAttribute : MethodInterceptionAspect
{
    public override void OnInvoke(MethodInterceptionArgs args)
    {
        //declare the transaction options
        var transactionOptions = new TransactionOptions();
        //set it to read uncommited
        transactionOptions.IsolationLevel = IsolationLevel.ReadUncommitted;
        //create the transaction scope, passing our options in
        using (var transactionScope = new TransactionScope(TransactionScopeOption.Required, transactionOptions))
        {
            //declare our context
            using (var scope = new TransactionScope())
            {
                args.Proceed();
                scope.Complete();
            }
        }
    }
}

必要なときはいつでも

    [ReadUncommitedTransactionScope()]
    public static SomeEntities[] GetSomeEntities()
    {
        using (var context = new MyEntityConnection())
        {
            //any reads we do here will also read uncomitted data
            //...
            //...

        }
    }

インターセプターを使用して「NOLOCK」を追加できることも便利ですが、Oracleなどの他のデータベースシステムに接続する場合は機能しません。


6

これを回避するには、データベースにビューを作成し、ビューのクエリにNOLOCKを適用します。次に、ビューをEF内のテーブルとして扱います。


4

EF6の導入により、BeginTransaction()メソッドを使用することをお勧めします。

EF6 +およびEF Coreでは、TransactionScopeの代わりにBeginTransactionを使用できます

using (var ctx = new ContractDbContext())
using (var transaction = ctx.Database.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted))
{
    //any reads we do here will also read uncommitted data
}

2

いいえ、本当ではありません-Entity Frameworkは基本的に、実際のデータベースの上にあるかなり厳密なレイヤーです。クエリはESQL-エンティティSQL-で最初に作成され、エンティティモデルを対象としています。EFは複数のデータベースバックエンドをサポートしているため、「ネイティブ」SQLをバックエンドに直接送信することはできません。

NOLOCKクエリヒントはSQL Server固有のものであり、他のサポートされているデータベースでは動作しません(同じヒントが実装されていない限り、これは疑わしいです)。

マーク


この回答は古くなっています。他の人が述べたようにNOLOCKを使用でき、Database.ExecuteSqlCommand()またはを使用して「ネイティブ」SQLを実行できますDbSet<T>.SqlQuery()
2016年

1
@Dunc:downvoteに感謝-ところで:あなたがする必要がありませ使っ(NOLOCK)とにかく-を参照してキックに悪い習慣を-どこでもNOLOCKを置く -されて推奨しませんどこでもこれを使用するために-かなり反対!
marc_s 2016年

0

1つのオプションは、(Ryanによって提案されたビューソリューションと同様の)ストアドプロシージャを使用して、EFからストアドプロシージャを実行することです。このように、EFは結果をパイプ処理するだけで、ストアドプロシージャはダーティリードを実行します。

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