Robin Hames

Hints, tricks and tips relating to MS SQL Server and .NET
posts - 14 , comments - 45 , trackbacks - 0

Wednesday, January 26, 2011

Data Caching Architecture: Using the ASP.NET Cache to cache data in a Model or Business Object layer, without a dependency on System.Web in the layer - Part Two. Adding Cache Dependency Support

Adding Cache Dependency Support
This second part of my article on adding cache support to applications will extend the sample application developed in part one to add support for cache dependencies such as the SqlCacheDependency.
Part One of this article is at:
ICacheDependency Interface
We will need to pass cache dependencies to the Cache Provider implementation, so the first step is to create an empty interface called ICacheDependency in the CacheSample.Caching project:
namespace CacheSample.Caching
{
    public interface ICacheDependency
    {
    }
}
 
We can then add overloaded definitions for the Fetch methods in the ICacheProvider<T> interface that will take instances of ICacheDependency. As there could be more than one dependency, the overloads will have a parameter of type IEnumerable<ICacheDependency>, as shown below:
public interface ICacheProvider<T> : ICacheProvider
{
    T Fetch(string key, Func<T> retrieveData,
DateTime? absoluteExpiry, TimeSpan? relativeExpiry);
    T Fetch(string key, Func<T> retrieveData,
DateTime? absoluteExpiry, TimeSpan? relativeExpiry,
IEnumerable<ICacheDependency> cacheDependencies);
 
    IEnumerable<T> Fetch(string key, Func<IEnumerable<T>> retrieveData,
DateTime? absoluteExpiry, TimeSpan? relativeExpiry);
    IEnumerable<T> Fetch(string key, Func<IEnumerable<T>> retrieveData,
DateTime? absoluteExpiry, TimeSpan? relativeExpiry,
IEnumerable<ICacheDependency> cacheDependencies);
}
 
Each type of cache dependency will have its own set of parameters, and therefore will require its own interface, which will then be implemented by the cache provider. For this sample I will implement a SQL cache dependency and a Key cache dependency (which gives a dependency on other cache items, identified by a set of cache keys). For these two types of cache dependency, there will be two interfaces, both of which will inherit from ICacheDependency:
namespace CacheSample.Caching
{
    public interface ISqlCacheDependency : ICacheDependency
    {
        string DatabaseConnectionName { get; set; }
        string TableName { get; set; }
        System.Data.SqlClient.SqlCommand SqlCommand { get; set; }
    }
}
 
 
namespace CacheSample.Caching
{
    public interface IKeyCacheDependency : ICacheDependency
    {
        string[] Keys { get; set; }
    }
}
 
Dependency Injection of Cache Dependency Implementations
We will need to configure the implementations of each cache dependency interface in the application configuration file so that dependency injection may be used to create instances of the dependencies at runtime. For this we will need an additional custom configuration element with properties for the interface being implemented and the type of the class that implements the interface. As there may be several cache dependency implementations there will also be an associated configuration collection.
CacheDependency Custom Configurations
In the CacheSample.Caching project, create a class called CacheDependencyConfigurationElement, in the Configuration folder. The C# code for this class is shown below:
using System;
using System.Configuration;
 
namespace CacheSample.Caching.Configuration
{
    internal class CacheDependencyConfigurationElement : ConfigurationElement
    {
        [ConfigurationProperty("interface", IsRequired = true)]
        public string CacheDependencyInterface
        {
            get
            {
                return (string)this["interface"];
            }
        }
 
        [ConfigurationProperty("type", IsRequired = true)]
        public string CacheDependencyType
        {
            get
            {
                return (string)this["type"];
            }
        }
 
    }
}
 
Create a second class called CacheDependencyConfigurationElementCollection, with the following C# code:
using System;
using System.Configuration;
 
