获取实体框架6在其下面的SELECT语句中使用NOLOCK

时间:2014-07-10 19:39:41

标签: c# sql .net sql-server entity-framework

我在MVC 5项目中使用Entity Framework 6。正如您所知,如果我们在其中使用SELECT,那么SQL Server中的WITH (NOLOCK)查询执行速度更快,效率更高。我查看了Entity Framework 6生成的一些SQL SELECT语句,并意识到它们都不包含NOLOCK。

我不想在我的提取操作中使用事务来读取未提交的事务。

如何在下面生成的SELECT语句中强制使用EF 6来使用NOLOCK?

5 个答案:

答案 0 :(得分:47)

首先......你永远不应该为每个SQL语句使用NOLOCK。它可能会损害数据的完整性。

就像任何其他查询一样,提示你只有在做一些与众不同的事情时才能使用的机制。

无法告诉EF Provider呈现NoLock提示。如果您确实需要读取未提交的数据,则可以使用以下选项。

  1. 编写您自己的EntityFramework Provider。

  2. 使用Command Interceptor修改语句 执行。 http://msdn.microsoft.com/en-us/data/dn469464.aspx

  3. 将TransactionScope与IsolationLevel.ReadUncommited一起使用。

  4. 我知道你说你不想使用交易,但它是唯一一种读取未提交数据的开箱即用方式。此外,它不会产生太多开销,因为SQL Server中的每个语句都“隐式地”在事务中运行。

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

    修改 值得注意的是,NOLOCK for Updates and Deletes(选择保持不变)已被Microsoft从SQL Server 2016中弃用,并且将在'a'未来版本中删除。

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

答案 1 :(得分:9)

我同意codeworx所说的Read Uncommitted可能很危险。如果你能忍受脏读,那就去吧。

我找到了一种方法,可以在不改变当前查询中的任何内容的情况下完成这项工作。

你需要像这样创建一个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写入数据,其中数据不是基于数据库的读数。 (由于脏读,数据可能会被破坏),因此工作正常。

答案 2 :(得分:7)

您可以使用不为每个查询使用事务范围的变通方法。如果运行下面的代码,ef将对同一服务器进程ID使用相同的事务隔离级别。由于服务器进程ID在同一请求中不会更改,因此每个请求只能调用一次就足够了。这也适用于EF Core。

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

答案 3 :(得分:2)

在我们的项目中,我们使用第二种解决方案和第三种解决方案的组合,这是@Cem Mutlu和@anotherNeo建议的。

使用Sql Profiler进行的实验表明,我们必须使用一对命令:

  • 读未提交
  • 已提交阅读

因为通过SqlConnectionPool的NET重用连接

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}";
    }
}

答案 4 :(得分:0)

首先,请对 gds03 的回答点赞。因为如果没有它,我不会走到这一步。

我也为“关闭事务”和为 IDataReader/DbDataReader 情况安排正确的时间做出了贡献。 基本上,在 IDataReader/DbDataReader 情况下,您不会关闭“ReaderExecuted”(Async) 方法上的事务(强调 Executed 的“ed”),而是让它“落入”(覆盖)DataReaderDisposing。

但是,如果您阅读了其他一些答案(此处)(和评论),我认为 SetEndTransaction 是...不从连接池中获取伏都教的重要组成部分(如果您可能不使用(对我而言)未提交读)。

using System.Data;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Diagnostics;

namespace My.Interceptors
{
    public class IsolationLevelInterceptor : DbCommandInterceptor
    {
        private IsolationLevel _isolationLevel;

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

        //[ThreadStatic]
        //private DbCommand _command;


        public override InterceptionResult DataReaderDisposing(DbCommand command, DataReaderDisposingEventData eventData, InterceptionResult result)
        {
            InterceptionResult returnItem = base.DataReaderDisposing(command, eventData, result);
            SetEndTransaction(command);
            return returnItem;
        }




        public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
        {
            SetStartTransaction(command);
            InterceptionResult<DbDataReader> returnItem = base.ReaderExecuting(command, eventData, result);
            return returnItem;
        }


