Click here to Skip to main content
15,885,920 members
Articles / Programming Languages / C#

A Transactional Repository Implementation in .NET

Rate me:
Please Sign up or sign in to vote.
4.60/5 (7 votes)
26 Nov 2008CPOL4 min read 53.9K   668   37  
A Transactional Enterprise Caching Application Block implementation.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Transactions;
using Microsoft.Practices.EnterpriseLibrary.Caching;
using Microsoft.Practices.EnterpriseLibrary.Caching.Instrumentation;

namespace CachingAppBlock.Transactional
{
    /// <summary>
    /// This is main class for transactional caching
    /// </summary>
    public class TransactionalCacheManager : ICacheManager, IEnlistmentNotification
    {
        private readonly ICacheManager cacheManager;

        [ThreadStatic]
        private static ThreadSafeDictionary<string, CommandItem> transactionalRepository;

        public TransactionalCacheManager()
        {
            cacheManager = CacheFactory.GetCacheManager();
        }

        #region ICacheManager Members

        void ICacheManager.Add(string key, object value, CacheItemPriority scavengingPriority, ICacheItemRefreshAction refreshAction, params ICacheItemExpiration[] expirations)
        {
            throw new NotSupportedException("Transaction cache does not support custom priorities and refresh actions");
        }

        void ICacheManager.Add(string key, object value)
        {
            TransactionalLock.Lock(key);
            Enlist(key, Operation.Added, value, () => cacheManager.Add(key, value, CacheItemPriority.NotRemovable, null, null));
        }

        /// <summary>
        /// This method is not transactional
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        bool ICacheManager.Contains(string key)
        {
            if(Transaction.Current != null)
            {
                if(CurrentTransactionalRepository.Keys.Contains(key))
                {
                    return CurrentTransactionalRepository[key].Operation == Operation.Added;
                }
            }
            return cacheManager.Contains(key);
        }

        int ICacheManager.Count
        {
            get { return cacheManager.Count; }
        }

        void ICacheManager.Flush()
        {
            throw new NotImplementedException("Not implemented yet");
        }

        /// <summary>
        /// here we do return copy of data to protect consistency. if you need to updated your data, use Add method which will replace old object with the new one
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        object ICacheManager.GetData(string key)
        {
            TransactionalLock.Lock(key);
            if (Transaction.Current != null)
            {
                if (CurrentTransactionalRepository.ContainsKey(key))
                {
                    if (CurrentTransactionalRepository[key].Operation == Operation.Added)
                    {
                        return CurrentTransactionalRepository[key].Value;
                    }
                    else
                    {
                        return null;
                    }
                }

                return AttackOfTheClonesFactory.Clone(cacheManager.GetData(key));
            }
            return cacheManager.GetData(key);
            
        }

        void ICacheManager.Remove(string key)
        {
            TransactionalLock.Lock(key);
            Enlist(key, Operation.Removed, null, () => cacheManager.Remove(key));
        }

        object ICacheManager.this[string key]
        {
            get { return ((ICacheManager)this).GetData(key); }
        }

        #endregion


        #region IEnlistmentNotification Members

        void IEnlistmentNotification.Commit(Enlistment enlistment)
        {
            foreach(var commandItem in CurrentTransactionalRepository.Values)
            {
                commandItem.Command.Invoke();
            }
            enlistment.Done();
        }

        void IEnlistmentNotification.InDoubt(Enlistment enlistment)
        {
            enlistment.Done();
        }

        void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
        {
            preparingEnlistment.Prepared();
        }

        void IEnlistmentNotification.Rollback(Enlistment enlistment)
        {
            enlistment.Done();
        }

        #endregion

        void Enlist(string key, Operation operation, object value, Action command)
        {
            if(Transaction.Current != null)
            {
                Debug.Assert(Transaction.Current.TransactionInformation.Status == TransactionStatus.Active);
                //notify transaction manager that we want to participate in transaction
                Transaction.Current.EnlistVolatile(this, EnlistmentOptions.None);
                CurrentTransactionalRepository[key] = new CommandItem(operation, value, command);
            }
            else
            {
                command.Invoke();
            }
        }

        private ThreadSafeDictionary<string, CommandItem> CurrentTransactionalRepository
        {
            get
            {
                if(transactionalRepository == null)
                {
                    transactionalRepository = new ThreadSafeDictionary<string, CommandItem>();
                    //here we make sure that our transaction repository will be cleaned after transaction complition
                    Transaction.Current.TransactionCompleted +=
                        (object sender, TransactionEventArgs e) => CleanTransactionRepository(Thread.CurrentThread);
                }

                return transactionalRepository;
            }
        }

        private void CleanTransactionRepository(Thread t)
        {
            transactionalRepository = null;
        }


        private class CommandItem
        {
            
            public Operation Operation { get; private set; }
            public object Value { get; private set; }
            public Action Command { get; private set; }

            public CommandItem(Operation operation, object value, Action command)
            {
                Operation = operation;
                Value = value;
                Command = command;
            }
        }

        private enum Operation
        {
            Added,
            Removed
        }
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead bwin Interactive Entertainment AG
Austria Austria
The views expressed in my articles are mine and do not necessarily reflect the views of my employer.

if(youWantToContactMe)
{
SendMessage(string.Format("{0}@{1}.com", "liptchinski_vit", "yahoo"));
}

More info in my LinkedIn profile:
http://www.linkedin.com/in/vitaliyliptchinsky

Comments and Discussions