What am I missing?
I'm trying to read with NOLOCK using a TransactionScope like this:
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();
}
I expected to see with NOLOCK added to the SQL query (looking in SQL Profiler and also a custom DbCommandInterceptor - but it's not there...
UPDATE: after some more research, I wonder if the selected cursor is being used after all, just without the NOLOCK "hint" (SQL Server specific - and also specific to just one table), I found some code that get the current transaction and it seem to show the right selected transaction isolation (ReadUncommitted / Serializable etc.)I still want to test it but let me know if you have any thoughts
Get current .net TransactionScope IsolationLevel
Transaction trans = Transaction.Current;
System.Transactions.IsolationLevel level = trans.IsolationLevel;
LogService.Instance.Debug($"Transaction IsolationLevel = {level.ToString()}");
So it looks like Entity Framework does respect the IsolationLevel, only it does not use the NOLOCK hint (probably because it is too database specific) and this by the way my main complaint against EF - that it is not very optimized for different database types, another example is where the new identity is saving a GUID primary key for AspNetUsers as a string (again for lack of optimization) other than that (and few other things) EF is awesome!
I could not find a solution to my problem anywhere, I definitely didn't want to make all my queries use NOLOCK - just the uncommitted ones, so I ended up combining two solutions (with some changes):
NoLockInterceptor - for adding NOLOCK on the fly (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>
/// https://stackoverflow.com/questions/926656/entity-framework-with-nolock
/// </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 - to invoke the NoLockInterceptor behaviour and also useful for repeated use of transactions (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;
}
}
}
Use it like this:
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 is now added just to queries with a ReadUncommitted transaction isolation level scopes.