Click here to Skip to main content
15,887,333 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;
using System.Collections.Generic;
using System.Threading;

public interface IThreadSafeDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    /// <summary>
    /// Merge is similar to the SQL merge or upsert statement.  
    /// </summary>
    /// <param name="key">Key to lookup</param>
    /// <param name="newValue">New Value</param>
    void MergeSafe(TKey key, TValue newValue);

    /// <summary>
    /// This is a blind remove. Prevents the need to check for existence first.
    /// </summary>
    /// <param name="key">Key to Remove</param>
    void RemoveSafe(TKey key);
}

[Serializable]
public class ThreadSafeDictionary<TKey, TValue> : IThreadSafeDictionary<TKey, TValue>
{
    //This is the internal dictionary that we are wrapping
    IDictionary<TKey, TValue> dict = new Dictionary<TKey, TValue>();

    public ThreadSafeDictionary()
    {
        this.dictionaryLock = Locks.GetLockInstance(); //setup the lock
    }

    [NonSerialized]
    ReaderWriterLockSlim dictionaryLock;

    /// <summary>
    /// This is a blind remove. Prevents the need to check for existence first.
    /// </summary>
    /// <param name="key">Key to remove</param>
    public void RemoveSafe(TKey key)
    {
        using (new ReadLock(this.dictionaryLock))
        {
            if (this.dict.ContainsKey(key))
            {
                using (new WriteLock(this.dictionaryLock))
                {
                    this.dict.Remove(key);
                }
            }
        }
    }

    /// <summary>
    /// Merge does a blind remove, and then add.  Basically a blind Upsert.  
    /// </summary>
    /// <param name="key">Key to lookup</param>
    /// <param name="newValue">New Value</param>
    public void MergeSafe(TKey key, TValue newValue)
    {
        using (new WriteLock(this.dictionaryLock)) // take a writelock immediately since we will always be writing
        {
            if (this.dict.ContainsKey(key))
            {
                this.dict.Remove(key);
            }

            this.dict.Add(key, newValue);
        }
    }

    public virtual bool Remove(TKey key)
    {
        using (new WriteLock(this.dictionaryLock))
        {
            return this.dict.Remove(key);
        }
    }

    public virtual bool ContainsKey(TKey key)
    {
        using (new ReadLock(this.dictionaryLock))
        {
            return this.dict.ContainsKey(key);
        }
    }

    public virtual bool TryGetValue(TKey key, out TValue value)
    {
        using (new ReadLock(this.dictionaryLock))
        {
            return this.dict.TryGetValue(key, out value);
        }
    }

    public virtual TValue this[TKey key]
    {
        get
        {
            using (new ReadLock(this.dictionaryLock))
            {
                return this.dict[key];
            }
        }
        set
        {
            using (new WriteLock(this.dictionaryLock))
            {
                this.dict[key] = value;
            }
        }
    }

    public virtual ICollection<TKey> Keys
    {
        get
        {
            using (new ReadLock(this.dictionaryLock))
            {
                return new List<TKey>(this.dict.Keys);
            }
        }
    }

    public virtual ICollection<TValue> Values
    {
        get
        {
            using (new ReadLock(this.dictionaryLock))
            {
                return new List<TValue>(this.dict.Values);
            }
        }
    }

    public virtual void Clear()
    {
        using (new WriteLock(this.dictionaryLock))
        {
            this.dict.Clear();
        }
    }

    public virtual int Count
    {
        get
        {
            using (new ReadLock(this.dictionaryLock))
            {
                return this.dict.Count;
            }
        }
    }

    public virtual bool Contains(KeyValuePair<TKey, TValue> item)
    {
        using (new ReadLock(this.dictionaryLock))
        {
            return this.dict.Contains(item);
        }
    }

    public virtual void Add(KeyValuePair<TKey, TValue> item)
    {
        using (new WriteLock(this.dictionaryLock))
        {
            this.dict.Add(item);
        }
    }

    public virtual void Add(TKey key, TValue value)
    {
        using (new WriteLock(this.dictionaryLock))
        {
            this.dict.Add(key, value);
        }
    }

    public virtual bool Remove(KeyValuePair<TKey, TValue> item)
    {
        using (new WriteLock(this.dictionaryLock))
        {
            return this.dict.Remove(item);
        }
    }

    public virtual void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        using (new ReadLock(this.dictionaryLock))
        {
            this.dict.CopyTo(array, arrayIndex);
        }
    }

    public virtual bool IsReadOnly
    {
        get
        {
            using (new ReadLock(this.dictionaryLock))
            {
                return this.dict.IsReadOnly;
            }
        }
    }

    public virtual IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        throw new NotSupportedException("Cannot enumerate a threadsafe dictionary.  Instead, enumerate the keys or values collection");
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotSupportedException("Cannot enumerate a threadsafe dictionary.  Instead, enumerate the keys or values collection");
    }
}

public static class Locks
{
    public static void GetReadLock(ref ReaderWriterLockSlim locks)
    {
        bool lockAcquired = false;
        while (!lockAcquired)
            lockAcquired = locks.TryEnterUpgradeableReadLock(1);
    }

    public static void GetWriteLock(ref ReaderWriterLockSlim locks)
    {
        bool lockAcquired = false;
        while (!lockAcquired)
            lockAcquired = locks.TryEnterWriteLock(1);
    }

    public static void ReleaseReadLock(ref ReaderWriterLockSlim locks)
    {
        if (locks.IsUpgradeableReadLockHeld)
            locks.ExitUpgradeableReadLock();
    }

    public static void ReleaseWriteLock(ref ReaderWriterLockSlim locks)
    {
        if (locks.IsWriteLockHeld)
            locks.ExitWriteLock();
    }

    public static void ReleaseLock(ref ReaderWriterLockSlim locks)
    {
        ReleaseWriteLock(ref locks);
        ReleaseReadLock(ref locks);
    }

    public static ReaderWriterLockSlim GetLockInstance()
    {
        return new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
    }
}

public abstract class ObjectLock : IDisposable
{
    protected ReaderWriterLockSlim _Locks;

    public ObjectLock(ReaderWriterLockSlim locks)
    {
        _Locks = locks;
    }

    public abstract void Dispose();
}

public class ReadLock : ObjectLock
{
    public ReadLock(ReaderWriterLockSlim locks)
        : base(locks)
    {
        Locks.GetReadLock(ref this._Locks);
    }

    public override void Dispose()
    {
        Locks.ReleaseReadLock(ref this._Locks);
    }
}

public class WriteLock : ObjectLock
{
    public WriteLock(ReaderWriterLockSlim locks)
        : base(locks)
    {
        Locks.GetWriteLock(ref this._Locks);
    }

    public override void Dispose()
    {
        Locks.ReleaseWriteLock(ref this._Locks);
    }
}

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