        public override DbDataReader ReaderExecuted(DbCommand command, CommandExecutedEventData eventData, DbDataReader result)
        {
            DbDataReader returnItem = base.ReaderExecuted(command, eventData, result);
            //SetEndTransaction(command); // DO NOT DO THIS HERE .. datareader still open and working .. fall back on DataReaderDisposing... you don't really need this override, but left in to show the possible issue.
            return returnItem;
        }


        public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = default)
        {
            SetStartTransaction(command);
            ValueTask<InterceptionResult<DbDataReader>> returnItem = base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
            return returnItem;
        }


        public override ValueTask<DbDataReader> ReaderExecutedAsync(DbCommand command, CommandExecutedEventData eventData, DbDataReader result, CancellationToken cancellationToken = default)
        {
            ValueTask<DbDataReader> returnItem = base.ReaderExecutedAsync(command, eventData, result, cancellationToken);
            //SetEndTransaction(command); // DO NOT DO THIS HERE .. datareader still open and working .. fall back on DataReaderDisposing... you don't really need this override, but left in to show the possible issue.
            return returnItem;
        }


        public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<object> interceptionContext)
        {
            SetStartTransaction(command);
            InterceptionResult<object> returnItem = base.ScalarExecuting(command, eventData, interceptionContext);
            return returnItem;
        }

        public override object ScalarExecuted(DbCommand command, CommandExecutedEventData eventData, object result)
        {
            SetEndTransaction(command);
            object returnItem = base.ScalarExecuted(command, eventData, result);
            return returnItem;
        }


        public override ValueTask<InterceptionResult<object>> ScalarExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<object> result, CancellationToken cancellationToken = default)
        {
            SetStartTransaction(command);
            ValueTask<InterceptionResult<object>> returnItem = base.ScalarExecutingAsync(command, eventData, result, cancellationToken);
            return returnItem;
        }


        public override ValueTask<object> ScalarExecutedAsync(DbCommand command, CommandExecutedEventData eventData, object result, CancellationToken cancellationToken = default)
        {
            SetEndTransaction(command);
            ValueTask<object> returnItem = base.ScalarExecutedAsync(command, eventData, result, cancellationToken);
            return returnItem;
        }


        /* start maybe not needed on queries that only do "reading", but listed here anyways */

        public override InterceptionResult<int> NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<int> result)
        {
            SetStartTransaction(command);
            InterceptionResult<int> returnItem = base.NonQueryExecuting(command, eventData, result);
            return returnItem;
        }

        public override int NonQueryExecuted(DbCommand command, CommandExecutedEventData eventData, int result)
        {
            int returnValue = base.NonQueryExecuted(command, eventData, result);
            SetEndTransaction(command);
            return returnValue;
        }

        public override ValueTask<InterceptionResult<int>> NonQueryExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default)
        {
            SetStartTransaction(command);
            ValueTask<InterceptionResult<int>> returnItem = base.NonQueryExecutingAsync(command, eventData, result, cancellationToken);
            return returnItem;
        }

        public override ValueTask<int> NonQueryExecutedAsync(DbCommand command, CommandExecutedEventData eventData, int result, CancellationToken cancellationToken = default)
        {
            ValueTask<int> returnValue = base.NonQueryExecutedAsync(command, eventData, result, cancellationToken);
            SetEndTransaction(command);
            return returnValue;
        }

        /* end maybe not needed on queries that only do "reading", but listed here anyways */

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

        private void SetEndTransaction(DbCommand command)
        {
            if (command != null)
            {
                if (command.Transaction != null)
                {
                    command.Transaction.Commit();
                    //_command = command;
                }

                command.Dispose();
            }
        }

    }
}

这篇文章有助于“看到”所有的“ing”和“ed”方法。

https://lizzy-gallagher.github.io/query-interception-entity-framework/

请注意,我的答案是 EntityFrameworkCore (3.1.+),但我认为它会“向后移植”到 EF-for-DN-Framework。

我的回答中更重要的部分是某些方法的“时间”……尤其是 IDataReader/DbDataReader 方法。