namespace CacheSample.Caching.Configuration
{
    internal class CacheDependencyConfigurationElementCollection :
ConfigurationElementCollection
    {
        protected override string ElementName
        {
            get
            {
                return "cacheDependency";
            }
        }
 
        protected override ConfigurationElement CreateNewElement()
        {
            return new CacheDependencyConfigurationElement();
        }
 
        public override ConfigurationElementCollectionType CollectionType
        {
            get
            {
                return ConfigurationElementCollectionType.BasicMap;
            }
        }
 
        protected override object GetElementKey(ConfigurationElement element)
        {
            return ((CacheDependencyConfigurationElement)element).
CacheDependencyInterface;
        }
 
        public CacheDependencyConfigurationElement this[int index]
        {
            get
            {
                return (CacheDependencyConfigurationElement)BaseGet(index);
            }
        }
 
        public new CacheDependencyConfigurationElement this[string Interface]
        {
            get
            {
                return (CacheDependencyConfigurationElement)BaseGet(Interface);
            }
        }
    }
}
 
Finally to complete the custom configuration code, add the following property to the CacheProviderConfigurationSection class:
[ConfigurationProperty("cacheDependencies")]
public CacheDependencyConfigurationElementCollection CacheDependencies
{
    get
    {
        return (CacheDependencyConfigurationElementCollection)
base["cacheDependencies"];
    }
}
 
Cache Dependency Factory
Instances of the cache dependency implementations will be retrieved using a factory class, which will function in a similar manner to the Cache Provider Factory. However, there is one key difference between the Cache Dependency Factory and the Cache Provider Factory. Whereas instances of Cache Providers can be reused, we will have to create a new Cache Dependency instance each time the dependency is required, as it will be passed directly into the cache system. We do not want to have to make an expensive call to Activator.CreateInstance() every time, so we need an efficient method of creating our cache dependency instances at runtime. My solution is to provide a CacheDependencyCreator generic class, which has a Create() method that will create a new instance of the relevant ICacheDependency generic type by called new T(). The first time a particular cache dependency is requested, the factory class will create the relevant creator class, and store this in a static dictionary for use in future requests.
Cache Dependency Creator class
Create a class called CacheDependencyCreator in the CacheSample.Caching project, under the CacheDependency folder. The C# code for the CacheDependencyCreator is shown below:
using System;
 
namespace CacheSample.Caching
{
    ///<summary>
    /// Provide an efficient mechanism for creating new instances
    /// of ICacheDependency implementations at runtime
    ///</summary>
    internal class CacheDependencyCreator
    {
        ///<summary>
        /// Create a new instance of the relevant ICacheDependency implementation
        ///</summary>
        ///<returns></returns>
        public virtual ICacheDependency Create()
        {
            throw new NotImplementedException();
        }
    }
 
    ///<summary>
    /// Provide an efficient mechanism for creating new instances
    /// of ICacheDependency implementations at runtime
    ///</summary>
    ///<typeparam name="T">The ICacheDependency type</typeparam>
    internal class CacheDependencyCreator<T> : CacheDependencyCreator
        where T : ICacheDependency, new()
    {
        ///<summary>
        /// Create a new instance of the relevant ICacheDependency implementation
        ///</summary>
        ///<returns></returns>
        public override ICacheDependency Create()
        {
            return new T();
        }
    }
}
 
Note that there is a non-generic parent class for CacheDependencyCreator that will be used as a type for the static dictionary in the Cache Dependency Facotry.
Cache Dependency Factory class
When a request for a cache dependency instance is made, the factory will first check for the relevant CacheDependencyCreator<> object in the dictionary. If it is present, it will call the Create() method on the relevant creator object. If the dictionary does not contain an instance of the relevant creator, one will be created using Activator.CreateInstance().
The C# code for the Cache Dependency Factory class is shown below:
using System;
using System.Collections.Generic;
using System.Linq;
 
using CacheSample.Caching.Configuration;
 
namespace CacheSample.Caching
{
    public static class CacheDependencyFactory
    {
        private static Dictionary<Type, CacheDependencyCreator>
cacheDependencyCreators = new Dictionary<Type,
CacheDependencyCreator>();
        private static object syncRoot = new object();
 
