Skip to content

Instantly share code, notes, and snippets.

@steven-r
Created March 17, 2015 10:11
Show Gist options
  • Save steven-r/9b26489874a5db285ce8 to your computer and use it in GitHub Desktop.
Save steven-r/9b26489874a5db285ce8 to your computer and use it in GitHub Desktop.
Abstract cache implementation
#region copyright
/*The MIT License (MIT)
Copyright (c) 2015 Stephen Reindl
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#endregion
#region usings
using System;
using System.Collections.Generic;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Infrastructure;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.Reflection;
using System.Text;
using Microsoft.ApplicationServer.Caching;
using TimeJack.Model.Annotations;
using TimeJack.Model.Models;
#endregion
namespace TimeJack.Common.Cache
{
/// <summary>
///
/// </summary>
public abstract class AbstractCache<TCache, TEntity> : ICacheProvider
where TCache : class
where TEntity : class
{
// ReSharper disable StaticFieldInGenericType
private static readonly object Lock = new object();
protected static volatile DataCacheFactory CacheFactory;
// ReSharper restore StaticFieldInGenericType
/// <summary>
///
/// </summary>
private DataCache _cache;
protected DataCache Cache { get { return _cache; } set { _cache = value; } }
/// <summary>
/// Number of cache accesses
/// </summary>
public long StatisticsAccessCount { get; set; }
/// <summary>
/// Number of cache hits
/// </summary>
public long StatisticsHitCount { get; set; }
/// <summary>
/// Number of cache cleanups (without <see cref="Invalidate()"/> calls.
/// </summary>
public long StatisticsRemoveCount { get; set; }
/// <summary>
///
/// </summary>
public string CacheName { get; private set; }
/// <summary>
///
/// </summary>
protected AbstractCache()
{
CacheName = typeof(TCache).Name;
GetCacheFactory();
//GlimpseCacheStatisticsTab.RegisterCache(this);
Cache = CacheFactory.GetCache(CacheName);
}
/// <summary>
/// Return the cache information for this azure instance
/// </summary>
private void GetCacheFactory()
{
if (CacheFactory == null)
{
lock (Lock)
{
if (CacheFactory == null)
{
DataCacheFactoryConfiguration config = new DataCacheFactoryConfiguration("default");
DataCacheFactory cacheFactory = new DataCacheFactory(config);
CacheFactory = cacheFactory;
}
}
}
}
/// <summary>
///
/// </summary>
/// <returns></returns>
protected abstract TimeSpan GetExpiration();
/// <summary>
/// Retrieve a single element currently not in cache.
/// </summary>
/// <param name="db">The current database contract</param>
/// <param name="key">The key to retrieve. In case of a multi field key, please refer to all elements of the key</param>
/// <returns>An object of type <see cref="TEntity"/>. This element <strong>MUST NOT BE</strong> an EF proxy. Please use
/// <see cref="DbQuery{TResult}.AsNoTracking"/>.
/// </returns>
/// <exception cref="ArgumentNullException">Either <see cref="db"/> or <see cref="key"/> are null</exception>
protected abstract TEntity GetSingleElement(ITimeJackContext db, [NotNull] object[] key);
/// <summary>
///
/// </summary>
/// <param name="entry"></param>
/// <returns></returns>
protected abstract IEnumerable<object> GetKey(TEntity entry);
/// <summary>
///
/// </summary>
/// <param name="db"></param>
/// <param name="key"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
[CanBeNull]
public TEntity Get([NotNull] ITimeJackContext db, [NotNull] object[] key)
{
Contract.Requires(key != null);
try
{
StatisticsAccessCount++;
if (Cache == null)
{
// Cache not active (should not happen, but who knows)
return GetSingleElement(db, key);
}
string stringKey = GetKeyName(typeof (TEntity).Name, key);
var data = Cache.Get(stringKey);
if (data == null)
{
lock (Lock)
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (data == null)
{
// create cache region if not exist already
//_cache.CreateRegion(CacheName);
// if after lock still no entry (for race conditions)
data = GetSingleElement(db, key);
if (data == null)
{
// set cache entry to false to prevent loading this element again and again.
Cache.Add(stringKey, false);
// no element
return null;
}
if (IsEfProxy(data))
{
throw new InvalidOperationException("EF proxy elements cannot be cached");
}
Cache.Add(stringKey, data);
}
}
}
else
{
StatisticsHitCount++;
}
return data as TEntity;
}
catch (DataCacheException e)
{
log4net.LogManager.GetLogger(GetType()).Error("Exception", e);
}
return null;
}
/// <summary>
/// Convenience method
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public TEntity Get(object[] key)
{
return Get(new TimeJackContext(), key);
}
/// <summary>
///
/// </summary>
/// <param name="entry"></param>
public void Invalidate(TEntity entry)
{
Invalidate(GetKeyName(typeof(TEntity).Name, GetKey(entry)));
}
/// <summary>
///
/// </summary>
/// <param name="key"></param>
public void Invalidate(IEnumerable<object> key)
{
Contract.Requires(key != null);
string stringKey = GetKeyName(typeof(TEntity).Name, key);
Invalidate(stringKey);
}
/// <summary>
///
/// </summary>
public void Invalidate()
{
foreach (KeyValuePair<string, object> keyValuePair in _cache.GetObjectsInRegion(CacheName))
{
Invalidate(keyValuePair.Key);
}
}
private void Invalidate(string key)
{
Contract.Requires(_cache != null);
_cache.Remove(key, CacheName);
StatisticsRemoveCount++;
}
private static string GetKeyName(string name, IEnumerable<object> key)
{
Contract.Requires(key != null);
StringBuilder sb = new StringBuilder();
sb.Append(name);
foreach (object obj in key)
{
sb.Append('/');
sb.Append((obj != null ? obj.ToString() : "NULL"));
}
return sb.ToString();
}
#region Singleton
// ReSharper disable StaticFieldInGenericType
private static readonly object SyncRoot = new Object();
// ReSharper restore StaticFieldInGenericType
private static volatile TCache _instance;
/// <summary>
/// Gets the singleton instance of <SEE langword="this" /> class.
/// </summary>
/// <remarks>
/// borrowed from http://codebender.denniland.com/a-singleton-base-class-to-implement-the-singleton-pattern-in-c/
/// </remarks>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "This is by design"), NotNull]
public static TCache Instance
{
get
{
if (_instance == null)
{
lock (SyncRoot)
{
if (_instance == null)
{
_instance = SingletonFactory.SingletonFactoryInstance;
}
}
}
return _instance;
}
}
/// <SUMMARY>
/// The singleton class factory to create the singleton instance.
/// </SUMMARY>
private abstract class SingletonFactory
{
/// <SUMMARY>
/// Explicit static constructor to tell C# compiler
/// not to mark type as <C>beforefieldinit</C>
/// </SUMMARY>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Explicit static constructor to tell C# compiler not to mark type as beforefieldinit")]
static SingletonFactory() { }
/// <SUMMARY>
/// Prevent the compiler from generating a default constructor.
/// </SUMMARY>
private SingletonFactory() { }
/// <SUMMARY>
/// access to the instance of the T type
/// </SUMMARY>
// ReSharper disable StaticFieldInGenericType
internal static readonly TCache SingletonFactoryInstance = GetInstance();
// ReSharper restore StaticFieldInGenericType
/// <SUMMARY>
/// Access to the <SEE langword="private" /> constructor, warn if it doesn't exists.
/// </SUMMARY>
[NotNull]
private static TCache GetInstance()
{
lock (SyncRoot)
{
Type theType = typeof(TCache);
try
{
return (TCache)theType.InvokeMember(theType.Name,
BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.NonPublic,
null, null, null,
CultureInfo.InvariantCulture);
}
catch (MissingMethodException ex)
{
throw new TypeLoadException(string.Format(CultureInfo.CurrentCulture, "The type '{0}' must have a private constructor to " +
"be used in the Singleton pattern.", theType.FullName), ex);
}
}
}
}
#endregion
#region utility functions
/// <summary>
/// Check if an element is a proxy element of ef (those elements should not be cached)
/// </summary>
/// <param name="element"></param>
/// <returns></returns>
public static bool IsEfProxy(object element)
{
return element != null && ObjectContext.GetObjectType(element.GetType()) != element.GetType();
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment