带 NOLOCK 的实体框架

如何在实体框架上使用 NOLOCK函数? XML 是唯一的方法吗?

83447 次浏览

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

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

马克

没有,但是您可以启动一个事务并设置 读取未提交的隔离级别。这在本质上与 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();
}

为了解决这个问题,我在数据库上创建了一个视图,并对视图的查询应用了 NOLOCK。然后,我将视图视为 EF 中的一个表。

如果你需要大量的东西,我们发现最好的方法就是在你创建了对象上下文之后,通过运行这个简单的命令简单的设置你的连接的默认事务隔离:

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

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

使用这种技术,我们能够创建一个简单的 EF 提供程序,它为我们创建上下文,并且每次都为我们的所有上下文运行这个命令,因此默认情况下我们总是处于“ read uncommit”状态。

扩展方法可以使这更容易

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

虽然我完全同意使用事务隔离阅读是最好的选择,但是有时你被迫使用 NOLOCK 提示,经理或客户的要求,没有理由反对这个接受。

使用实体框架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;

提高 琼斯博士的公认答案和使用 PostSharp;

第一个“ 属性

[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)时不能工作。

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

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

可以在 EF6 + 和 EF 核心中使用 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
}

这个线程死亡,因为它是第一次出现在谷歌搜索。EF 的查询结构发生了一些变化。这个正则表达式还将涵盖连接。

另外,事务级别读取未提交的工作(例如上面的 transactionscope) ,但是它仍然会阻止更新。使用表级 nolock 将允许继续更新。在这样做之前,要非常清楚并且做一些关于使用脏读的影响的研究

(?<tableAlias>((FROM)|(JOIN))\s\[([^\s]+)\]\sAS\s\[([^\s]+)\](?!\sWITH\s\(NOLOCK\)))

您可以使用任何正则表达式在线测试程序(如 Regex101.com)对此示例进行测试

FROM [table1] AS [t]
INNER JOIN [table2] AS [t2] ON ([t].[ID] = [t2].[ID])
INNER JOIN [table3] AS [t3] WITH (NOLOCK) ON ([t].[ID] = [t3].[ID])

我也清理了示例,但遗漏了触发器的 bool 标志。想加就加吧

public class NoLockInterceptor : DbCommandInterceptor
{


public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
{
            

var finishedresult = base.ReaderExecuting(command.NoLockCommand(), eventData, result);
return finishedresult;
}


public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = default)
{
            

var finishedresult = base.ReaderExecutingAsync(command.NoLockCommand(), eventData, result, cancellationToken);
return finishedresult;
}


public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<object> result)
{
var finishedresult = base.ScalarExecuting(command.NoLockCommand(), eventData, result);
return finishedresult;
}


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


        

}


public static class DbCommandExtensions
{
private static Regex _tableAliasRegex = new Regex(@"(?<tableAlias>((FROM)|(JOIN))\s\[([^\s]+)\]\sAS\s\[([^\s]+)\](?!\sWITH\s\(NOLOCK\)))",
RegexOptions.Multiline | RegexOptions.IgnoreCase);


public static DbCommand NoLockCommand(this DbCommand command)        {


string updatedCommandText = _tableAliasRegex.Replace(command.CommandText, "${tableAlias} WITH (NOLOCK)");


command.CommandText = updatedCommandText;
return command;
}
}