        ///<summary>
        /// Create a new cache dependency instance for the cache dependency
        /// interface specified by <typeparamref name="T"/>
        ///</summary>
        ///<typeparam name="T">The ICacheDependency interface being
 /// requested</typeparam>
        ///<returns>An instance of the implementation of the cache dependency
 /// interface</returns>
        public static T CreateCacheDependency<T>()
            where T : ICacheDependency
        {
            ICacheDependency cacheDependency = null;
            Type typeOfT = typeof(T);
 
            lock (syncRoot)
            {
                if (cacheDependencyCreators.ContainsKey(typeOfT))
                    // The cacheDependencyCreators dictionary already
                    // has a creator for this type of cache dependency,
                    // so create the instance by calling its Create() method
                    cacheDependency = ((CacheDependencyCreator)
cacheDependencyCreators[typeOfT]).Create();
                else
                {
                    // Get the cache dependeny configuration for the cache dependency
      // interface type
                    var cacheDependencyConfiguration =
CacheProviderConfigurationSection.Current.
CacheDependencies[typeOfT.Name];
                    if (cacheDependencyConfiguration != null)
                    {
                        // Get the type for the implementation of the specified cache
   // dependency interface
                        string strCacheDependencyImplementationType =
cacheDependencyConfiguration.CacheDependencyType;
                        Type typeOfCacheDependencyImplementation =
Type.GetType(strCacheDependencyImplementationType);
                        // Get the Type reference for the CacheDependencyCreator
   // generic class
                        Type typeOfCacheDependencyCreator =
typeof(CacheDependencyCreator<>);
                        if (typeOfCacheDependencyImplementation != null
&& typeOfCacheDependencyCreator != null)
                        {
                            // Get the type reference for
// CacheDependencyCreator<cache interface implementation>
                            Type typeOfCacheDependencyCreatorForImplementationOfT =
typeOfCacheDependencyCreator.MakeGenericType(
new Type[]
{ typeOfCacheDependencyImplementation });
                            if (typeOfCacheDependencyCreatorForImplementationOfT
!= null)
                            {
                                // Create the CacheDependencyCreator<
    // cache interface implementation> instance
                                var cacheDependencyCreator =
(CacheDependencyCreator)Activator.CreateInstance(
typeOfCacheDependencyCreatorForImplementationOfT);
                                // Add the CacheDependencyCreator<cache interface
    // implementation> instance to the dictionary
                                cacheDependencyCreators.Add(
typeOfT, cacheDependencyCreator);
                                // Create the Cache Dependency instance
                                cacheDependency = cacheDependencyCreator.Create();
                            }
                        }
                    }
 
                }
            }
 
            return (T)cacheDependency;
        }
    }
}
 
Implementing ICacheDependency interfaces
My original intention when implementing the ICacheDependency interfaces was to create classes within the CacheSample.CacheProvider that extend the System.Web.Caching.CacheDependency and System.Web.Caching.SqlCacheDependency classes. However the System.Web.Caching.SqlCacheDependency class is sealed, so my implementation cannot inherit from this.
My alternative was to create an interface in the CacheSample.CacheProvider project called IAspNetCacheDependency. This interface contains a single method that returns an instance of System.Web.Caching.CacheDependency. My implementations of the ICacheDependency interfaces will also implement this Asp.Net specific interface, so that the Cache Provider implementation can use this method to create the relevant System.Web.Caching.CacheDependency object.
The C# code for the IAspNetCacheDependency interface is shown below:
 using System.Web.Caching;
 
namespace CacheSample.CacheProvider
{
    public interface IAspNetCacheDependency
    {
        CacheDependency CreateAspNetCacheDependency();
    }
}
 
AspNetSqlCacheDependency implementation of ISqlCacheDependency
Add a class called AspNetSqlCacheDependency to the CacheSample.CacheProvider project to implement both the ISqlCacheDependency and IAspNetCacheDependency interfaces. For ISqlCacheDependency it is simply a matter of implementing the relevant properties. For the IAspNetCacheDependency interface, the CreateAspNetCacheDependency() method is implemented to return an instance of System.Web.Caching.SqlCacheDependency with the appropriate properties set. The C# code for AspNetCacheDependency is shown below:
using System.Web.Caching;
 
