Files
Umbraco-CMS/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs
T

197 lines
7.3 KiB
C#

using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Caching;
using System.Threading;
using System.Web;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Persistence.Caching
{
/// <summary>
/// The Runtime Cache provider looks up objects in the Runtime cache for fast retrival
/// </summary>
/// <remarks>
///
/// If a web session is detected then the HttpRuntime.Cache will be used for the runtime cache, otherwise a custom
/// MemoryCache instance will be used. It is important to use the HttpRuntime.Cache when a web session is detected so
/// that the memory management of cache in IIS can be handled appopriately.
///
/// When a web sessions is detected we will pre-fix all HttpRuntime.Cache entries so that when we clear it we are only
/// clearing items that have been inserted by this provider.
///
/// NOTE: These changes are all temporary until we finalize the ApplicationCache implementation which will support static cache, runtime cache
/// and request based cache which will all live in one central location so it is easily managed.
///
/// Also note that we don't always keep checking if HttpContext.Current == null and instead check for _memoryCache != null. This is because
/// when there are async requests being made even in the context of a web request, the HttpContext.Current will be null but the HttpRuntime.Cache will
/// always be available.
///
/// </remarks>
internal sealed class RuntimeCacheProvider : IRepositoryCacheProvider
{
#region Singleton
private static readonly Lazy<RuntimeCacheProvider> lazy = new Lazy<RuntimeCacheProvider>(() => new RuntimeCacheProvider());
public static RuntimeCacheProvider Current { get { return lazy.Value; } }
private RuntimeCacheProvider()
{
if (HttpContext.Current == null)
{
_memoryCache = new MemoryCache("in-memory");
}
}
#endregion
//TODO Save this in cache as well, so its not limited to a single server usage
private readonly ConcurrentHashSet<string> _keyTracker = new ConcurrentHashSet<string>();
private ObjectCache _memoryCache;
private static readonly ReaderWriterLockSlim ClearLock = new ReaderWriterLockSlim();
public IEntity GetById(Type type, Guid id)
{
var key = GetCompositeId(type, id);
var item = _memoryCache != null
? _memoryCache.Get(key)
: HttpRuntime.Cache.Get(key);
return item as IEntity;
}
public IEnumerable<IEntity> GetByIds(Type type, List<Guid> ids)
{
foreach (var guid in ids)
{
var item = _memoryCache != null
? _memoryCache.Get(GetCompositeId(type, guid))
: HttpRuntime.Cache.Get(GetCompositeId(type, guid));
yield return item as IEntity;
}
}
public IEnumerable<IEntity> GetAllByType(Type type)
{
foreach (var key in _keyTracker)
{
if (key.StartsWith(string.Format("{0}{1}-", CacheItemPrefix, type.Name)))
{
var item = _memoryCache != null
? _memoryCache.Get(key)
: HttpRuntime.Cache.Get(key);
yield return item as IEntity;
}
}
}
public void Save(Type type, IEntity entity)
{
var key = GetCompositeId(type, entity.Id);
_keyTracker.TryAdd(key);
//NOTE: Before we were checking if it already exists but the MemoryCache.Set handles this implicitly and does
// an add or update, same goes for HttpRuntime.Cache.Insert.
if (_memoryCache != null)
{
_memoryCache.Set(key, entity, new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(5) });
}
else
{
HttpRuntime.Cache.Insert(key, entity, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(5));
}
}
public void Delete(Type type, IEntity entity)
{
var key = GetCompositeId(type, entity.Id);
if (_memoryCache != null)
{
_memoryCache.Remove(key);
}
else
{
HttpRuntime.Cache.Remove(key);
}
_keyTracker.Remove(key);
}
/// <summary>
/// Clear cache by type
/// </summary>
/// <param name="type"></param>
public void Clear(Type type)
{
using (new WriteLock(ClearLock))
{
var keys = new string[_keyTracker.Count];
_keyTracker.CopyTo(keys, 0);
var keysToRemove = new List<string>();
foreach (var key in keys.Where(x => x.StartsWith(string.Format("{0}{1}-", CacheItemPrefix, type.Name))))
{
_keyTracker.Remove(key);
keysToRemove.Add(key);
}
foreach (var key in keysToRemove)
{
if (_memoryCache != null)
{
_memoryCache.Remove(key);
}
else
{
HttpRuntime.Cache.Remove(key);
}
}
}
}
public void Clear()
{
using (new WriteLock(ClearLock))
{
_keyTracker.Clear();
if (_memoryCache != null)
{
_memoryCache.DisposeIfDisposable();
_memoryCache = new MemoryCache("in-memory");
}
else
{
foreach (DictionaryEntry c in HttpRuntime.Cache)
{
if (c.Key is string && ((string)c.Key).InvariantStartsWith(CacheItemPrefix))
{
if (HttpRuntime.Cache[(string)c.Key] == null) return;
HttpRuntime.Cache.Remove((string)c.Key);
}
}
}
}
}
/// <summary>
/// We prefix all cache keys with this so that we know which ones this class has created when
/// using the HttpRuntime cache so that when we clear it we don't clear other entries we didn't create.
/// </summary>
private const string CacheItemPrefix = "umbrtmche_";
private string GetCompositeId(Type type, Guid id)
{
return string.Format("{0}{1}-{2}", CacheItemPrefix, type.Name, id.ToString());
}
private string GetCompositeId(Type type, int id)
{
return string.Format("{0}{1}-{2}", CacheItemPrefix, type.Name, id.ToGuid());
}
}
}