Moves repository caching logic into new CachePolicies.

This commit is contained in:
Shannon
2016-01-07 16:31:20 +01:00
parent 3e985acef1
commit 68aa6ff093
16 changed files with 559 additions and 291 deletions

View File

@@ -0,0 +1,222 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Cache
{
/// <summary>
/// The default cache policy for retrieving a single entity
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TId"></typeparam>
internal class DefaultRepositoryCachePolicy<TEntity, TId> : DisposableObject, IRepositoryCachePolicy<TEntity, TId>
where TEntity : class, IAggregateRoot
{
private readonly RepositoryCachePolicyOptions _options;
protected IRuntimeCacheProvider Cache { get; private set; }
private Action _action;
public DefaultRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options)
{
_options = options;
Cache = cache;
}
public string GetCacheIdKey(object id)
{
return string.Format("{0}{1}", GetCacheTypeKey(), id);
}
public string GetCacheTypeKey()
{
return string.Format("uRepo_{0}_", typeof(TEntity).Name);
}
public void CreateOrUpdate(TEntity entity, Action<TEntity> persistMethod)
{
var cacheKey = GetCacheIdKey(entity.Id);
try
{
persistMethod(entity);
//set the disposal action
SetCacheAction(() => Cache.InsertCacheItem(cacheKey, () => entity));
////If there's a GetAll zero count cache, ensure it is cleared
//_cache.ClearCacheItem(GetCacheTypeKey());
}
catch
{
//if an exception is thrown we need to remove the entry from cache, this is ONLY a work around because of the way
// that we cache entities: http://issues.umbraco.org/issue/U4-4259
Cache.ClearCacheItem(cacheKey);
////If there's a GetAll zero count cache, ensure it is cleared
//_cache.ClearCacheItem(GetCacheTypeKey());
throw;
}
}
public void Remove(TEntity entity, Action<TEntity> persistMethod)
{
persistMethod(entity);
//set the disposal action
var cacheKey = GetCacheIdKey(entity.Id);
SetCacheAction(() => Cache.ClearCacheItem(cacheKey));
////If there's a GetAll zero count cache, ensure it is cleared
//_cache.ClearCacheItem(GetCacheTypeKey());
}
public TEntity Get(TId id, Func<TId, TEntity> getFromRepo)
{
var cacheKey = GetCacheIdKey(id);
var fromCache = Cache.GetCacheItem<TEntity>(cacheKey);
if (fromCache != null)
return fromCache;
var entity = getFromRepo(id);
//set the disposal action
SetCacheAction(cacheKey, entity);
return entity;
}
public TEntity Get(TId id)
{
var cacheKey = GetCacheIdKey(id);
return Cache.GetCacheItem<TEntity>(cacheKey);
}
public bool Exists(TId id, Func<TId, bool> getFromRepo)
{
var cacheKey = GetCacheIdKey(id);
var fromCache = Cache.GetCacheItem<TEntity>(cacheKey);
return fromCache != null || getFromRepo(id);
}
public virtual TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> getFromRepo)
{
if (ids.Any())
{
var entities = ids.Select(Get).ToArray();
if (ids.Length.Equals(entities.Length) && entities.Any(x => x == null) == false)
return entities;
}
else
{
var allEntities = GetAllFromCache();
if (allEntities.Any())
{
if (_options.GetAllCacheValidateCount)
{
//Get count of all entities of current type (TEntity) to ensure cached result is correct
var totalCount = _options.PerformCount();
if (allEntities.Length == totalCount)
return allEntities;
}
else
{
return allEntities;
}
}
else if (_options.GetAllCacheAllowZeroCount)
{
//if the repository allows caching a zero count, then check the zero count cache
var zeroCount = Cache.GetCacheItem<TEntity[]>(GetCacheTypeKey());
if (zeroCount != null && zeroCount.Any() == false)
{
//there is a zero count cache so return an empty list
return new TEntity[] {};
}
}
}
//we need to do the lookup from the repo
var entityCollection = getFromRepo(ids)
//ensure we don't include any null refs in the returned collection!
.WhereNotNull()
.ToArray();
//set the disposal action
SetCacheAction(ids, entityCollection);
return entityCollection;
}
/// <summary>
/// Performs the lookup for all entities of this type from the cache
/// </summary>
/// <returns></returns>
protected virtual TEntity[] GetAllFromCache()
{
var allEntities = Cache.GetCacheItemsByKeySearch<TEntity>(GetCacheTypeKey())
.WhereNotNull()
.ToArray();
return allEntities.Any() ? allEntities : new TEntity[] {};
}
/// <summary>
/// The disposal performs the caching
/// </summary>
protected override void DisposeResources()
{
if (_action != null)
{
_action();
}
}
/// <summary>
/// Sets the action to execute on disposal for a single entity
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="entity"></param>
protected virtual void SetCacheAction(string cacheKey, TEntity entity)
{
SetCacheAction(() => Cache.InsertCacheItem(cacheKey, () => entity));
}
/// <summary>
/// Sets the action to execute on disposal for an entity collection
/// </summary>
/// <param name="ids"></param>
/// <param name="entityCollection"></param>
protected virtual void SetCacheAction(TId[] ids, TEntity[] entityCollection)
{
SetCacheAction(() =>
{
//This option cannot execute if we are looking up specific Ids
if (ids.Any() == false && entityCollection.Length == 0 && _options.GetAllCacheAllowZeroCount)
{
//there was nothing returned but we want to cache a zero count result so add an TEntity[] to the cache
// to signify that there is a zero count cache
Cache.InsertCacheItem(GetCacheTypeKey(), () => new TEntity[] {});
}
else
{
//This is the default behavior, we'll individually cache each item so that if/when these items are resolved
// by id, they are returned from the already existing cache.
foreach (var entity in entityCollection.WhereNotNull())
{
var localCopy = entity;
Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => localCopy);
}
}
});
}
/// <summary>
/// Sets the action to execute on disposal
/// </summary>
/// <param name="action"></param>
protected void SetCacheAction(Action action)
{
_action = action;
}
}
}

View File

@@ -0,0 +1,27 @@
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Cache
{
/// <summary>
/// Creates cache policies
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TId"></typeparam>
internal class DefaultRepositoryCachePolicyFactory<TEntity, TId> : IRepositoryCachePolicyFactory<TEntity, TId>
where TEntity : class, IAggregateRoot
{
private readonly IRuntimeCacheProvider _runtimeCache;
private readonly RepositoryCachePolicyOptions _options;
public DefaultRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, RepositoryCachePolicyOptions options)
{
_runtimeCache = runtimeCache;
_options = options;
}
public virtual IRepositoryCachePolicy<TEntity, TId> CreatePolicy()
{
return new DefaultRepositoryCachePolicy<TEntity, TId>(_runtimeCache, _options);
}
}
}

View File

@@ -0,0 +1,57 @@
using System.Linq;
using Umbraco.Core.Collections;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Cache
{
/// <summary>
/// A caching policy that caches an entire dataset as a single collection
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TId"></typeparam>
internal class FullDataSetRepositoryCachePolicy<TEntity, TId> : DefaultRepositoryCachePolicy<TEntity, TId>
where TEntity : class, IAggregateRoot
{
public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache) : base(cache, new RepositoryCachePolicyOptions())
{
}
/// <summary>
/// For this type of caching policy, we don't cache individual items
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="entity"></param>
protected override void SetCacheAction(string cacheKey, TEntity entity)
{
//do nothing
}
/// <summary>
/// Sets the action to execute on disposal for an entity collection
/// </summary>
/// <param name="ids"></param>
/// <param name="entityCollection"></param>
protected override void SetCacheAction(TId[] ids, TEntity[] entityCollection)
{
//for this type of caching policy, we don't want to cache any GetAll request containing specific Ids
if (ids.Any()) return;
//set the disposal action
SetCacheAction(() =>
{
//We want to cache the result as a single collection
Cache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList<TEntity>(entityCollection));
});
}
/// <summary>
/// This policy will cache the full data set as a single collection
/// </summary>
/// <returns></returns>
protected override TEntity[] GetAllFromCache()
{
var found = Cache.GetCacheItem<DeepCloneableList<TEntity>>(GetCacheTypeKey());
return found == null ? new TEntity[] { } : found.WhereNotNull().ToArray();
}
}
}

View File