using CacheSample.Caching;
 
namespace CacheSample.CacheProvider
{
    public class AspNetSqlCacheDependency :
ISqlCacheDependency, IAspNetCacheDependency
    {
        #region ISqlCacheDependency Members
 
        public string DatabaseConnectionName
        {
            get;
            set;
        }
 
        public string TableName
        {
            get;
            set;
        }
 
        public System.Data.SqlClient.SqlCommand SqlCommand
        {
            get;
            set;
        }
 
        #endregion
 
        #region IAspNetCacheDependency Members
 
        public System.Web.Caching.CacheDependency CreateAspNetCacheDependency()
        {
            if (SqlCommand != null)
                return new SqlCacheDependency(SqlCommand);
            else
                return new SqlCacheDependency(DatabaseConnectionName, TableName);
        }
 
        #endregion
 
    }
}
 
AspNetKeyCacheDependency implementation of IKeyCacheDependency
A second class called AspNetKeyCacheDependency is created in the CacheSample.CacheProvider project to implement the IKeyCacheDependency interface, using the C# code shown below:
using System.Web.Caching;
 
using CacheSample.Caching;
 
namespace CacheSample.CacheProvider
{
    public class AspNetKeyCacheDependency :
IKeyCacheDependency, IAspNetCacheDependency
    {
        #region IKeyCacheDependency Members
 
        public string[] Keys
        {
            get;
            set;
        }
 
        #endregion
 
        #region IAspNetCacheDependency Members
 
        public CacheDependency CreateAspNetCacheDependency()
        {
            return new CacheDependency(null, Keys);
        }
 
        #endregion
    }
}
 
Implementing the ICacheProvider.Fetch() method overloads
The helper method FetchAndCache<U>() in the AspNetCacheProvider class is extended to take an additional IEnumerable<ICacheDependency> parameter. The Fetch method implementations then call this helper method, as shown below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Caching;
 
using CacheSample.Caching;
 
namespace CacheSample.CacheProvider
{
    public class AspNetCacheProvider<T> : ICacheProvider<T>
    {
        #region ICacheProvider<T> Members
 
        public T Fetch(string key, Func<T> retrieveData,
DateTime? absoluteExpiry, TimeSpan? relativeExpiry)
        {
            return FetchAndCache<T>(key, retrieveData, absoluteExpiry,
relativeExpiry, null);
        }
 
        public T Fetch(string key, Func<T> retrieveData,
DateTime? absoluteExpiry, TimeSpan? relativeExpiry,
IEnumerable<ICacheDependency> cacheDependencies)
        {
            return FetchAndCache<T>(key, retrieveData,
absoluteExpiry, relativeExpiry, cacheDependencies);
        }
 
        public IEnumerable<T> Fetch(string key, Func<IEnumerable<T>> retrieveData,
DateTime? absoluteExpiry, TimeSpan? relativeExpiry)
        {
            return FetchAndCache<IEnumerable<T>>(key, retrieveData,
absoluteExpiry, relativeExpiry, null);
        }
 
        public IEnumerable<T> Fetch(string key, Func<IEnumerable<T>> retrieveData,
DateTime? absoluteExpiry, TimeSpan? relativeExpiry,
IEnumerable<ICacheDependency> cacheDependencies)
        {
            return FetchAndCache<IEnumerable<T>>(key, retrieveData,
absoluteExpiry, relativeExpiry, cacheDependencies);
        }
 
        #endregion
 
        #region Helper Methods
 
