/******************************************************** * ADO.NET 2.0 Data Provider for SQLite Version 3.X * Written by Robert Simpson (robert@blackcastlesoft.com) * * Released to the public domain, use at your own risk! ********************************************************/ #if !PLATFORM_COMPACTFRAMEWORK namespace System.Data.SQLite { using System.Globalization; using System.Transactions; internal sealed class SQLiteEnlistment : IDisposable, IEnlistmentNotification { internal SQLiteTransaction _transaction; internal Transaction _scope; internal bool _disposeConnection; internal SQLiteEnlistment( SQLiteConnection cnn, Transaction scope, System.Data.IsolationLevel defaultIsolationLevel, bool throwOnUnavailable, bool throwOnUnsupported ) { _transaction = cnn.BeginTransaction(GetSystemDataIsolationLevel( cnn, scope, defaultIsolationLevel, throwOnUnavailable, throwOnUnsupported)); _scope = scope; _scope.EnlistVolatile(this, EnlistmentOptions.None); } /////////////////////////////////////////////////////////////////////////// #region Private Methods private System.Data.IsolationLevel GetSystemDataIsolationLevel( SQLiteConnection connection, Transaction transaction, System.Data.IsolationLevel defaultIsolationLevel, bool throwOnUnavailable, bool throwOnUnsupported ) { if (transaction == null) { // // NOTE: If neither the transaction nor connection isolation // level is available, throw an exception if instructed // by the caller. // if (connection != null) return connection.GetDefaultIsolationLevel(); if (throwOnUnavailable) { throw new InvalidOperationException( "isolation level is unavailable"); } return defaultIsolationLevel; } IsolationLevel isolationLevel = transaction.IsolationLevel; // // TODO: Are these isolation level mappings actually correct? // switch (isolationLevel) { case IsolationLevel.Unspecified: return System.Data.IsolationLevel.Unspecified; case IsolationLevel.Chaos: return System.Data.IsolationLevel.Chaos; case IsolationLevel.ReadUncommitted: return System.Data.IsolationLevel.ReadUncommitted; case IsolationLevel.ReadCommitted: return System.Data.IsolationLevel.ReadCommitted; case IsolationLevel.RepeatableRead: return System.Data.IsolationLevel.RepeatableRead; case IsolationLevel.Serializable: return System.Data.IsolationLevel.Serializable; case IsolationLevel.Snapshot: return System.Data.IsolationLevel.Snapshot; } // // NOTE: When in "strict" mode, throw an exception if the isolation // level is not recognized; otherwise, fallback to the default // isolation level specified by the caller. // if (throwOnUnsupported) { throw new InvalidOperationException( HelperMethods.StringFormat(CultureInfo.CurrentCulture, "unsupported isolation level {0}", isolationLevel)); } return defaultIsolationLevel; } /////////////////////////////////////////////////////////////////////////// private void Cleanup(SQLiteConnection cnn) { if (_disposeConnection && (cnn != null)) cnn.Dispose(); _transaction = null; _scope = null; } #endregion /////////////////////////////////////////////////////////////////////////// #region IDisposable Members public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion /////////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members private bool disposed; private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) throw new ObjectDisposedException(typeof(SQLiteEnlistment).Name); #endif } /////////////////////////////////////////////////////////////////////////// private /* protected virtual */ void Dispose(bool disposing) { if (!disposed) { if (disposing) { //////////////////////////////////// // dispose managed resources here... //////////////////////////////////// if (_transaction != null) { _transaction.Dispose(); _transaction = null; } if (_scope != null) { // _scope.Dispose(); // NOTE: Not "owned" by us. _scope = null; } } ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////////// #region Destructor ~SQLiteEnlistment() { Dispose(false); } #endregion /////////////////////////////////////////////////////////////////////////// #region IEnlistmentNotification Members public void Commit(Enlistment enlistment) { CheckDisposed(); SQLiteConnection cnn = null; try { while (true) { cnn = _transaction.Connection; if (cnn == null) break; lock (cnn._enlistmentSyncRoot) /* TRANSACTIONAL */ { // // NOTE: This check is necessary to detect the case where // the SQLiteConnection.Close() method changes the // connection associated with our transaction (i.e. // to avoid a race (condition) between grabbing the // Connection property and locking its enlistment). // if (!Object.ReferenceEquals(cnn, _transaction.Connection)) continue; cnn._enlistment = null; _transaction.IsValid(true); /* throw */ cnn._transactionLevel = 1; _transaction.Commit(); break; } } enlistment.Done(); } finally { Cleanup(cnn); } } /////////////////////////////////////////////////////////////////////////// public void InDoubt(Enlistment enlistment) { CheckDisposed(); enlistment.Done(); } /////////////////////////////////////////////////////////////////////////// public void Prepare(PreparingEnlistment preparingEnlistment) { CheckDisposed(); if (_transaction.IsValid(false) == false) preparingEnlistment.ForceRollback(); else preparingEnlistment.Prepared(); } /////////////////////////////////////////////////////////////////////////// public void Rollback(Enlistment enlistment) { CheckDisposed(); SQLiteConnection cnn = null; try { while (true) { cnn = _transaction.Connection; if (cnn == null) break; lock (cnn._enlistmentSyncRoot) /* TRANSACTIONAL */ { // // NOTE: This check is necessary to detect the case where // the SQLiteConnection.Close() method changes the // connection associated with our transaction (i.e. // to avoid a race (condition) between grabbing the // Connection property and locking its enlistment). // if (!Object.ReferenceEquals(cnn, _transaction.Connection)) continue; cnn._enlistment = null; _transaction.Rollback(); break; } } enlistment.Done(); } finally { Cleanup(cnn); } } #endregion } } #endif // !PLATFORM_COMPACT_FRAMEWORK