带有NOLOCK的实体框架


Answers:


207

否,但是您可以启动事务并将隔离级别设置为uncommited。这本质上与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

@FMFF我不知道是否为EF4引入了任何新功能。我确实知道上述代码可用于EFv1及更高版本。
琼斯医生

会有什么后果?如果有人在上述块中省略了transactionScope.Complete()?您认为我应该为此提出另一个问题吗?
Eakan Gopalakrishnan 2015年

@EakanGopalakrishnan未能调用此方法将中止事务,因为事务管理器将其解释为系统故障或事务范围内引发的异常。(从MSDN采取msdn.microsoft.com/en-us/library/...
Jones医生

1
@JsonStatham它已添加到此拉请求中,这是针对里程碑2.1.0的
琼斯医生

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,请不要忘记总是配置您的连接上下文。
2014年

能够缩小问题范围,以排除可能的交易范围。谢谢。与我在构造函数中拥有的一些连接重试功能有关。
Ben Tidman 2014年

我认为范围应为TransactionScopeOption.Suppress
CodeGrue 2014年

@Alexandre如果我在另一个ReadCommitted事务中执行此操作会发生什么?例如,我产生了一个事务来开始保存数据,但是现在我正在查询更多数据,因此产生了一个ReadUncommitted事务?将其称为“完成”还会完成我的外部交易吗?请告知:)
Jason Loki Smith,

27

如果您需要大量的东西,最好的方法是,在每次创建对象上下文之后,通过运行以下简单命令,简单地在连接上设置默认的事务隔离级别:

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

http://msdn.microsoft.com/zh-CN/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

我已经尝试过了,我们有一个高负载环境,其中在这些事务范围(和匹配的事务)之一中不执行查询将导致死锁。我的观察是在SQL 2005服务器上进行的,因此我不知道此后的行为是否已更改。因此,我建议您这样做;如果指定读取未提交隔离级别,但仍然遇到死锁,请尝试将查询放入事务中。如果您没有创建事务就不会遇到僵局,那就足够了。
2013年

3
@DoctorJones-关于Microsoft SQL Server,所有查询本质上都是事务。指定显式事务只是将2条或更多条语句分组到同一事务中的一种手段,因此可以将它们视为工作的基本单元。该SET TRANSACTION ISOLATION LEVEL...命令会影响连接级别的属性,因此会影响从该点开始(对于THAT连接)所做的所有SQL语句,除非被查询提示覆盖。至少从SQL Server 2000开始,并且可能在更早之前,这种现象就已经存在。
所罗门·鲁兹基

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;。由于使用排他锁的选项卡#1中仍处于打开状态的事务阻止了SELECT,因此SELECT不会返回。取消#2中的SELECT。SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED在标签#2中运行一次。再次在选项卡#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;

我喜欢此解决方案,尽管我对正则表达式进行了一些更改:
Russ 2014年

2
(?<tableAlias>] AS [Extent \ d +](?! WITH(NOLOCK)))以防止在派生表中添加nolock从而导致错误。:)
俄罗斯

在线程级别设置SuppressNoLock是一种方便的方法,但是很容易忘记取消设置布尔值,应该使用返回IDisposable的函数,Dispose方法可以再次将bool设置为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附加到数据库功能。如何避免功能?
伊万·刘易斯

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这样的其他数据库系统时将不起作用。



4

随着EF6的引入,Microsoft建议使用BeginTransaction()方法。

您可以在EF6 +和EF Core中使用BeginTransaction代替TransactionScope

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

不,不是真的-实体框架基本上是您实际数据库之上的一个相当严格的层。您的查询以ESQL-实体SQL编写,它首先针对您的实体模型,并且由于EF支持多个数据库后端,因此您无法真正将“本机” SQL直接发送到后端。

NOLOCK查询提示是SQL Server特有的,不能在任何其他受支持的数据库上运行(除非它们也实现了相同的提示-我对此表示强烈怀疑)。

马克


这个答案已经过时了-您可以像其他人一样使用NOLOCK,并且可以使用Database.ExecuteSqlCommand()或执行“本机” SQL DbSet<T>.SqlQuery()
邓克

1
@Dunc:谢谢你的不赞成-顺便说一句:无论如何你都不要使用(NOLOCK)-见坏习惯-将NOLOCK放到任何地方 - 不建议到处使用它-恰恰相反!
marc_s

0

一种选择是使用存储过程(类似于Ryan提出的视图解决方案),然后从EF执行存储过程。这样,存储过程执行脏读取,而EF仅通过管道传递结果。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.