        private U FetchAndCache<U>(string key, Func<U> retrieveData,
DateTime? absoluteExpiry, TimeSpan? relativeExpiry,
IEnumerable<ICacheDependency> cacheDependencies)
        {
            U value;
            if (!TryGetValue<U>(key, out value))
            {
                value = retrieveData();
                if (!absoluteExpiry.HasValue)
                    absoluteExpiry = Cache.NoAbsoluteExpiration;
 
                if (!relativeExpiry.HasValue)
                    relativeExpiry = Cache.NoSlidingExpiration;
 
                CacheDependency aspNetCacheDependencies = null;
 
                if (cacheDependencies != null)
                {
                    if (cacheDependencies.Count() == 1)
                        // We know that the implementations of ICacheDependency
                        // will also implement IAspNetCacheDependency
                        // so we can use a cast here and call the
                        // CreateAspNetCacheDependency() method
                        aspNetCacheDependencies =
((IAspNetCacheDependency)cacheDependencies
.ElementAt(0)).CreateAspNetCacheDependency();
                    else if(cacheDependencies.Count() > 1)
                    {
                        AggregateCacheDependency aggregateCacheDependency =
new AggregateCacheDependency();
                        foreach(ICacheDependency cacheDependency in cacheDependencies)
                        {
                            // We know that the implementations of ICacheDependency
                            // will also implement IAspNetCacheDependency
                            // so we can use a cast here and call the
                            // CreateAspNetCacheDependency() method
                            aggregateCacheDependency
.Add(((IAspNetCacheDependency)cacheDependency)
.CreateAspNetCacheDependency());
                        }
                        aspNetCacheDependencies = aggregateCacheDependency;
                    }
                }
 
                HttpContext.Current.Cache.Insert(key, value,
aspNetCacheDependencies,
absoluteExpiry.Value, relativeExpiry.Value);
 
            }
            return value;
        }
 
        private bool TryGetValue<U>(string key, out U value)
        {
            object cachedValue = HttpContext.Current.Cache.Get(key);
            if (cachedValue == null)
            {
                value = default(U);
                return false;
            }
            else
            {
                try
                {
                    value = (U)cachedValue;
                    return true;
                }
                catch
                {
                    value = default(U);
                    return false;
                }
            }
        }
       
        #endregion
 
    }
}
 
If any cache dependencies are passed to the FetchAndCache<U> method, they are cast to the IAspNetCacheDependency interface, then the CreateAspNetCacheDependency() method is called to create an instance of the relevant System.Web.Caching.CacheDependency class to include in the HttpContext.Current.Cache.Insert() method call.
Specifying the ICacheDependency implementations in Web.Config
We need to add a <cacheDependencies> collection to the <cacheProvider> element in the application configuration file, and then add individual <cacheDependency> elements to this collection for each of the ICacheDependency implementations, as show below:
 <cacheProvider
type="CacheSample.CacheProvider.AspNetCacheProvider`1,
CacheSample.CacheProvider, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null">
    <cacheDependencies>
      <cacheDependencyinterface="IKeyCacheDependency"
                       type="CacheSample.CacheProvider.AspNetKeyCacheDependency,
CacheSample.CacheProvider, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null" />
      <cacheDependencyinterface="ISqlCacheDependency"
                       type="CacheSample.CacheProvider.AspNetSqlCacheDependency,
CacheSample.CacheProvider, Version=1.0.0.0,
Culture=neutral, PublicKeyToken=null" />
    </cacheDependencies
 </cacheProvider>
 
This will allow the CacheDependencyFactory class to use dependency injection to get the implementations of the relevant interfaces at runtime.
Using the ISqlCacheDependency in the Sale.GetSales() method
We can now modify the Sale.GetSales() method to include a cache dependency on the Sale table in the database.
First we create a cache dependency instance of the ISqlCacheDependency interface, using the factory, and set its properties, as shown below:
var sqlCacheDependency = CacheSample.Caching.CacheDependencyFactory
.CreateCacheDependency<CacheSample.Caching.ISqlCacheDependency>();
sqlCacheDependency.DatabaseConnectionName = "CacheSample";
sqlCacheDependency.TableName = "Sale";
 
