web-dev-qa-db-ja.com

Entity Framework 6を​​取得し、その下のSELECTステートメントでNOLOCKを使用します

MVC 5プロジェクトでEntity Framework 6を​​使用しています。ご存知のように、SQL ServerのSELECTクエリは、WITH (NOLOCK)を使用すると、より高速で効率的に実行されます。 Entity Framework 6で生成されたいくつかのSQL SELECTステートメントをチェックアウトすると、NOLOCKが含まれていないことがわかりました。

コミットされていないトランザクションから読み取るために、フェッチ操作でトランザクションを使用したくありません。

生成されたSELECTステートメントの下でNOLOCKを使用するようにEF 6を強制するにはどうすればよいですか?

33
Arash

まず第一に...各SQLステートメントに対してNOLOCKを決して使用しないでください。データの整合性を損なう可能性があります。

他のクエリヒントのように、通常とは異なる操作を行う場合にのみ使用すべきメカニズムです。

EFプロバイダーにNoLockヒントを表示するように指示する方法はありません。コミットされていないデータを本当に読み取る必要がある場合は、次のオプションがあります。

  1. 独自のEntityFrameworkプロバイダーを作成します。

  2. コマンドインターセプターを使用して、ステートメントを実行する前に変更します。 http://msdn.Microsoft.com/en-us/data/dn469464.aspx

  3. IsolationLevel.ReadUncommitedでTransactionScopeを使用します。

トランザクションを使用したくないと言っていましたが、コミットされていないデータを読み取るための唯一の独創的な方法です。また、SQL Serverの各ステートメントがトランザクションで「暗黙的に」実行されるため、オーバーヘッドはあまり発生しません。

using (new TransactionScope(
                    TransactionScopeOption.Required, 
                    new TransactionOptions 
                    { 
                         IsolationLevel = IsolationLevel.ReadUncommitted 
                    })) 
{
        using (var db = new MyDbContext()) { 
            // query
        }
}

EDIT:また、更新と削除のNOLOCK(選択はそのまま)は、SQL Server 2016の時点でMicrosoftによって非推奨になり、今後も廃止されることに注意することが重要です。 'a'将来のリリースで削除されました。

https://docs.Microsoft.com/en-us/sql/database-engine/deprecated-database-engine-features-in-sql-server-2016?view=sql-server-2017

49
codeworx

各クエリにトランザクションスコープを使用しない回避策を使用できます。以下のコードを実行すると、efは同じサーバープロセスIDに対して同じトランザクション分離レベルを使用します。サーバープロセスIDは同じリクエストで変更されないため、リクエストごとに1回の呼び出しで十分です。これはEF Coreでも機能します。

this.Database.ExecuteSqlCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;");
9
Cem Mutlu

読み取り非コミットが危険になる可能性のある方法で、codeworxが言っていることに同意します。ダーティリードで生きることができるなら、それを試してください。

現在のクエリで何も変更せずにこの機能を実現する方法を見つけました。

次のようなDbCommandInterceptorを作成する必要があります。

public class IsolationLevelInterceptor : DbCommandInterceptor
{
    private IsolationLevel _isolationLevel;

    public IsolationLevelInterceptor(IsolationLevel level)
    {
        _isolationLevel = level;
    }



    //[ThreadStatic]
    //private DbCommand _command;


    public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        SetTransaction(command);

    }

    public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
        SetTransaction(command);
    }

    public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    {
        SetTransaction(command);
    }




    private void SetTransaction(DbCommand command)
    {
        if (command != null)
        {
            if (command.Transaction == null)
            {
                var t = command.Connection.BeginTransaction(_isolationLevel);
                command.Transaction = t;
                //_command = command;
            }
        }
    }

}

次に、cctor(dbcontextの静的コンストラクター)で、インターセプターをエンティティフレームワークコレクションのDbInfrastructureに追加します。

DbInterception.Add(new IsolationLevelInterceptor());

これにより、EFがストアに送信する各コマンドに対して、その分離レベルでトランザクションがラップされます。

私の場合、そのデータがデータベースからの読み取りに基づいていないAPIを介してデータを書き込むため、うまく機能しました。 (ダーティリードが原因でデータが破損する可能性があります)。

9
anotherNeo

このプロジェクトでは、@ Cem Mutluと@anotherNeoによって提案された2番目と3番目のソリューションの組み合わせを使用します。

Sql Profilerの実験では、コマンドのペアを使用する必要があることが示されました。

  • 非コミットで読む
  • コミットを読む

nETは SqlConnectionPool を介して接続を再利用するため

internal class NoLockInterceptor : DbCommandInterceptor
{
    public static readonly string SET_READ_UNCOMMITED = "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED";
    public static readonly string SET_READ_COMMITED = "SET TRANSACTION ISOLATION LEVEL READ COMMITTED";

    public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
        if (!interceptionContext.DbContexts.OfType<IFortisDataStoreNoLockContext>().Any())
        {
            return;
        }

        ExecutingBase(command);
    }

    public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        if (!interceptionContext.DbContexts.OfType<IFortisDataStoreNoLockContext>().Any())
        {
            return;
        }

        ExecutingBase(command);
    }

    private static void ExecutingBase(DbCommand command)
    {
        var text = command.CommandText;
        command.CommandText = $"{SET_READ_UNCOMMITED} {Environment.NewLine} {text} {Environment.NewLine} {SET_READ_COMMITED}";
    }
}
2
StuS