为什么 Entity Framework 忽略 TransactionScope(不添加 NOLOCK)?
Why does Entity Framework ignore TransactionScope (not adding with NOLOCK)?
我错过了什么?
我正在尝试使用这样的 TransactionScope 使用 NOLOCK 进行读取:
var scopeOptions = new TransactionOptions { IsolationLevel = IsolationLevel.ReadUncommitted };
using (var scope = new TransactionScope(TransactionScopeOption.Required, scopeOptions))
{
using (var db = new MyDbContext(ConnectionStringEntities))
{
// Simple read with a try catch block...
}
scope.Complete();
}
我希望看到将 NOLOCK 添加到 SQL 查询中(查看 SQL Profiler 以及自定义 DbCommandInterceptor - 但它不存在...
更新: 经过更多研究,我想知道是否最终使用了选定的游标,只是没有 NOLOCK "hint" (SQL 服务器特定的 - 也特定于一个 table),我发现了一些获取当前事务的代码,它似乎显示了正确选择的事务隔离(ReadUncommitted / Serializable 等)我仍然想测试它但让我知道你有什么想法
Get current .net TransactionScope IsolationLevel
Transaction trans = Transaction.Current;
System.Transactions.IsolationLevel level = trans.IsolationLevel;
LogService.Instance.Debug($"Transaction IsolationLevel = {level.ToString()}");
您无法 Entity Framework 呈现 NOLOCK 提示。如果你想读取未提交的数据,你必须做一些不同的事情,就像你通过将带有 IsolationLevel.ReadUncommited 的 TransactionScope 添加到 TransactionOptions 所做的那样。
编写您自己的命令拦截器或您自己的 EF 提供程序也可以。
所以看起来 Entity Framework 确实尊重 IsolationLevel,只是它不使用 NOLOCK 提示(可能是因为它过于特定于数据库),顺便说一下,我对 EF 的主要抱怨是对于不同的数据库类型不是很优化,另一个例子是新身份将 AspNetUsers 的 GUID 主键保存为字符串(同样是因为缺乏优化)除此之外(和其他一些东西)EF 很棒!
我在任何地方都找不到解决我的问题的方法,我绝对不想让我的所有查询都使用 NOLOCK - 只是未提交的查询,所以我最终结合了两个解决方案(有一些变化):
NoLockInterceptor - 用于动态添加 NOLOCK (Entity Framework with NOLOCK):
/// <summary>
/// Add "WITH (NOLOCK)" hint to SQL queries, SQL Server specifc - may break queries on different databases.
/// (conditionally turn off with NoLockInterceptor.AddNoLockHintToSqlQueries = false to change on runtime)
/// <para>
///
/// </para>
/// </summary>
public class NoLockInterceptor : DbCommandInterceptor
{
private static readonly Regex TableAliasRegex = new Regex(
@"(?<tableAlias>AS \[Extent\d+\](?! WITH \(NOLOCK\)))",
RegexOptions.Multiline | RegexOptions.IgnoreCase);
/// <summary>
/// Add "WITH (NOLOCK)" hint to SQL queries - unique to each thread
/// (set to true only when needed and then back to false)
/// </summary>
[ThreadStatic]
public static bool AddNoLockHintToSqlQueries;
public NoLockInterceptor()
{
// Do not use by default for all queries
AddNoLockHintToSqlQueries = false;
}
public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
{
if (AddNoLockHintToSqlQueries)
{
command.CommandText = TableAliasRegex.Replace(command.CommandText, "${tableAlias} WITH (NOLOCK)");
}
}
public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
if (AddNoLockHintToSqlQueries)
{
command.CommandText = TableAliasRegex.Replace(command.CommandText, "${tableAlias} WITH (NOLOCK)");
}
}
}
TransactionWrapper - 调用 NoLockInterceptor 行为并且对于重复使用事务也很有用 (http://haacked.com/archive/2009/08/18/simpler-transactions.aspx/):
/// <summary>
/// Transaction wrapper for setting pre-defined transaction scopes
/// <para>
/// http://haacked.com/archive/2009/08/18/simpler-transactions.aspx/
/// </para>
/// </summary>
public static class TransactionWrapper
{
/// <summary>
/// Set transaction scope and using NoLockInterceptor for adding SQL Server specific "WITH (NOLOCK)"
/// to ReadUncommitted isolation level transactions (not supported by Entity Framework)
/// </summary>
/// <param name="isolationLevel"></param>
/// <param name="transactionScopeOption"></param>
/// <param name="timeout"></param>
/// <param name="action"></param>
public static void SetScope(IsolationLevel isolationLevel, TransactionScopeOption transactionScopeOption,
TimeSpan timeout, Action action)
{
var transactionOptions = new TransactionOptions { IsolationLevel = isolationLevel, Timeout = timeout };
using (var transactionScope = new TransactionScope(transactionScopeOption, transactionOptions))
{
if (isolationLevel == IsolationLevel.ReadUncommitted)
NoLockInterceptor.AddNoLockHintToSqlQueries = true;
action();
transactionScope.Complete();
if (isolationLevel == IsolationLevel.ReadUncommitted)
NoLockInterceptor.AddNoLockHintToSqlQueries = false;
}
}
}
这样使用:
var timeout = TimeSpan.FromSeconds(ConfigVariables.Instance.Timeout_Transaction_Default_In_Seconds);
TransactionWrapper.SetScope(IsolationLevel.ReadUncommitted, TransactionScopeOption.Required, timeout, () =>
{
using (var db = new MyDbContext(MyDbContextConnectionStringEntities))
{
// Do stuff...
}
});
NOLOCK 现在仅添加到具有 ReadUncommitted 事务隔离级别范围的查询。
我试过事务作用域,然后它分析了对数据库的调用。 EF 开始和结束事务,但从不更改 Read Committed 的隔离级别。
using (var scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions()
{
IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted
}))
{
List<T> toReturn = query.ToList();
scope.Complete();
return toReturn;
}
我错过了什么?
我正在尝试使用这样的 TransactionScope 使用 NOLOCK 进行读取:
var scopeOptions = new TransactionOptions { IsolationLevel = IsolationLevel.ReadUncommitted };
using (var scope = new TransactionScope(TransactionScopeOption.Required, scopeOptions))
{
using (var db = new MyDbContext(ConnectionStringEntities))
{
// Simple read with a try catch block...
}
scope.Complete();
}
我希望看到将 NOLOCK 添加到 SQL 查询中(查看 SQL Profiler 以及自定义 DbCommandInterceptor - 但它不存在...
更新: 经过更多研究,我想知道是否最终使用了选定的游标,只是没有 NOLOCK "hint" (SQL 服务器特定的 - 也特定于一个 table),我发现了一些获取当前事务的代码,它似乎显示了正确选择的事务隔离(ReadUncommitted / Serializable 等)我仍然想测试它但让我知道你有什么想法
Get current .net TransactionScope IsolationLevel
Transaction trans = Transaction.Current;
System.Transactions.IsolationLevel level = trans.IsolationLevel;
LogService.Instance.Debug($"Transaction IsolationLevel = {level.ToString()}");
您无法 Entity Framework 呈现 NOLOCK 提示。如果你想读取未提交的数据,你必须做一些不同的事情,就像你通过将带有 IsolationLevel.ReadUncommited 的 TransactionScope 添加到 TransactionOptions 所做的那样。
编写您自己的命令拦截器或您自己的 EF 提供程序也可以。
所以看起来 Entity Framework 确实尊重 IsolationLevel,只是它不使用 NOLOCK 提示(可能是因为它过于特定于数据库),顺便说一下,我对 EF 的主要抱怨是对于不同的数据库类型不是很优化,另一个例子是新身份将 AspNetUsers 的 GUID 主键保存为字符串(同样是因为缺乏优化)除此之外(和其他一些东西)EF 很棒!
我在任何地方都找不到解决我的问题的方法,我绝对不想让我的所有查询都使用 NOLOCK - 只是未提交的查询,所以我最终结合了两个解决方案(有一些变化):
NoLockInterceptor - 用于动态添加 NOLOCK (Entity Framework with NOLOCK):
/// <summary> /// Add "WITH (NOLOCK)" hint to SQL queries, SQL Server specifc - may break queries on different databases. /// (conditionally turn off with NoLockInterceptor.AddNoLockHintToSqlQueries = false to change on runtime) /// <para> /// /// </para> /// </summary> public class NoLockInterceptor : DbCommandInterceptor { private static readonly Regex TableAliasRegex = new Regex( @"(?<tableAlias>AS \[Extent\d+\](?! WITH \(NOLOCK\)))", RegexOptions.Multiline | RegexOptions.IgnoreCase); /// <summary> /// Add "WITH (NOLOCK)" hint to SQL queries - unique to each thread /// (set to true only when needed and then back to false) /// </summary> [ThreadStatic] public static bool AddNoLockHintToSqlQueries; public NoLockInterceptor() { // Do not use by default for all queries AddNoLockHintToSqlQueries = false; } public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { if (AddNoLockHintToSqlQueries) { command.CommandText = TableAliasRegex.Replace(command.CommandText, "${tableAlias} WITH (NOLOCK)"); } } public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { if (AddNoLockHintToSqlQueries) { command.CommandText = TableAliasRegex.Replace(command.CommandText, "${tableAlias} WITH (NOLOCK)"); } } }
TransactionWrapper - 调用 NoLockInterceptor 行为并且对于重复使用事务也很有用 (http://haacked.com/archive/2009/08/18/simpler-transactions.aspx/):
/// <summary> /// Transaction wrapper for setting pre-defined transaction scopes /// <para> /// http://haacked.com/archive/2009/08/18/simpler-transactions.aspx/ /// </para> /// </summary> public static class TransactionWrapper { /// <summary> /// Set transaction scope and using NoLockInterceptor for adding SQL Server specific "WITH (NOLOCK)" /// to ReadUncommitted isolation level transactions (not supported by Entity Framework) /// </summary> /// <param name="isolationLevel"></param> /// <param name="transactionScopeOption"></param> /// <param name="timeout"></param> /// <param name="action"></param> public static void SetScope(IsolationLevel isolationLevel, TransactionScopeOption transactionScopeOption, TimeSpan timeout, Action action) { var transactionOptions = new TransactionOptions { IsolationLevel = isolationLevel, Timeout = timeout }; using (var transactionScope = new TransactionScope(transactionScopeOption, transactionOptions)) { if (isolationLevel == IsolationLevel.ReadUncommitted) NoLockInterceptor.AddNoLockHintToSqlQueries = true; action(); transactionScope.Complete(); if (isolationLevel == IsolationLevel.ReadUncommitted) NoLockInterceptor.AddNoLockHintToSqlQueries = false; } } }
这样使用:
var timeout = TimeSpan.FromSeconds(ConfigVariables.Instance.Timeout_Transaction_Default_In_Seconds);
TransactionWrapper.SetScope(IsolationLevel.ReadUncommitted, TransactionScopeOption.Required, timeout, () =>
{
using (var db = new MyDbContext(MyDbContextConnectionStringEntities))
{
// Do stuff...
}
});
NOLOCK 现在仅添加到具有 ReadUncommitted 事务隔离级别范围的查询。
我试过事务作用域,然后它分析了对数据库的调用。 EF 开始和结束事务,但从不更改 Read Committed 的隔离级别。
using (var scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions()
{
IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted
}))
{
List<T> toReturn = query.ToList();
scope.Complete();
return toReturn;
}