Index: System.Data.SQLite/SQLiteCommand.cs ================================================================== --- System.Data.SQLite/SQLiteCommand.cs +++ System.Data.SQLite/SQLiteCommand.cs @@ -51,10 +51,14 @@ internal List _statementList; /// /// Unprocessed SQL text that has not been executed /// internal string _remainingText; + /// + /// Transaction associated with this command + /// + private SQLiteTransaction _transaction; /// /// Constructs a new SQLiteCommand /// /// @@ -117,10 +121,11 @@ _isReaderOpen = false; _commandTimeout = 30; _parameterCollection = new SQLiteParameterCollection(this); _designTimeVisible = true; _updateRowSource = UpdateRowSource.FirstReturnedRecord; + _transaction = null; if (strSql != null) CommandText = strSql; if (cnn != null) @@ -350,17 +355,24 @@ /// The transaction associated with this command. SQLite only supports one transaction per connection, so this property forwards to the /// command's underlying connection. /// public new SQLiteTransaction Transaction { - get { return _cnn._activeTransaction; } + get { return _transaction; } set { if (_cnn != null) { - if (value != _cnn._activeTransaction && value != null) - throw new ArgumentOutOfRangeException("SQLiteTransaction", "Transaction is for a different connection than the one associated with this Command"); + if (_isReaderOpen) + throw new InvalidOperationException("Cannot set Transaction while a DataReader is active"); + + if (value != null) + { + if (value._cnn != _cnn) + throw new ArgumentException("Transaction is not associated with the command's connection"); + } + _transaction = value; } else if (value != null) throw new ArgumentOutOfRangeException("SQLiteTransaction", "Not associated with a connection"); } } Index: System.Data.SQLite/SQLiteConnection.cs ================================================================== --- System.Data.SQLite/SQLiteConnection.cs +++ System.Data.SQLite/SQLiteConnection.cs @@ -103,13 +103,17 @@ /// /// The connection string /// private string _connectionString; /// - /// One transaction allowed per connection please! + /// Nesting level of the transactions open on the connection + /// + internal int _transactionLevel; + /// + /// Whether or not the connection is enlisted in a distrubuted transaction /// - internal SQLiteTransaction _activeTransaction; + internal bool _enlisted; /// /// The base SQLite object to interop with /// internal SQLiteBase _sql; /// @@ -205,11 +209,12 @@ private void Initialize(string connectionString) { _sql = null; _connectionState = ConnectionState.Closed; _connectionString = ""; - _activeTransaction = null; + _transactionLevel = 0; + _enlisted = false; _commandList = new List(); if (connectionString != null) ConnectionString = connectionString; } @@ -347,15 +352,11 @@ public SQLiteTransaction BeginTransaction(bool deferredLock) { if (_connectionState != ConnectionState.Open) throw new InvalidOperationException(); - if (_activeTransaction != null) - throw new ArgumentException("Transaction already pending"); - - _activeTransaction = new SQLiteTransaction(this, deferredLock); - return _activeTransaction; + return new SQLiteTransaction(this, deferredLock); } /// /// Creates a new SQLiteTransaction if one isn't already active on the connection. /// @@ -563,10 +564,23 @@ ls.CopyTo(ar, 0); // Return the array of key-value pairs return ar; } + +#if !PLATFORM_COMPACTFRAMEWORK + public override void EnlistTransaction(System.Transactions.Transaction transaction) + { + if (_transactionLevel > 0 && transaction != null) + throw new ArgumentException("Unable to enlist in transaction, a local transaction already exists"); + + if (_enlisted == true) + throw new ArgumentException("Already enlisted in a transaction"); + + transaction.EnlistVolatile(new SQLiteEnlistment(this), System.Transactions.EnlistmentOptions.None); + } +#endif /// /// Looks for a key in the array of key/values of the parameter string. If not found, return the specified default value /// /// The Key/Value pair array to look in @@ -627,10 +641,15 @@ _sql.Execute(String.Format(CultureInfo.InvariantCulture, "PRAGMA Synchronous={0}", FindKey(opts, "Synchronous", "Normal"))); _sql.Execute(String.Format(CultureInfo.InvariantCulture, "PRAGMA Cache_Size={0}", FindKey(opts, "Cache Size", "2000"))); if (String.Compare(":MEMORY:", strFile, true, CultureInfo.InvariantCulture) == 0) _sql.Execute(String.Format(CultureInfo.InvariantCulture, "PRAGMA Page_Size={0}", FindKey(opts, "Page Size", "1024"))); + +#if !PLATFORM_COMPACTFRAMEWORK + if (FindKey(opts, "Enlist", "Y").ToUpper()[0] == 'Y' && Transactions.Transaction.Current != null) + EnlistTransaction(Transactions.Transaction.Current); +#endif } catch (SQLiteException) { OnStateChange(ConnectionState.Broken); throw; ADDED System.Data.SQLite/SQLiteEnlistment.cs Index: System.Data.SQLite/SQLiteEnlistment.cs ================================================================== --- /dev/null +++ System.Data.SQLite/SQLiteEnlistment.cs @@ -0,0 +1,81 @@ +/******************************************************** + * 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; + using System.Data; + using System.Data.Common; + using System.Transactions; + + internal class SQLiteEnlistment : IEnlistmentNotification + { + private SQLiteTransaction _transaction; + + internal SQLiteEnlistment(SQLiteConnection cnn) + { + _transaction = cnn.BeginTransaction(); + _transaction.Connection._enlisted = true; + } + + #region IEnlistmentNotification Members + + public void Commit(Enlistment enlistment) + { + try + { + _transaction.IsValid(); + _transaction.Connection._transactionLevel = 1; + _transaction.Commit(); + + enlistment.Done(); + } + finally + { + _transaction.Connection._enlisted = false; + _transaction = null; + } + } + + public void InDoubt(Enlistment enlistment) + { + enlistment.Done(); + } + + public void Prepare(PreparingEnlistment preparingEnlistment) + { + try + { + _transaction.IsValid(); + } + catch(Exception e) + { + preparingEnlistment.ForceRollback(e); + return; + } + preparingEnlistment.Done(); + } + + public void Rollback(Enlistment enlistment) + { + _transaction.Connection._enlisted = false; + try + { + _transaction.Rollback(); + enlistment.Done(); + } + finally + { + _transaction = null; + } + } + + #endregion + } +} +#endif // !PLATFORM_COMPACT_FRAMEWORK Index: System.Data.SQLite/SQLiteTransaction.cs ================================================================== --- System.Data.SQLite/SQLiteTransaction.cs +++ System.Data.SQLite/SQLiteTransaction.cs @@ -26,44 +26,51 @@ /// /// The connection to open a transaction on /// TRUE to defer the writelock, or FALSE to lock immediately internal SQLiteTransaction(SQLiteConnection cnn, bool deferredLock) { - try - { - if (!deferredLock) - cnn._sql.Execute("BEGIN IMMEDIATE"); - else - cnn._sql.Execute("BEGIN"); - - _cnn = cnn; - } - catch (SQLiteException) - { - BaseDispose(); - throw; + _cnn = cnn; + if (_cnn._transactionLevel++ == 0) + { + try + { + if (!deferredLock) + cnn._sql.Execute("BEGIN IMMEDIATE"); + else + cnn._sql.Execute("BEGIN"); + } + catch (SQLiteException) + { + _cnn._transactionLevel--; + _cnn = null; + throw; + } } } /// /// Commits the current transaction. /// public override void Commit() { - if (_cnn == null) - throw new ArgumentNullException(); - - try - { - _cnn._sql.Execute("COMMIT"); - } - catch (SQLiteException) - { - BaseDispose(); - throw; - } - BaseDispose(); + IsValid(); + + if (--_cnn._transactionLevel == 0) + { + try + { + _cnn._sql.Execute("COMMIT"); + } + finally + { + _cnn = null; + } + } + else + { + _cnn = null; + } } /// /// Returns the underlying connection to which this transaction applies. /// @@ -86,12 +93,10 @@ protected override void Dispose(bool disposing) { if (_cnn != null) Rollback(); - _cnn = null; - base.Dispose(disposing); } /// /// Gets the isolation level of the transaction. SQLite does not support isolation levels, so this always returns Unspecified. @@ -104,27 +109,31 @@ /// /// Rolls back the active transaction. /// public override void Rollback() { - if (_cnn == null) - throw new ArgumentNullException(); + IsValid(); try { _cnn._sql.Execute("ROLLBACK"); + _cnn._transactionLevel = 0; } - catch (SQLiteException) + finally { - BaseDispose(); - throw; + _cnn = null; } - BaseDispose(); } - private void BaseDispose() + internal void IsValid() { - _cnn._activeTransaction = null; - _cnn = null; + if (_cnn == null) + throw new ArgumentNullException("No connection associated with this transaction"); + + if (_cnn._transactionLevel == 0) + { + _cnn = null; + throw new SQLiteException((int)SQLiteErrorCode.Misuse, "No transactions active on this connection"); + } } } } Index: System.Data.SQLite/System.Data.SQLite ================================================================== --- System.Data.SQLite/System.Data.SQLite +++ System.Data.SQLite/System.Data.SQLite @@ -42,10 +42,11 @@ 512 + @@ -64,10 +65,11 @@ Component +