197 lines
7.3 KiB
C#
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());
|
|
}
|
|
}
|
|
} |