This project has moved and is read-only. For the latest updates, please go here.

PersistentDictionary and Transactions

Jul 7, 2011 at 3:27 PM

Hello all,

we often need to combine some operations on PersistentDictionary<TKey,TValue> to one atomic operations. Let me explain this on a simple example:

var dictionary = PersitentDictionary<string,int>( "data" );
dictionary.Add( "a", 1 ); 
dictionary.Add( "b", 2 ); 
dictionary.Add( "sum", 3 ); 

These three operations make only sense as a whole. It would be useful to have a transaction object to combine these operations. This could look like this:

var dictionary = PersitentDictionary<string,int>( "data" );
using ( var transaction = dictionary.CreateTransaction() )
{
    dictionary.Add( "a", 1 ); 
    dictionary.Add( "b", 2 ); 
    dictionary.Add( "sum", 3 ); 
    transaction.Commit();
}

We have tried to implement a "PersistentTransaction" as follows:

namespace Microsoft.Isam.Esent.Collections.Generic
{
    /// <summary>
    /// Disposable wrapper for a transaction. The Dispose method
    /// will rollback the transaction if it isn't committed.
    /// </summary>
    public class PersistentTransaction<TKey, TValue> : IDisposable where TKey : IComparable<TKey>
    {
        private bool isDisposed;
        private LazyTransaction lazyTransaction;
        PersistentDictionaryCursorCache<TKey, TValue> cursors;
        PersistentDictionaryCursor<TKey, TValue> cursor;

        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <param name="cursor">Cursor object, where the transaction should be started for.</param>
        /// <exception cref="ArgumentNullException">The cursor is <c>null</c>.</exception>
        internal PersistentTransaction( PersistentDictionaryCursorCache<TKey, TValue> cursors )
        {
            if( cursors == null )
                throw new ArgumentNullException( "cursors" );
            this.cursors = cursors;
            cursor = cursors.GetCursor();
            lazyTransaction = cursor.BeginLazyTransaction();
            isDisposed = false;
        }

        /// <summary>
        /// Commit the transaction.
        /// </summary>
        public void Commit()
        {
            if( isDisposed )
                throw new ObjectDisposedException( this.ToString() );
            lazyTransaction.Commit();
        }
        #region IDisposable Members

        /// <summary>
        /// Rollback the transaction if not already committed.
        /// </summary>
        public void Dispose()
        {
            lazyTransaction.Dispose();
            cursors.FreeCursor( cursor );
        }

        #endregion
    }
}

We've also extended PersistentDictionary to create such a transaction:

namespace Microsoft.Isam.Esent.Collections.Generic
{
    /// <summary>
    /// 
    /// </summary>
    public sealed partial class PersistentDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDisposable
        where TKey : IComparable<TKey>
    {
        /// <summary>
        /// Creates and returns a PersistentTransaction object.
        /// </summary>
        /// <returns>The persistent transaction object.</returns>
        public PersistentTransaction<TKey,TValue> CreateTransaction()
        {
            return new PersistentTransaction<TKey, TValue>( cursors );
        }
    }
}

We've tested this extensions but it doesn't work. Probably you could find the error, but what do you think about this extension in your Product?

Thank you in advance!

Best regards,

Stefan

Jul 7, 2011 at 4:32 PM

Stefan,

Do not know why you need another layer on top of this for a transaction support, but PersistentDictionary
does transaction on its own, and I think if you need to do something different, you might have to modify the
original code instead of simple extension.

Best regards,
Paul. 

Jul 7, 2011 at 8:31 PM

Providing transactions will be a bit more complex because you will want to support having multiple transactions active in different threads. This means you need to have the BeginTransaction() method return a cursor which is then passed back into the add/remove calls. You would end up with code looking like this:

 

var dictionary = PersistentDictionary<string,int>( "data" );
using ( var transaction = dictionary.CreateTransaction() )
{
    dictionary.Add( transaction, "a", 1 );
    dictionary.Add( transaction, "b", 2 );
    dictionary.Add( transaction, "sum", 3 );
    dictionary.Remove( transaction, "old" );
    transaction.Commit();
}

Alternatively dictionary.CreateTransaction() could return an object that has Add/Remove methods:

var dictionary = PersistentDictionary<string,int>( "data" );
using ( var transaction = dictionary.CreateTransaction() )
{
    transaction.Add( "a", 1 );
    transaction.Add( "b", 2 );
    transaction.Add( "sum", 3 );
    transaction.Remove( "old" );
    transaction.Commit();
}

The primary goal of PersistentDictionary is ease of use and I couldn't see a foolproof way to add transactions -- in particular people will either not put them in a 'using' block or will forget to commit them.

You might be able to get some what you need by creating bulk insert/delete operations on the PersistentDictionary. That wouldn't let you both add and remove in the same transaction though.

May 9, 2012 at 5:16 AM
Edited May 9, 2012 at 5:19 AM

I think the best way to handle this would be to use System.Transactions

as per odp.net http://docs.oracle.com/cd/B28359_01/win.111/b28375/featADO20.htm#CJAEDJII

i.e. something like

 

using (TransactionScope scope = new TransactionScope())
{
    var dictionary = PersistentDictionary<string,int>( "data" , true);
    dictionary.Add( "a", 1 );
    dictionary.Add( "b", 2 );
    dictionary.Add( "sum", 3 );
    dictionary.Remove( "old" );
    scope.Complete();
}

 

Where the boolean in the constructor indicates whether or not to enlist in the transaction.

I would expect scope.Complete(); would flush to disk.

People who find transactions to hard to use can simply use the (string) constructor which would not enlist.

Apr 30, 2013 at 9:47 AM
Agreed. TransactionScope should really be used and supported by ManagedEsent.
Feb 3, 2015 at 10:03 AM
So no movement here? Is TransctionScope supported?