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

Ara*_*ash 36 .net c# sql sql-server entity-framework

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

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

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

cod*_*orx 50

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

这就像任何其他查询提示你应该只在你做一些与众不同的事情时才使用的机制.

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

  1. 编写自己的EntityFramework提供程序.

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

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

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

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

编辑: 重要的是要注意NOLOCK for Updates和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

  • 是的,我完全理解在SELECT语句中使用NOLOCK的可能后果.对于保留大量数据的表使用NOLOCK,我们完全没问题,因为WHERE子句知道从"已提交"部分获取数据的位置.该特定表用于在正在进行的事务中插入/更新大量数据,这可能需要几分钟才能完成.但是,当事务正在进行时,我们想要使用数据表的另一个区域.我们的底线是我们不想失去申请的速度. (4认同)
  • “ ...从不...”和“任何其他查询都暗示了一种机制,只有在您做不同寻常的事情时才应使用”-谁说他没有在做不同寻常的事情?提示必须谨慎使用并理解,但不是邪恶的。 (3认同)
  • @Arash - 更好的解决方案是在快照模式下设置数据库... (2认同)
  • @Arash如果这是你的问题,为什么不禁用表级(和页面级)锁定而不是使用NOLOCK?如果读取和写入不接触相同的数据,则效果应该相同. (2认同)
  • 由于有些人似乎对我在答案中使用“NEVER EVER”有疑问,因此我想澄清我的确切含义。我使用这句话的方式与下面句子中使用的方式相同:你永远不应该尝试将飞机降落在哈德逊河上,除非两个引擎都停机了,否则就会坠毁在曼哈顿中城。 (2认同)

Cem*_*tlu 10

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

this.Database.ExecuteSqlCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;");
Run Code Online (Sandbox Code Playgroud)

  • 如果您的SQL Server版本早于Sql Server 2014,则此解决方案存在很大风险,因为这会为整个连接设置隔离级别.一旦完成连接,它将返回到连接池,并且将抓取此特定连接的下一个调用也将以该隔离级别执行,这可能是错误的.Sql Server 2014及更高版本在将连接返回到池时重置隔离级别. (6认同)

ano*_*Neo 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;
            }
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

然后在cctor(dbcontext的静态构造函数)中,只需将拦截器添加到实体框架集合的DbInfrastructure中.

DbInterception.Add(new IsolationLevelInterceptor());
Run Code Online (Sandbox Code Playgroud)

对于EF发送到商店的每个命令,这将包含具有该隔离级别的事务.

在我的情况下运作良好,因为我们通过API写入数据,其中数据不是基于数据库的读数.(由于脏读,数据可能会被破坏),所以工作正常.


Stu*_*tuS 6

在我们的项目中,我们结合了@Cem Mutlu 和@anotherNeo 建议的第二个和第三个解决方案。

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}";
    }
}
Run Code Online (Sandbox Code Playgroud)