@@ -0,0 +1,25 @@
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Cache
{
/// <summary>
/// Creates cache policies
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TId"></typeparam>
internal class FullDataSetRepositoryCachePolicyFactory<TEntity, TId> : IRepositoryCachePolicyFactory<TEntity, TId>
where TEntity : class, IAggregateRoot
{
private readonly IRuntimeCacheProvider _runtimeCache;
public FullDataSetRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache)
{
_runtimeCache = runtimeCache;
}
public virtual IRepositoryCachePolicy<TEntity, TId> CreatePolicy()
{
return new FullDataSetRepositoryCachePolicy<TEntity, TId>(_runtimeCache);
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Cache
{
internal interface IRepositoryCachePolicy<TEntity, TId> : IDisposable
where TEntity : class, IAggregateRoot
{
TEntity Get(TId id, Func<TId, TEntity> getFromRepo);
TEntity Get(TId id);
bool Exists(TId id, Func<TId, bool> getFromRepo);
string GetCacheIdKey(object id);
string GetCacheTypeKey();
void CreateOrUpdate(TEntity entity, Action<TEntity> persistMethod);
void Remove(TEntity entity, Action<TEntity> persistMethod);
TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> getFromRepo);
}
}

View File

@@ -0,0 +1,9 @@
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Cache
{
internal interface IRepositoryCachePolicyFactory<TEntity, TId> where TEntity : class, IAggregateRoot
{
IRepositoryCachePolicy<TEntity, TId> CreatePolicy();
}
}

View File

@@ -0,0 +1,24 @@
using System.Linq;
using Umbraco.Core.Collections;
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Cache
{
/// <summary>
/// A caching policy that ignores all caches for GetAll - it will only cache calls for individual items
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TId"></typeparam>
internal class OnlySingleItemsRepositoryCachePolicy<TEntity, TId> : DefaultRepositoryCachePolicy<TEntity, TId>
where TEntity : class, IAggregateRoot
{
public OnlySingleItemsRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options) : base(cache, options)
{
}
protected override void SetCacheAction(TId[] ids, TEntity[] entityCollection)
{
//do nothing
}
}
}

View File

@@ -0,0 +1,27 @@
using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Cache
{
/// <summary>
/// Creates cache policies
/// </summary>
/// <typeparam name="TEntity"></typeparam>
/// <typeparam name="TId"></typeparam>
internal class OnlySingleItemsRepositoryCachePolicyFactory<TEntity, TId> : IRepositoryCachePolicyFactory<TEntity, TId>
where TEntity : class, IAggregateRoot
{
private readonly IRuntimeCacheProvider _runtimeCache;
private readonly RepositoryCachePolicyOptions _options;
public OnlySingleItemsRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, RepositoryCachePolicyOptions options)
{
_runtimeCache = runtimeCache;
_options = options;
}
public virtual IRepositoryCachePolicy<TEntity, TId> CreatePolicy()
{
return new OnlySingleItemsRepositoryCachePolicy<TEntity, TId>(_runtimeCache, _options);
}
}
}

View File

@@ -1,18 +1,34 @@
namespace Umbraco.Core.Persistence.Repositories
using System;
namespace Umbraco.Core.Cache
{
internal class RepositoryCacheOptions
internal class RepositoryCachePolicyOptions
{
/// <summary>
/// Constructor sets defaults
/// Ctor - sets GetAllCacheValidateCount = true
/// </summary>
public RepositoryCacheOptions()
public RepositoryCachePolicyOptions(Func<int> performCount)
{
PerformCount = performCount;
GetAllCacheValidateCount = true;
GetAllCacheAllowZeroCount = false;
GetAllCacheThresholdLimit = 100;
GetAllCacheAsCollection = false;
}
/// <summary>
/// Ctor - sets GetAllCacheValidateCount = false
/// </summary>
public RepositoryCachePolicyOptions()
{
PerformCount = null;
GetAllCacheValidateCount = false;
GetAllCacheAllowZeroCount = false;
}
/// <summary>
/// Callback required to get count for GetAllCacheValidateCount
/// </summary>
public Func<int> PerformCount { get; private set; }
/// <summary>
/// True/false as to validate the total item count when all items are returned from cache, the default is true but this
/// means that a db lookup will occur - though that lookup will probably be significantly less expensive than the normal
@@ -22,26 +38,11 @@ namespace Umbraco.Core.Persistence.Repositories
/// setting this to return false will improve performance of GetAll cache with no params but should only be used
/// for specific circumstances
/// </remarks>
public bool GetAllCacheValidateCount { get; set; }
public bool GetAllCacheValidateCount { get; private set; }
/// <summary>
/// True if the GetAll method will cache that there are zero results so that the db is not hit when there are no results found
/// </summary>
public bool GetAllCacheAllowZeroCount { get; set; }
/// <summary>
/// The threshold entity count for which the GetAll method will cache entities
/// </summary>
public int GetAllCacheThresholdLimit { get; set; }
/// <summary>
/// When set to true, the cache for the result of GetAll will be cached as a List/Collection rather than
/// individual entities in the dictionary
/// </summary>
/// <remarks>
/// The default is false which means that if the result of GetAll is less than the GetAllCacheThresholdLimit, each entity
/// returned will be cached individually
/// </remarks>
public bool GetAllCacheAsCollection { get; set; }
}
}

