File: System\Data\Services\Caching\MetadataCache.cs
Project: ndp\fx\src\DataWeb\Server\System.Data.Services.csproj (System.Data.Services)
//---------------------------------------------------------------------
// <copyright file="MetadataCache.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <summary>
//      Provides a class to cache metadata information.
// </summary>
//
// @owner  Microsoft
//---------------------------------------------------------------------
 
namespace System.Data.Services.Caching
{
    using System;
    using System.Collections.Generic;
    using System.Data.EntityClient;
    using System.Data.Objects;
    using System.Diagnostics;
    using System.Threading;
 
    /// <summary>
    /// Use this class to cache metadata through MetadataCacheItem instances.
    /// </summary>
    internal static class MetadataCache
    {
        /// <summary>AppDomain-wide cache for metadata items.</summary>
        private static Dictionary<MetadataCacheKey, MetadataCacheItem> cache = new Dictionary<MetadataCacheKey, MetadataCacheItem>(new MetadataCacheKey.Comparer());
 
        /// <summary>Reader/writer lock for AppDomain <see cref="cache"/>.</summary>
        private static ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
 
        /// <summary>Adds a new cache item, and returns the item that is put in the cache.</summary>
        /// <param name="serviceType">Type of service with metadata being cached.</param>
        /// <param name="dataContextInstance">
        /// Data context instance being cached, possibly segmenting the cache 
        /// space for <paramref name="serviceType"/>.
        /// </param>
        /// <param name="item">Item being added.</param>
        /// <returns>The item being put in the cache (possibly an existing one).</returns>
        /// <remarks>This method is thread-safe but not re-entrant.</remarks>
        internal static MetadataCacheItem AddCacheItem(Type serviceType, object dataContextInstance, MetadataCacheItem item)
        {
            Debug.Assert(serviceType != null, "serviceType != null");
            Debug.Assert(dataContextInstance != null, "dataContextInstance != null");
            Debug.Assert(item != null, "item != null");
 
            MetadataCacheKey key = new MetadataCacheKey(serviceType, dataContextInstance as ObjectContext);
            MetadataCacheItem result;
            cacheLock.EnterWriteLock();
            try
            {
                // If another thread beat the current thread, we return the
                // previously created item, which has a higher chance of
                // having survived a garbage collection already.
                if (!cache.TryGetValue(key, out result))
                {
                    cache.Add(key, item);
                    result = item;
                }
            }
            finally
            {
                cacheLock.ExitWriteLock();
            }
 
            Debug.Assert(result != null, "result != null -- a null item is never returned.");
            Debug.Assert(
                result == TryLookup(serviceType, dataContextInstance),
                "result == TryLookup(serviceType, dataContextInstance) -- instance from cache is being returned.");
 
            return result;
        }
 
        /// <summary>Tries to look up metadata for the specifed service type and context instance.</summary>
        /// <param name="serviceType">Type of service with metadata being cached.</param>
        /// <param name="dataContextInstance">
        /// Data context instance being cached, possibly segmenting the cache 
        /// space for <paramref name="serviceType"/>.
        /// </param>
        /// <returns>The cached metadata item, if one exists.</returns>
        /// <remarks>This method is thread-safe but not re-entrant.</remarks>
        internal static MetadataCacheItem TryLookup(Type serviceType, object dataContextInstance)
        {
            Debug.Assert(serviceType != null, "serviceType != null");
            Debug.Assert(dataContextInstance != null, "dataContextInstance != null");
 
            MetadataCacheKey key = new MetadataCacheKey(serviceType, dataContextInstance as ObjectContext);
            MetadataCacheItem result;
            cacheLock.EnterReadLock();
            try
            {
                cache.TryGetValue(key, out result);
            }
            finally
            {
                cacheLock.ExitReadLock();
            }
 
            return result;
        }
 
        /// <summary>This type is used as the key in the metadata cache.</summary>
        internal struct MetadataCacheKey
        {
            /// <summary>Connection string used to segment service type.</summary>
            private readonly string dataContextConnection;
 
            /// <summary>Hash code for this instance.</summary>
            private readonly int hashCode;
 
            /// <summary>Service type.</summary>
            private readonly Type serviceType;
 
            /// <summary>Initializes a new MetadataCacheKey instance.</summary>
            /// <param name='serviceType'>Service type for key.</param>
            /// <param name='dataContextInstance'>Data context instace for key, possibly null.</param>
            internal MetadataCacheKey(Type serviceType, ObjectContext dataContextInstance)
            {
                Debug.Assert(serviceType != null, "serviceType != null");
                this.serviceType = serviceType;
                this.dataContextConnection = null;
                this.hashCode = this.serviceType.GetHashCode();
                
                if (dataContextInstance != null)
                {
                    EntityConnection connection = dataContextInstance.Connection as EntityConnection;
                    if (connection != null)
                    {
                        this.dataContextConnection = new EntityConnectionStringBuilder(connection.ConnectionString).Metadata;
                        this.hashCode ^= this.dataContextConnection.GetHashCode();
                    }
                }
            }
 
            /// <summary>Comparer for metadata cache keys.</summary>
            internal class Comparer : IEqualityComparer<MetadataCacheKey>
            {
                /// <summary>Compares the specified keys.</summary>
                /// <param name="x">First key.</param>
                /// <param name="y">Second key.</param>
                /// <returns>true if <paramref name="x"/> equals <paramref name="y"/>, false otherwise.</returns>
                public bool Equals(MetadataCacheKey x, MetadataCacheKey y)
                {
                    return x.dataContextConnection == y.dataContextConnection && x.serviceType == y.serviceType;
                }
 
                /// <summary>Gets the hash code for the object.</summary>
                /// <param name="obj">Object.</param>
                /// <returns>The hash code for this key.</returns>
                public int GetHashCode(MetadataCacheKey obj)
                {
                    return obj.hashCode;
                }
            }
        }
    }
}