diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs index 42ab8ab610..4728795365 100644 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ b/src/Umbraco.Core/Persistence/PetaPoco.cs @@ -215,6 +215,10 @@ namespace Umbraco.Core.Persistence _paramPrefix = "?"; if (_dbType == DBType.Oracle) _paramPrefix = ":"; + + // by default use MSSQL default ReadCommitted level + //TODO change to RepeatableRead - but that is breaking + _isolationLevel = IsolationLevel.ReadCommitted; } // Automatically close one open shared connection @@ -237,7 +241,27 @@ namespace Umbraco.Core.Persistence _sharedConnection.ConnectionString = _connectionString; _sharedConnection.OpenWithRetry();//Changed .Open() => .OpenWithRetry() extension method - _sharedConnection = OnConnectionOpened(_sharedConnection); + // ensure we have the proper isolation level, as levels can leak in pools + // read http://stackoverflow.com/questions/9851415/sql-server-isolation-level-leaks-across-pooled-connections + // and http://stackoverflow.com/questions/641120/what-exec-sp-reset-connection-shown-in-sql-profiler-means + // + // NPoco has that code in OpenSharedConnectionImp (commented out?) + //using (var cmd = _sharedConnection.CreateCommand()) + //{ + // cmd.CommandText = GetSqlForTransactionLevel(_isolationLevel); + // cmd.CommandTimeout = CommandTimeout; + // cmd.ExecuteNonQuery(); + //} + // + // although MSDN documentation for SQL CE clearly states that the above method + // should work, it fails & reports an error parsing the query on 'TRANSACTION', + // and Google is no help (others have faced the same issue... no solution). So, + // switching to another method that does work on all databases. + var tr = _sharedConnection.BeginTransaction(_isolationLevel); + tr.Commit(); + tr.Dispose(); + + _sharedConnection = OnConnectionOpened(_sharedConnection); if (KeepConnectionAlive) _sharedConnectionDepth++; // Make sure you call Dispose @@ -269,10 +293,20 @@ namespace Umbraco.Core.Persistence // Helper to create a transaction scope public Transaction GetTransaction() { - return new Transaction(this); - } + return GetTransaction(_isolationLevel); + } - // Use by derived repo generated by T4 templates + public Transaction GetTransaction(IsolationLevel isolationLevel) + { + return new Transaction(this, isolationLevel); + } + + public IsolationLevel CurrentTransactionIsolationLevel + { + get { return _transaction == null ? IsolationLevel.Unspecified : _transaction.IsolationLevel; } + } + + // Use by derived repo generated by T4 templates public virtual void OnBeginTransaction() { } public virtual void OnEndTransaction() { } @@ -281,17 +315,23 @@ namespace Umbraco.Core.Persistence // Use `using (var scope=db.Transaction) { scope.Complete(); }` to ensure correct semantics public void BeginTransaction() { - _transactionDepth++; + BeginTransaction(_isolationLevel); + } + + public void BeginTransaction(IsolationLevel isolationLevel) + { + _transactionDepth++; if (_transactionDepth == 1) { OpenSharedConnection(); - _transaction = _sharedConnection.BeginTransaction(); + _transaction = _sharedConnection.BeginTransaction(isolationLevel); _transactionCancelled = false; OnBeginTransaction(); } - - } + else if (isolationLevel > _transaction.IsolationLevel) + throw new Exception("Already in a transaction with a lower isolation level."); + } // Internal helper to cleanup transaction stuff void CleanupTransaction() @@ -324,7 +364,27 @@ namespace Umbraco.Core.Persistence CleanupTransaction(); } - // Helper to handle named parameters from object properties + // in NPoco this is in DatabaseType + private static string GetSqlForTransactionLevel(IsolationLevel isolationLevel) + { + switch (isolationLevel) + { + case IsolationLevel.ReadCommitted: + return "SET TRANSACTION ISOLATION LEVEL READ COMMITTED"; + case IsolationLevel.ReadUncommitted: + return "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED"; + case IsolationLevel.RepeatableRead: + return "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ"; + case IsolationLevel.Serializable: + return "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"; + case IsolationLevel.Snapshot: + return "SET TRANSACTION ISOLATION LEVEL SNAPSHOT"; + default: + return "SET TRANSACTION ISOLATION LEVEL READ COMMITTED"; + } + } + + // Helper to handle named parameters from object properties static Regex rxParams = new Regex(@"(? args_dest) { @@ -2264,16 +2324,17 @@ namespace Umbraco.Core.Persistence string _lastSql; object[] _lastArgs; string _paramPrefix = "@"; + IsolationLevel _isolationLevel; } // Transaction object helps maintain transaction depth counts public class Transaction : IDisposable { - public Transaction(Database db) - { - _db = db; - _db.BeginTransaction(); - } + public Transaction(Database db, IsolationLevel isolationLevel) + { + _db = db; + _db.BeginTransaction(isolationLevel); + } public virtual void Complete() {