View File

@@ -28,27 +28,19 @@ namespace Umbraco.Core.Persistence.Repositories
_languageRepository = languageRepository;
}
/// <summary>
/// Returns the repository cache options
/// </summary>
/// <remarks>
/// The dictionary repository is also backed by two sub repositories, the main one that will be used is the DictionaryByKeyRepository
/// since the queries from DefaultCultureDictionary will use this. That repositories will manage it's own caches by keys.
/// </remarks>
protected override RepositoryCacheOptions RepositoryCacheOptions
private IRepositoryCachePolicyFactory<IDictionaryItem, int> _cachePolicyFactory;
protected override IRepositoryCachePolicyFactory<IDictionaryItem, int> CachePolicyFactory
{
get
{
return new RepositoryCacheOptions
{
//If there is zero, we can cache it
GetAllCacheAllowZeroCount = true,
GetAllCacheAsCollection = false,
GetAllCacheValidateCount = false,
//dont' cache any result with GetAll - since there could be a ton
// of dictionary items.
GetAllCacheThresholdLimit = 0
};
//custom cache policy which will not cache any results for GetAll
return _cachePolicyFactory ?? (_cachePolicyFactory = new OnlySingleItemsRepositoryCachePolicyFactory<IDictionaryItem, int>(
RuntimeCache,
new RepositoryCachePolicyOptions
{
//allow zero to be cached
GetAllCacheAllowZeroCount = true
}));
}
}
@@ -354,23 +346,19 @@ namespace Umbraco.Core.Persistence.Repositories
return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("id") + " in (@ids)";
}
/// <summary>
/// Returns the repository cache options
/// </summary>
protected override RepositoryCacheOptions RepositoryCacheOptions
private IRepositoryCachePolicyFactory<IDictionaryItem, Guid> _cachePolicyFactory;
protected override IRepositoryCachePolicyFactory<IDictionaryItem, Guid> CachePolicyFactory
{
get
{
return new RepositoryCacheOptions
{
//If there is zero, we can cache it
GetAllCacheAllowZeroCount = true,
GetAllCacheAsCollection = false,
GetAllCacheValidateCount = false,
//dont' cache any result with GetAll - since there could be a ton
// of dictionary items.
GetAllCacheThresholdLimit = 0
};
//custom cache policy which will not cache any results for GetAll
return _cachePolicyFactory ?? (_cachePolicyFactory = new OnlySingleItemsRepositoryCachePolicyFactory<IDictionaryItem, Guid>(
RuntimeCache,
new RepositoryCachePolicyOptions
{
//allow zero to be cached
GetAllCacheAllowZeroCount = true
}));
}
}
}
@@ -417,23 +405,19 @@ namespace Umbraco.Core.Persistence.Repositories
return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("key") + " in (@ids)";
}
/// <summary>
/// Returns the repository cache options
/// </summary>
protected override RepositoryCacheOptions RepositoryCacheOptions
private IRepositoryCachePolicyFactory<IDictionaryItem, string> _cachePolicyFactory;
protected override IRepositoryCachePolicyFactory<IDictionaryItem, string> CachePolicyFactory
{
get
{
return new RepositoryCacheOptions
{
//If there is zero, we can cache it
GetAllCacheAllowZeroCount = true,
GetAllCacheAsCollection = false,
GetAllCacheValidateCount = false,
//dont' cache any result with GetAll - since there could be a ton
// of dictionary items.
GetAllCacheThresholdLimit = 0
};
//custom cache policy which will not cache any results for GetAll
return _cachePolicyFactory ?? (_cachePolicyFactory = new OnlySingleItemsRepositoryCachePolicyFactory<IDictionaryItem, string>(
RuntimeCache,
new RepositoryCachePolicyOptions
{
//allow zero to be cached
GetAllCacheAllowZeroCount = true
}));
}
}
}

