/******************************************************** * 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! ********************************************************/ namespace System.Data.SQLite { using System; using System.Threading; /////////////////////////////////////////////////////////////////////////////////////////////////// /// /// SQLite implementation of DbTransaction that does support nested transactions. /// public sealed class SQLiteTransaction2 : SQLiteTransaction { /// /// The original transaction level for the associated connection /// when this transaction was created (i.e. begun). /// private int _beginLevel; /// /// The SAVEPOINT name for this transaction, if any. This will /// only be non-null if this transaction is a nested one. /// private string _savePointName; /////////////////////////////////////////////////////////////////////////////////////////////// /// /// Constructs the transaction object, binding it to the supplied connection /// /// The connection to open a transaction on /// TRUE to defer the writelock, or FALSE to lock immediately internal SQLiteTransaction2(SQLiteConnection connection, bool deferredLock) : base(connection, deferredLock) { // do nothing. } /////////////////////////////////////////////////////////////////////////////////////////////// #region IDisposable "Pattern" Members private bool disposed; private void CheckDisposed() /* throw */ { #if THROW_ON_DISPOSED if (disposed) throw new ObjectDisposedException(typeof(SQLiteTransaction2).Name); #endif } /////////////////////////////////////////////////////////////////////////////////////////////// /// /// Disposes the transaction. If it is currently active, any changes are rolled back. /// protected override void Dispose(bool disposing) { try { if (!disposed) { if (disposing) { //////////////////////////////////// // dispose managed resources here... //////////////////////////////////// if (IsValid(false)) { IssueRollback(false); } } ////////////////////////////////////// // release unmanaged resources here... ////////////////////////////////////// } } finally { base.Dispose(disposing); // // NOTE: Everything should be fully disposed at this point. // disposed = true; } } #endregion /////////////////////////////////////////////////////////////////////////////////////////////// /// /// Commits the current transaction. /// public override void Commit() { CheckDisposed(); SQLiteConnection.Check(_cnn); IsValid(true); if (_beginLevel == 0) { using (SQLiteCommand cmd = _cnn.CreateCommand()) { cmd.CommandText = "COMMIT;"; cmd.ExecuteNonQuery(); } _cnn._transactionLevel = 0; _cnn = null; } else { using (SQLiteCommand cmd = _cnn.CreateCommand()) { if (String.IsNullOrEmpty(_savePointName)) throw new SQLiteException("Cannot commit, unknown SAVEPOINT"); cmd.CommandText = String.Format( "RELEASE {0};", _savePointName); cmd.ExecuteNonQuery(); } _cnn._transactionLevel--; _cnn = null; } } /////////////////////////////////////////////////////////////////////////////////////////////// /// /// Attempts to start a transaction. An exception will be thrown if the transaction cannot /// be started for any reason. /// /// TRUE to defer the writelock, or FALSE to lock immediately protected override void Begin( bool deferredLock ) { int transactionLevel; if ((transactionLevel = _cnn._transactionLevel++) == 0) { try { using (SQLiteCommand cmd = _cnn.CreateCommand()) { if (!deferredLock) cmd.CommandText = "BEGIN IMMEDIATE;"; else cmd.CommandText = "BEGIN;"; cmd.ExecuteNonQuery(); _beginLevel = transactionLevel; } } catch (SQLiteException) { _cnn._transactionLevel--; _cnn = null; throw; } } else { try { using (SQLiteCommand cmd = _cnn.CreateCommand()) { _savePointName = GetSavePointName(); cmd.CommandText = String.Format( "SAVEPOINT {0};", _savePointName); cmd.ExecuteNonQuery(); _beginLevel = transactionLevel; } } catch (SQLiteException) { _cnn._transactionLevel--; _cnn = null; throw; } } } /////////////////////////////////////////////////////////////////////////////////////////////// /// /// Issue a ROLLBACK command against the database connection, /// optionally re-throwing any caught exception. /// /// /// Non-zero to re-throw caught exceptions. /// protected override void IssueRollback(bool throwError) { SQLiteConnection cnn = Interlocked.Exchange(ref _cnn, null); if (cnn != null) { if (_beginLevel == 0) { try { using (SQLiteCommand cmd = cnn.CreateCommand()) { cmd.CommandText = "ROLLBACK;"; cmd.ExecuteNonQuery(); } cnn._transactionLevel = 0; } catch { if (throwError) throw; } } else { try { using (SQLiteCommand cmd = cnn.CreateCommand()) { if (String.IsNullOrEmpty(_savePointName)) throw new SQLiteException("Cannot rollback, unknown SAVEPOINT"); cmd.CommandText = String.Format( "ROLLBACK TO {0};", _savePointName); cmd.ExecuteNonQuery(); } cnn._transactionLevel--; } catch { if (throwError) throw; } } } } /////////////////////////////////////////////////////////////////////////////////////////////// /// /// Constructs the name of a new savepoint for this transaction. It /// should only be called from the constructor of this class. /// /// /// The name of the new savepoint -OR- null if it cannot be constructed. /// private string GetSavePointName() { int sequence = ++_cnn._transactionSequence; return String.Format( "sqlite_dotnet_savepoint_{0}", sequence); } } }