What is the best way to implement different isolation levels for individual transactions when using a client framework, ORM or similar to build queries, which does not support query hints like WITH(NOLOCK)?
Imagine an application which uses ReadUncommitted level for a number of complex and long running queries (well aware of the related risks), and it is supposed to run with NHibernate and it's query criteria (or QueryOver/LINQ, just no string concatenation!).
NHibernate doesn't support the with(nolock) hint (except when using native SQL, which is currently used in many cases).
So, in order to replace the native SQL strings and their tedious building code, I want to use transactions with IsolationLevel.ReadUncommitted to replace 'with(nolock)'.
But the connection remains in the changed isolation level even after Commit/Rollback, running everything in the new level. Even after connection.Close(), it is returned to the connection pool and reused with the changed isolation level.
I originally noticed this, because I tested opening a connection with Snapshot isolation level and sending a simple query, to disable Read Uncommitted if Snapshot mode on database is enabled (no easy switch to snapshot in general possible). The test database had snapshot mode disabled, so I got an exception and set my UseReadUncommitted variable to 'true' in the catch block, but later queries from a "new"/reused connection still got the same exception.
I wrote a simple class to wrap transaction handling in a using block, automatically resetting the IsolationLevel in .Dispose(). But this seems to cause two extra roundtrips to the DB, and I am not sure if the altered isolation level might "survive" the disposal in certain situations and affect other queries. The code worked in a first try, it's for plain ADO.NET connections/transactions (I will do another for NHibernate sessions, if good!).
Any suggestions?
public class TransactionContainerTempIsolationLevel : IDisposable
{
public IsolationLevel OldIsolationLevel { get; private set; }
public IsolationLevel TempIsolationLevel { get; private set; }
public IDbTransaction Transaction { get; private set; }
private readonly IDbConnection _conn;
public TransactionContainerTempIsolationLevel(IDbConnection connection, IsolationLevel tempIsolationLevel)
{
_conn = connection;
LocalIsolationLevel = localIsolationLevel;
var checkTran = _conn.BeginTransaction();
if (checkTran.IsolationLevel == tempIsolationLevel)
{
Transaction = checkTran;
}
else
{
OldIsolationLevel = checkTran.IsolationLevel;
checkTran.Dispose();
Transaction = _conn.BeginTransaction(tempIsolationLevel);
}
}
public void Dispose()
{
Transaction.Dispose();
if (OldIsolationLevel != TempIsolationLevel)
{
using (var restoreTran = _conn.BeginTransaction(OldIsolationLevel))
{
restoreTran.Commit();
}
}
}
}