View File

@@ -18,26 +18,19 @@ namespace Umbraco.Core.Persistence.Repositories
internal class DomainRepository : PetaPocoRepositoryBase<int, IDomain>, IDomainRepository
{
private readonly RepositoryCacheOptions _cacheOptions;
public DomainRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{
//Custom cache options for better performance
_cacheOptions = new RepositoryCacheOptions
{
GetAllCacheAllowZeroCount = true,
GetAllCacheValidateCount = false,
GetAllCacheAsCollection = true
};
{
}
/// <summary>
/// Returns the repository cache options
/// </summary>
protected override RepositoryCacheOptions RepositoryCacheOptions
private FullDataSetRepositoryCachePolicyFactory<IDomain, int> _cachePolicyFactory;
protected override IRepositoryCachePolicyFactory<IDomain, int> CachePolicyFactory
{
get { return _cacheOptions; }
get
{
//Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection
return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory<IDomain, int>(RuntimeCache));
}
}
protected override IDomain PerformGet(int id)

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
@@ -20,24 +21,17 @@ namespace Umbraco.Core.Persistence.Repositories
{
public LanguageRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{
//Custom cache options for better performance
_cacheOptions = new RepositoryCacheOptions
{
GetAllCacheAllowZeroCount = true,
GetAllCacheValidateCount = false,
GetAllCacheAsCollection = true
};
{
}
private readonly RepositoryCacheOptions _cacheOptions;
/// <summary>
/// Returns the repository cache options
/// </summary>
protected override RepositoryCacheOptions RepositoryCacheOptions
private FullDataSetRepositoryCachePolicyFactory<ILanguage, int> _cachePolicyFactory;
protected override IRepositoryCachePolicyFactory<ILanguage, int> CachePolicyFactory
{
get { return _cacheOptions; }
get
{
//Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection
return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory<ILanguage, int>(RuntimeCache));
}
}
#region Overrides of RepositoryBase<int,Language>

View File

@@ -17,20 +17,18 @@ namespace Umbraco.Core.Persistence.Repositories
{
public PublicAccessRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{
_options = new RepositoryCacheOptions
{
//We want to ensure that a zero count gets cached, even if there is nothing in the db we don't want it to lookup nothing each time
GetAllCacheAllowZeroCount = true,
//We'll use GetAll as the backing source for all queries and we'll cache the result as a single collection
GetAllCacheAsCollection = true,
//Override to false so that a Count check against the db is NOT performed when doing a GetAll without params, we just want to
// return the raw cache without validation. The GetAll on this repository gets called *A lot*, we want max performance
GetAllCacheValidateCount = false
};
{
}
private readonly RepositoryCacheOptions _options;
private FullDataSetRepositoryCachePolicyFactory<PublicAccessEntry, Guid> _cachePolicyFactory;
protected override IRepositoryCachePolicyFactory<PublicAccessEntry, Guid> CachePolicyFactory
{
get
{
//Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection
return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory<PublicAccessEntry, Guid>(RuntimeCache));
}
}
protected override PublicAccessEntry PerformGet(Guid id)
{
@@ -94,15 +92,6 @@ namespace Umbraco.Core.Persistence.Repositories
get { throw new NotImplementedException(); }
}
/// <summary>
/// Returns the repository cache options
/// </summary>
protected override RepositoryCacheOptions RepositoryCacheOptions
{
get { return _options; }
}
protected override void PersistNewItem(PublicAccessEntry entity)
{
entity.AddingEntity();

View File

@@ -82,7 +82,7 @@ namespace Umbraco.Core.Persistence.Repositories
{
}
private readonly RepositoryCacheOptions _cacheOptions = new RepositoryCacheOptions();
/// <summary>
/// The runtime cache used for this repo by default is the isolated cache for this type
@@ -92,6 +92,28 @@ namespace Umbraco.Core.Persistence.Repositories
get { return RepositoryCache.IsolatedRuntimeCache.GetOrCreateCache<TEntity>(); }
}
private IRepositoryCachePolicyFactory<TEntity, TId> _cachePolicyFactory;
/// <summary>
/// Returns the Cache Policy for the repository
/// </summary>
/// <remarks>
/// The Cache Policy determines how each entity or entity collection is cached
/// </remarks>
protected virtual IRepositoryCachePolicyFactory<TEntity, TId> CachePolicyFactory
{
get
{
return _cachePolicyFactory ?? (_cachePolicyFactory = new DefaultRepositoryCachePolicyFactory<TEntity, TId>(
RuntimeCache,
new RepositoryCachePolicyOptions(() =>
{
//Get count of all entities of current type (TEntity) to ensure cached result is correct
var query = Query<TEntity>.Builder.Where(x => x.Id != 0);
return PerformCount(query);
})));
}
}
/// <summary>
/// Adds or Updates an entity of type TEntity
/// </summary>
@@ -123,23 +145,16 @@ namespace Umbraco.Core.Persistence.Repositories
protected abstract TEntity PerformGet(TId id);
/// <summary>
/// Gets an entity by the passed in Id utilizing the repository's runtime cache
/// Gets an entity by the passed in Id utilizing the repository's cache policy
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public TEntity Get(TId id)
{
var cacheKey = GetCacheIdKey<TEntity>(id);
var fromCache = RuntimeCache.GetCacheItem<TEntity>(cacheKey);
if (fromCache != null) return fromCache;
var entity = PerformGet(id);
if (entity == null) return null;
RuntimeCache.InsertCacheItem(cacheKey, () => entity);
return entity;
using (var p = CachePolicyFactory.CreatePolicy())
{
return p.Get(id, PerformGet);
}
}
protected abstract IEnumerable<TEntity> PerformGetAll(params TId[] ids);
@@ -162,110 +177,12 @@ namespace Umbraco.Core.Persistence.Repositories
throw new InvalidOperationException("Cannot perform a query with more than 2000 parameters");
}
if (ids.Any())
using (var p = CachePolicyFactory.CreatePolicy())
{
var entities = ids.Select(x => RuntimeCache.GetCacheItem<TEntity>(GetCacheIdKey<TEntity>(x))).ToArray();
if (ids.Count().Equals(entities.Count()) && entities.Any(x => x == null) == false)
return entities;
}
else
{
TEntity[] allEntities;
if (RepositoryCacheOptions.GetAllCacheAsCollection)
{
var found = RuntimeCache.GetCacheItem<DeepCloneableList<TEntity>>(GetCacheTypeKey<TEntity>());
allEntities = found == null ? new TEntity[] {} : found.WhereNotNull().ToArray();
}
else
{
allEntities = RuntimeCache.GetCacheItemsByKeySearch<TEntity>(GetCacheTypeKey<TEntity>())
.WhereNotNull()
.ToArray();
}
if (allEntities.Any())
{
if (RepositoryCacheOptions.GetAllCacheValidateCount)
{
//Get count of all entities of current type (TEntity) to ensure cached result is correct
var query = Query<TEntity>.Builder.Where(x => x.Id != 0);
int totalCount = PerformCount(query);
if (allEntities.Count() == totalCount)
return allEntities;
}
else
{
return allEntities;
}
}
else if (RepositoryCacheOptions.GetAllCacheAllowZeroCount)
{
//if the repository allows caching a zero count, then check the zero count cache
var zeroCount = RuntimeCache.GetCacheItem<TEntity[]>(GetCacheTypeKey<TEntity>());
if (zeroCount != null && zeroCount.Any() == false)
{
//there is a zero count cache so return an empty list
return Enumerable.Empty<TEntity>();
}
}
}
var entityCollection = PerformGetAll(ids)
//ensure we don't include any null refs in the returned collection!
.WhereNotNull()
.ToArray();
//This option cannot execute if we are looking up specific Ids
if (ids.Any() == false && entityCollection.Length == 0 && RepositoryCacheOptions.GetAllCacheAllowZeroCount)
{
//there was nothing returned but we want to cache a zero count result so add an TEntity[] to the cache
// to signify that there is a zero count cache
RuntimeCache.InsertCacheItem(GetCacheTypeKey<TEntity>(), () => new TEntity[] {});
return entityCollection;
}
//This option cannot execute if we are looking up specific Ids
if (ids.Any() == false && RepositoryCacheOptions.GetAllCacheAsCollection)
{
//when this is true, we don't want to cache each item individually, we want to cache the result as a single collection
RuntimeCache.InsertCacheItem(GetCacheTypeKey<TEntity>(), () => new DeepCloneableList<TEntity>(entityCollection));
return entityCollection;
}
if (entityCollection.Length > RepositoryCacheOptions.GetAllCacheThresholdLimit)
{
//We need to put a threshold here! IF there's an insane amount of items
// coming back here we don't want to chuck it all into memory, this added cache here
// is more for convenience when paging stuff temporarily
return entityCollection;
}
//This is the default behavior, we'll individually cache each item so that if/when these items are resolved
// by id, they are returned from the already existing cache.
foreach (var entity in entityCollection)
{
if (entity != null)
{
var localCopy = entity;
RuntimeCache.InsertCacheItem(GetCacheIdKey<TEntity>(entity.Id), () => localCopy);
}
}
return entityCollection;
return p.GetAll(ids, PerformGetAll);
}
}
/// <summary>
/// Returns the repository cache options
/// </summary>
protected virtual RepositoryCacheOptions RepositoryCacheOptions
{
get { return _cacheOptions; }
}
protected abstract IEnumerable<TEntity> PerformGetByQuery(IQuery<TEntity> query);
/// <summary>
/// Gets a list of entities by the passed in query
@@ -287,12 +204,10 @@ namespace Umbraco.Core.Persistence.Repositories
/// <returns></returns>
public bool Exists(TId id)
{
var fromCache = RuntimeCache.GetCacheItem<TEntity>(GetCacheIdKey<TEntity>(id));
if (fromCache != null)
using (var p = CachePolicyFactory.CreatePolicy())
{
return true;
return p.Exists(id, PerformExists);
}
return PerformExists(id);
}
protected abstract int PerformCount(IQuery<TEntity> query);
@@ -312,23 +227,12 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="entity"></param>
public virtual void PersistNewItem(IEntity entity)
{
try
{
PersistNewItem((TEntity)entity);
RuntimeCache.InsertCacheItem(GetCacheIdKey<TEntity>(entity.Id), () => entity);
//If there's a GetAll zero count cache, ensure it is cleared
RuntimeCache.ClearCacheItem(GetCacheTypeKey<TEntity>());
}
catch (Exception ex)
{
//if an exception is thrown we need to remove the entry from cache, this is ONLY a work around because of the way
// that we cache entities: http://issues.umbraco.org/issue/U4-4259
RuntimeCache.ClearCacheItem(GetCacheIdKey<TEntity>(entity.Id));
//If there's a GetAll zero count cache, ensure it is cleared
RuntimeCache.ClearCacheItem(GetCacheTypeKey<TEntity>());
throw;
}
var casted = (TEntity)entity;
using (var p = CachePolicyFactory.CreatePolicy())
{
p.CreateOrUpdate(casted, PersistNewItem);
}
}
/// <summary>
@@ -337,23 +241,12 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="entity"></param>
public virtual void PersistUpdatedItem(IEntity entity)
{
try
{
PersistUpdatedItem((TEntity)entity);
RuntimeCache.InsertCacheItem(GetCacheIdKey<TEntity>(entity.Id), () => entity);
//If there's a GetAll zero count cache, ensure it is cleared
RuntimeCache.ClearCacheItem(GetCacheTypeKey<TEntity>());
}
catch (Exception)
{
//if an exception is thrown we need to remove the entry from cache, this is ONLY a work around because of the way
// that we cache entities: http://issues.umbraco.org/issue/U4-4259
RuntimeCache.ClearCacheItem(GetCacheIdKey<TEntity>(entity.Id));
//If there's a GetAll zero count cache, ensure it is cleared
RuntimeCache.ClearCacheItem(GetCacheTypeKey<TEntity>());
throw;
}
var casted = (TEntity)entity;
using (var p = CachePolicyFactory.CreatePolicy())
{
p.CreateOrUpdate(casted, PersistUpdatedItem);
}
}
/// <summary>
@@ -362,10 +255,12 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="entity"></param>
public virtual void PersistDeletedItem(IEntity entity)
{
PersistDeletedItem((TEntity)entity);
RuntimeCache.ClearCacheItem(GetCacheIdKey<TEntity>(entity.Id));
//If there's a GetAll zero count cache, ensure it is cleared
RuntimeCache.ClearCacheItem(GetCacheTypeKey<TEntity>());
var casted = (TEntity)entity;
using (var p = CachePolicyFactory.CreatePolicy())
{
p.Remove(casted, PersistDeletedItem);
}
}