We can then pass this cache dependency instance in the call to the CacheProvider Fetch method. The full code for GetSales is shown below:
public static IEnumerable<Sale> GetSales(int? highestDayCount)
{
    string cacheKey = string.Format(
"CacheSample.BusinessObjects.GetSalesWithCache_SqlCommandDependency({0})",
highestDayCount);
    string connectionStr = System.Configuration.ConfigurationManager
.ConnectionStrings["CacheSample"].ConnectionString;
 
    var sqlCacheDependency = CacheSample.Caching.CacheDependencyFactory
.CreateCacheDependency<CacheSample.Caching.ISqlCacheDependency>();
    sqlCacheDependency.DatabaseConnectionName = "CacheSample";
    sqlCacheDependency.TableName = "Sale";
   
    CacheSample.Caching.ICacheDependency[] cacheDependencies =
new Caching.ICacheDependency[] { sqlCacheDependency };
 
    return CacheSample.Caching.CacheProviderFactory
.GetCacheProvider<Sale>().Fetch(cacheKey,
        delegate()
        {
            List<Sale> sales = new List<Sale>();
 
            SqlParameter highestDayCountParameter =
new SqlParameter("@HighestDayCount", SqlDbType.SmallInt);
            if (highestDayCount.HasValue)
                highestDayCountParameter.Value = highestDayCount;
            else
                highestDayCountParameter.Value = DBNull.Value;
 
 
            using (SqlConnection sqlConn = new SqlConnection(connectionStr))
            using (SqlCommand sqlCmd = sqlConn.CreateCommand())
            {
                sqlCmd.CommandText = "spGetRunningTotals";
                sqlCmd.CommandType = CommandType.StoredProcedure;
                sqlCmd.Parameters.Add(highestDayCountParameter);
 
                sqlConn.Open();
 
                using (SqlDataReader dr = sqlCmd.ExecuteReader())
                {
                    while (dr.Read())
                    {
                        Sale newSale = new Sale();
                        newSale.DayCount = dr.GetInt16(0);
                        newSale.Sales = dr.GetDecimal(1);
                        newSale.RunningTotal = dr.GetDecimal(2);
 
                        sales.Add(newSale);
                    }
                }
            }
 
            return sales;
        },
        null,
        null,
        cacheDependencies
        );
}
 
 
As we only provided a Fetch method in ICacheProvider that takes IEnumerable<ICacheDependency>, we create an array of ICacheDependency and add our SqlCacheDependency object to this. However, it would be simple to provide a Fetch overload that only takes a single ICacheDependency instead.
Configuring the Sql Server and ASP.NET application for SQL Cache Dependency
As the code will use the ASP.NET Sql cache dependency polling method (as supported by SQL Server 2000 onwards) we need to carry out some additional configuration.
Open the Visual Studio command prompt and run the following command to enable caching support in the CacheSample database:
aspnet_regsql -S <servername> -E -d CacheSample –ed
Next enable the Sale table for caching support by running the following command:
aspnet_regsql -S <servername> -E -d CacheSample –et –t Sale
We also have to add the following to the web.config file, inside the <system.web> element:
    <caching>
      <sqlCacheDependencypollTime="10000"enabled="true">
        <databases>
          <addname="CacheSample"connectionStringName="CacheSample"/>
        </databases>
      </sqlCacheDependency>
    </caching>
 
Testing the Cache Dependency
Using the web page developed in part one, the first time the GetSales() method is called, I get the following result:
Count of Sales: 4999, Last DayCount: 4999, Total Sales: 187138020.0000. Query took 979 ms
 
Subsequent calls to GetSales() give the following results. Note that the cache is now beings used, so the time recorded by the StopWatch is 0ms:
Count of Sales: 4999, Last DayCount: 4999, Total Sales: 187138020.0000. Query took 0 ms
 
Next add a new row to the Sale table:
INSERT INTO [CacheSample].[dbo].[Sale]
           ([DayCount]
           ,[Sales])
     VALUES
           (5000, 100)
 
This should invalidate the cache, making the next call to GetSales() get the data from the database again:
Count of Sales: 5000, Last DayCount: 5000, Total Sales: 187138120.0000. Query took 499 ms
 

Posted On Wednesday, January 26, 2011 10:56 AM | Comments (0) | Filed Under [ C# ASP.NET Design Patterns Data Caching ]

Powered by: