Adding SQL Cache Dependencies to the Loosely coupled .NET Cache Provider
Posted
by Rhames
on Geeks with Blogs
See other posts from Geeks with Blogs
or by Rhames
Published on Thu, 13 Sep 2012 10:24:45 GMT
Indexed on
2012/09/13
15:39 UTC
Read the original article
Hit count: 403
This post adds SQL Cache Dependency support to the loosely coupled .NET Cache Provider that I described in the previous post (http://geekswithblogs.net/Rhames/archive/2012/09/11/loosely-coupled-.net-cache-provider-using-dependency-injection.aspx). The sample code is available on github at https://github.com/RobinHames/CacheProvider.git.
Each time we want to apply a cache dependency to a call to fetch or cache a data item we need to supply an instance of the relevant dependency implementation. This suggests an Abstract Factory will be useful to create cache dependencies as needed. We can then use Dependency Injection to inject the factory into the relevant consumer.
Castle Windsor provides a typed factory facility that will be utilised to implement the cache dependency abstract factory (see http://docs.castleproject.org/Windsor.Typed-Factory-Facility-interface-based-factories.ashx).
Cache Dependency Interfaces
First I created a set of cache dependency interfaces in the domain layer, which can be used to pass a cache dependency into the cache provider.
ICacheDependency
The ICacheDependency interface is simply an empty interface that is used as a parent for the specific cache dependency interfaces. This will allow us to place a generic constraint on the Cache Dependency Factory, and will give us a type that can be passed into the relevant Cache Provider methods.
namespace CacheDiSample.Domain.CacheInterfaces
{
public interface ICacheDependency
{
}
}
ISqlCacheDependency.cs
The ISqlCacheDependency interface provides specific SQL caching details, such as a Sql Command or a database connection and table. It is the concrete implementation of this interface that will be created by the factory in passed into the Cache Provider.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CacheDiSample.Domain.CacheInterfaces
{
public interface ISqlCacheDependency : ICacheDependency
{
ISqlCacheDependency Initialise(string databaseConnectionName, string tableName);
ISqlCacheDependency Initialise(System.Data.SqlClient.SqlCommand sqlCommand);
}
}
If we want other types of cache dependencies, such as by key or file, interfaces may be created to support these (the sample code includes an IKeyCacheDependency interface).
Modifying ICacheProvider to accept Cache Dependencies
Next I modified the exisitng ICacheProvider<T> interface so that cache dependencies may be passed into a Fetch method call. I did this by adding two overloads to the existing Fetch methods, which take an IEnumerable<ICacheDependency> parameter (the IEnumerable allows more than one cache dependency to be included). I also added a method to create cache dependencies. This means that the implementation of the Cache Provider will require a dependency on the Cache Dependency Factory. It is pretty much down to personal choice as to whether this approach is taken, or whether the Cache Dependency Factory is injected directly into the repository or other consumer of Cache Provider. I think, because the cache dependency cannot be used without the Cache Provider, placing the dependency on the factory into the Cache Provider implementation is cleaner.
ICacheProvider.cs
using System;
using System.Collections.Generic;
namespace CacheDiSample.Domain.CacheInterfaces
{
public interface ICacheProvider<T>
{
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);
U CreateCacheDependency<U>()
where U : ICacheDependency;
}
}
Cache Dependency Factory
Next I created the interface for the Cache Dependency Factory in the domain layer.
ICacheDependencyFactory.cs
namespace CacheDiSample.Domain.CacheInterfaces
{
public interface ICacheDependencyFactory
{
T Create<T>()
where T : ICacheDependency;
void Release<T>(T cacheDependency)
where T : ICacheDependency;
}
}
I used the ICacheDependency parent interface as a generic constraint on the create and release methods in the factory interface.
Now the interfaces are in place, I moved on to the concrete implementations.
ISqlCacheDependency Concrete Implementation
The concrete implementation of ISqlCacheDependency will need to provide an instance of System.Web.Caching.SqlCacheDependency to the Cache Provider implementation. Unfortunately this class is sealed, so I cannot simply inherit from this. Instead, I created an interface called IAspNetCacheDependency that will provide a Create method to create an instance of the relevant System.Web.Caching Cache Dependency type. This interface is specific to the ASP.NET implementation of the Cache Provider, so it should be defined in the same layer as the concrete implementation of the Cache Provider (the MVC UI layer in the sample code).
IAspNetCacheDependency.cs
using System.Web.Caching;
namespace CacheDiSample.CacheProviders
{
public interface IAspNetCacheDependency
{
CacheDependency CreateAspNetCacheDependency();
}
}
Next, I created the concrete implementation of the ISqlCacheDependency interface. This class also implements the IAspNetCacheDependency interface. This concrete implementation also is defined in the same layer as the Cache Provider implementation.
AspNetSqlCacheDependency.cs
using System.Web.Caching;
using CacheDiSample.Domain.CacheInterfaces;
namespace CacheDiSample.CacheProviders
{
public class AspNetSqlCacheDependency : ISqlCacheDependency, IAspNetCacheDependency
{
private string databaseConnectionName;
private string tableName;
private System.Data.SqlClient.SqlCommand sqlCommand;
#region ISqlCacheDependency Members
public ISqlCacheDependency Initialise(string databaseConnectionName, string tableName)
{
this.databaseConnectionName = databaseConnectionName;
this.tableName = tableName;
return this;
}
public ISqlCacheDependency Initialise(System.Data.SqlClient.SqlCommand sqlCommand)
{
this.sqlCommand = sqlCommand;
return this;
}
#endregion
#region IAspNetCacheDependency Members
public System.Web.Caching.CacheDependency CreateAspNetCacheDependency()
{
if (sqlCommand != null)
return new SqlCacheDependency(sqlCommand);
else
return new SqlCacheDependency(databaseConnectionName, tableName);
}
#endregion
}
}
ICacheProvider Concrete Implementation
The ICacheProvider interface is implemented by the CacheProvider class. This implementation is modified to include the changes to the ICacheProvider interface.
First I needed to inject the Cache Dependency Factory into the Cache Provider:
private ICacheDependencyFactory cacheDependencyFactory;
public CacheProvider(ICacheDependencyFactory cacheDependencyFactory)
{
if (cacheDependencyFactory == null)
throw new ArgumentNullException("cacheDependencyFactory");
this.cacheDependencyFactory = cacheDependencyFactory;
}
Next I implemented the CreateCacheDependency method, which simply passes on the create request to the factory:
public U CreateCacheDependency<U>() where U : ICacheDependency
{
return this.cacheDependencyFactory.Create<U>();
}
The signature of the FetchAndCache helper method was modified to take an additional IEnumerable<ICacheDependency> parameter:
private U FetchAndCache<U>(string key, Func<U> retrieveData,
DateTime? absoluteExpiry, TimeSpan? relativeExpiry, IEnumerable<ICacheDependency> cacheDependencies)
and the following code added to create the relevant System.Web.Caching.CacheDependency object for any dependencies and pass them to the HttpContext Cache:
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);
The full code listing for the modified CacheProvider class is shown below:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Caching;
using CacheDiSample.Domain.CacheInterfaces;
namespace CacheDiSample.CacheProviders
{
public class CacheProvider<T> : ICacheProvider<T>
{
private ICacheDependencyFactory cacheDependencyFactory;
public CacheProvider(ICacheDependencyFactory cacheDependencyFactory)
{
if (cacheDependencyFactory == null)
throw new ArgumentNullException("cacheDependencyFactory");
this.cacheDependencyFactory = cacheDependencyFactory;
}
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);
}
public U CreateCacheDependency<U>() where U : ICacheDependency
{
return this.cacheDependencyFactory.Create<U>();
}
#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
}
}
Wiring up the DI Container
Now the implementations for the Cache Dependency are in place, I wired them up in the existing Windsor CacheInstaller. First I needed to register the implementation of the ISqlCacheDependency interface:
container.Register(
Component.For<ISqlCacheDependency>()
.ImplementedBy<AspNetSqlCacheDependency>()
.LifestyleTransient());
Next I registered the Cache Dependency Factory. Notice that I have not implemented the ICacheDependencyFactory interface. Castle Windsor will do this for me by using the Type Factory Facility. I do need to bring the Castle.Facilities.TypedFacility namespace into scope:
using Castle.Facilities.TypedFactory;
Then I registered the factory:
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component.For<ICacheDependencyFactory>()
.AsFactory());
The full code for the CacheInstaller class is:
using Castle.MicroKernel.Registration;
using Castle.MicroKernel.SubSystems.Configuration;
using Castle.Windsor;
using Castle.Facilities.TypedFactory;
using CacheDiSample.Domain.CacheInterfaces;
using CacheDiSample.CacheProviders;
namespace CacheDiSample.WindsorInstallers
{
public class CacheInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For(typeof(ICacheProvider<>))
.ImplementedBy(typeof(CacheProvider<>))
.LifestyleTransient());
container.Register(
Component.For<ISqlCacheDependency>()
.ImplementedBy<AspNetSqlCacheDependency>()
.LifestyleTransient());
container.AddFacility<TypedFactoryFacility>();
container.Register(
Component.For<ICacheDependencyFactory>()
.AsFactory());
}
}
}
Configuring the ASP.NET SQL Cache Dependency
There are a couple of configuration steps required to enable SQL Cache Dependency for the application and database. From the Visual Studio Command Prompt, the following commands should be used to enable the Cache Polling of the relevant database tables:
aspnet_regsql -S <servername> -E -d <databasename> –ed
aspnet_regsql -S <servername> -E -d CacheSample –et –t <tablename>
(The –t option should be repeated for each table that is to be made available for cache dependencies).
Finally the SQL Cache Polling needs to be enabled by adding the following configuration to the <system.web> section of web.config:
<caching>
<sqlCacheDependency pollTime="10000" enabled="true">
<databases>
<add name="BloggingContext" connectionStringName="BloggingContext"/>
</databases>
</sqlCacheDependency>
</caching>
(obviously the name and connection string name should be altered as required).
Using a SQL Cache Dependency
Now all the coding is complete. To specify a SQL Cache Dependency, I can modify my BlogRepositoryWithCaching decorator class (see the earlier post) as follows:
public IList<Blog> GetAll()
{
var sqlCacheDependency = cacheProvider.CreateCacheDependency<ISqlCacheDependency>()
.Initialise("BloggingContext", "Blogs");
ICacheDependency[] cacheDependencies = new ICacheDependency[] { sqlCacheDependency };
string key = string.Format("CacheDiSample.DataAccess.GetAll");
return cacheProvider.Fetch(key, () =>
{
return parentBlogRepository.GetAll();
},
null, null, cacheDependencies)
.ToList();
}
This will add a dependency of the “Blogs” table in the database. The data will remain in the cache until the contents of this table change, then the cache item will be invalidated, and the next call to the GetAll() repository method will be routed to the parent repository to refresh the data from the database.
© Geeks with Blogs or respective owner