View File

@@ -4,6 +4,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
@@ -32,7 +33,6 @@ namespace Umbraco.Core.Persistence.Repositories
private readonly ITemplatesSection _templateConfig;
private readonly ViewHelper _viewHelper;
private readonly MasterPageHelper _masterPageHelper;
private readonly RepositoryCacheOptions _cacheOptions;
internal TemplateRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IFileSystem masterpageFileSystem, IFileSystem viewFileSystem, ITemplatesSection templateConfig)
: base(work, cache, logger, sqlSyntax)
@@ -41,25 +41,18 @@ namespace Umbraco.Core.Persistence.Repositories
_viewsFileSystem = viewFileSystem;
_templateConfig = templateConfig;
_viewHelper = new ViewHelper(_viewsFileSystem);
_masterPageHelper = new MasterPageHelper(_masterpagesFileSystem);
_cacheOptions = new RepositoryCacheOptions
{
//Allow a zero count cache entry because GetAll() gets used quite a lot and we want to ensure
// if there are no templates, that it doesn't keep going to the db.
GetAllCacheAllowZeroCount = true,
//GetAll is used as the base call for getting all templates, so we'll cache it as a single entry
GetAllCacheAsCollection = true
};
_masterPageHelper = new MasterPageHelper(_masterpagesFileSystem);
}
/// <summary>
/// Returns the repository cache options
/// </summary>
protected override RepositoryCacheOptions RepositoryCacheOptions
private FullDataSetRepositoryCachePolicyFactory<ITemplate, int> _cachePolicyFactory;
protected override IRepositoryCachePolicyFactory<ITemplate, int> CachePolicyFactory
{
get { return _cacheOptions; }
get
{
//Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection
return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory<ITemplate, int>(RuntimeCache));
}
}
#region Overrides of RepositoryBase<int,ITemplate>

View File

@@ -156,12 +156,18 @@
<Compile Include="CacheHelper.cs" />
<Compile Include="Cache\CacheKeys.cs" />
<Compile Include="Cache\CacheProviderExtensions.cs" />
<Compile Include="Cache\DefaultRepositoryCachePolicy.cs" />
<Compile Include="Cache\DefaultRepositoryCachePolicyFactory.cs" />
<Compile Include="Cache\FullDataSetRepositoryCachePolicy.cs" />
<Compile Include="Cache\FullDataSetRepositoryCachePolicyFactory.cs" />
<Compile Include="Cache\ICacheProvider.cs" />
<Compile Include="Cache\CacheRefresherBase.cs" />
<Compile Include="Cache\CacheRefresherEventArgs.cs" />
<Compile Include="Cache\DictionaryCacheProviderBase.cs" />
<Compile Include="Cache\HttpRequestCacheProvider.cs" />
<Compile Include="Cache\IRepositoryCachePolicy.cs" />
<Compile Include="Cache\IPayloadCacheRefresher.cs" />
<Compile Include="Cache\IRepositoryCachePolicyFactory.cs" />
<Compile Include="Cache\IsolatedRuntimeCache.cs" />
<Compile Include="Cache\ObjectCacheRuntimeCacheProvider.cs" />
<Compile Include="Cache\IRuntimeCacheProvider.cs" />
@@ -169,7 +175,10 @@
<Compile Include="Cache\IJsonCacheRefresher.cs" />
<Compile Include="Cache\JsonCacheRefresherBase.cs" />
<Compile Include="Cache\NullCacheProvider.cs" />
<Compile Include="Cache\OnlySingleItemsRepositoryCachePolicy.cs" />
<Compile Include="Cache\OnlySingleItemsRepositoryCachePolicyFactory.cs" />
<Compile Include="Cache\PayloadCacheRefresherBase.cs" />
<Compile Include="Cache\RepositoryCachePolicyOptions.cs" />
<Compile Include="Cache\StaticCacheProvider.cs" />
<Compile Include="Cache\TypedCacheRefresherBase.cs" />
<Compile Include="CodeAnnotations\FriendlyNameAttribute.cs" />
@@ -457,7 +466,6 @@
<Compile Include="Persistence\Repositories\Interfaces\ITaskTypeRepository.cs" />
<Compile Include="Persistence\Repositories\MigrationEntryRepository.cs" />
<Compile Include="Persistence\Repositories\PublicAccessRepository.cs" />
<Compile Include="Persistence\Repositories\RepositoryCacheOptions.cs" />
<Compile Include="Persistence\Repositories\TaskRepository.cs" />
<Compile Include="Persistence\Repositories\TaskTypeRepository.cs" />
<Compile Include="PropertyEditors\ValueConverters\GridValueConverter.cs" />