diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs
new file mode 100644
index 0000000000..c887af6ef0
--- /dev/null
+++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs
@@ -0,0 +1,222 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// The default cache policy for retrieving a single entity
+ ///
+ ///
+ ///
+ internal class DefaultRepositoryCachePolicy : DisposableObject, IRepositoryCachePolicy
+ 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 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 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 getFromRepo)
+ {
+ var cacheKey = GetCacheIdKey(id);
+ var fromCache = Cache.GetCacheItem(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(cacheKey);
+ }
+
+ public bool Exists(TId id, Func getFromRepo)
+ {
+ var cacheKey = GetCacheIdKey(id);
+ var fromCache = Cache.GetCacheItem(cacheKey);
+ return fromCache != null || getFromRepo(id);
+ }
+
+ public virtual TEntity[] GetAll(TId[] ids, Func> 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(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;
+ }
+
+ ///
+ /// Performs the lookup for all entities of this type from the cache
+ ///
+ ///
+ protected virtual TEntity[] GetAllFromCache()
+ {
+ var allEntities = Cache.GetCacheItemsByKeySearch(GetCacheTypeKey())
+ .WhereNotNull()
+ .ToArray();
+ return allEntities.Any() ? allEntities : new TEntity[] {};
+ }
+
+ ///
+ /// The disposal performs the caching
+ ///
+ protected override void DisposeResources()
+ {
+ if (_action != null)
+ {
+ _action();
+ }
+ }
+
+ ///
+ /// Sets the action to execute on disposal for a single entity
+ ///
+ ///
+ ///
+ protected virtual void SetCacheAction(string cacheKey, TEntity entity)
+ {
+ SetCacheAction(() => Cache.InsertCacheItem(cacheKey, () => entity));
+ }
+
+ ///
+ /// Sets the action to execute on disposal for an entity collection
+ ///
+ ///
+ ///
+ 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);
+ }
+ }
+ });
+ }
+
+ ///
+ /// Sets the action to execute on disposal
+ ///
+ ///
+ protected void SetCacheAction(Action action)
+ {
+ _action = action;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicyFactory.cs
new file mode 100644
index 0000000000..5c02e41a48
--- /dev/null
+++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicyFactory.cs
@@ -0,0 +1,27 @@
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// Creates cache policies
+ ///
+ ///
+ ///
+ internal class DefaultRepositoryCachePolicyFactory : IRepositoryCachePolicyFactory
+ 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 CreatePolicy()
+ {
+ return new DefaultRepositoryCachePolicy(_runtimeCache, _options);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs
new file mode 100644
index 0000000000..c4c86b2ec7
--- /dev/null
+++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs
@@ -0,0 +1,57 @@
+using System.Linq;
+using Umbraco.Core.Collections;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// A caching policy that caches an entire dataset as a single collection
+ ///
+ ///
+ ///
+ internal class FullDataSetRepositoryCachePolicy : DefaultRepositoryCachePolicy
+ where TEntity : class, IAggregateRoot
+ {
+ public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache) : base(cache, new RepositoryCachePolicyOptions())
+ {
+ }
+
+ ///
+ /// For this type of caching policy, we don't cache individual items
+ ///
+ ///
+ ///
+ protected override void SetCacheAction(string cacheKey, TEntity entity)
+ {
+ //do nothing
+ }
+
+ ///
+ /// Sets the action to execute on disposal for an entity collection
+ ///
+ ///
+ ///
+ 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(entityCollection));
+ });
+ }
+
+ ///
+ /// This policy will cache the full data set as a single collection
+ ///
+ ///
+ protected override TEntity[] GetAllFromCache()
+ {
+ var found = Cache.GetCacheItem>(GetCacheTypeKey());
+ return found == null ? new TEntity[] { } : found.WhereNotNull().ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs
new file mode 100644
index 0000000000..470db33b6a
--- /dev/null
+++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs
@@ -0,0 +1,25 @@
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// Creates cache policies
+ ///
+ ///
+ ///
+ internal class FullDataSetRepositoryCachePolicyFactory : IRepositoryCachePolicyFactory
+ where TEntity : class, IAggregateRoot
+ {
+ private readonly IRuntimeCacheProvider _runtimeCache;
+
+ public FullDataSetRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache)
+ {
+ _runtimeCache = runtimeCache;
+ }
+
+ public virtual IRepositoryCachePolicy CreatePolicy()
+ {
+ return new FullDataSetRepositoryCachePolicy(_runtimeCache);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs
new file mode 100644
index 0000000000..97844933b7
--- /dev/null
+++ b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Cache
+{
+ internal interface IRepositoryCachePolicy : IDisposable
+ where TEntity : class, IAggregateRoot
+ {
+ TEntity Get(TId id, Func getFromRepo);
+ TEntity Get(TId id);
+ bool Exists(TId id, Func getFromRepo);
+
+ string GetCacheIdKey(object id);
+ string GetCacheTypeKey();
+ void CreateOrUpdate(TEntity entity, Action persistMethod);
+ void Remove(TEntity entity, Action persistMethod);
+ TEntity[] GetAll(TId[] ids, Func> getFromRepo);
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/IRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/IRepositoryCachePolicyFactory.cs
new file mode 100644
index 0000000000..2d69704b63
--- /dev/null
+++ b/src/Umbraco.Core/Cache/IRepositoryCachePolicyFactory.cs
@@ -0,0 +1,9 @@
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Cache
+{
+ internal interface IRepositoryCachePolicyFactory where TEntity : class, IAggregateRoot
+ {
+ IRepositoryCachePolicy CreatePolicy();
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/OnlySingleItemsRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/OnlySingleItemsRepositoryCachePolicy.cs
new file mode 100644
index 0000000000..4aeca15863
--- /dev/null
+++ b/src/Umbraco.Core/Cache/OnlySingleItemsRepositoryCachePolicy.cs
@@ -0,0 +1,24 @@
+using System.Linq;
+using Umbraco.Core.Collections;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// A caching policy that ignores all caches for GetAll - it will only cache calls for individual items
+ ///
+ ///
+ ///
+ internal class OnlySingleItemsRepositoryCachePolicy : DefaultRepositoryCachePolicy
+ where TEntity : class, IAggregateRoot
+ {
+ public OnlySingleItemsRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options) : base(cache, options)
+ {
+ }
+
+ protected override void SetCacheAction(TId[] ids, TEntity[] entityCollection)
+ {
+ //do nothing
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/OnlySingleItemsRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/OnlySingleItemsRepositoryCachePolicyFactory.cs
new file mode 100644
index 0000000000..3559ce6ea5
--- /dev/null
+++ b/src/Umbraco.Core/Cache/OnlySingleItemsRepositoryCachePolicyFactory.cs
@@ -0,0 +1,27 @@
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// Creates cache policies
+ ///
+ ///
+ ///
+ internal class OnlySingleItemsRepositoryCachePolicyFactory : IRepositoryCachePolicyFactory
+ 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 CreatePolicy()
+ {
+ return new OnlySingleItemsRepositoryCachePolicy(_runtimeCache, _options);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheOptions.cs b/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs
similarity index 53%
rename from src/Umbraco.Core/Persistence/Repositories/RepositoryCacheOptions.cs
rename to src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs
index b5abedac86..e8c6ac02b0 100644
--- a/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheOptions.cs
+++ b/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs
@@ -1,18 +1,34 @@
-namespace Umbraco.Core.Persistence.Repositories
+using System;
+
+namespace Umbraco.Core.Cache
{
- internal class RepositoryCacheOptions
+ internal class RepositoryCachePolicyOptions
{
///
- /// Constructor sets defaults
+ /// Ctor - sets GetAllCacheValidateCount = true
///
- public RepositoryCacheOptions()
+ public RepositoryCachePolicyOptions(Func performCount)
{
+ PerformCount = performCount;
GetAllCacheValidateCount = true;
GetAllCacheAllowZeroCount = false;
- GetAllCacheThresholdLimit = 100;
- GetAllCacheAsCollection = false;
}
+ ///
+ /// Ctor - sets GetAllCacheValidateCount = false
+ ///
+ public RepositoryCachePolicyOptions()
+ {
+ PerformCount = null;
+ GetAllCacheValidateCount = false;
+ GetAllCacheAllowZeroCount = false;
+ }
+
+ ///
+ /// Callback required to get count for GetAllCacheValidateCount
+ ///
+ public Func PerformCount { get; private set; }
+
///
/// 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
///
- public bool GetAllCacheValidateCount { get; set; }
+ public bool GetAllCacheValidateCount { get; private set; }
///
/// 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
///
public bool GetAllCacheAllowZeroCount { get; set; }
-
- ///
- /// The threshold entity count for which the GetAll method will cache entities
- ///
- public int GetAllCacheThresholdLimit { get; set; }
-
- ///
- /// 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
- ///
- ///
- /// The default is false which means that if the result of GetAll is less than the GetAllCacheThresholdLimit, each entity
- /// returned will be cached individually
- ///
- public bool GetAllCacheAsCollection { get; set; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs
index 4f588f42db..540476f93e 100644
--- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs
@@ -28,27 +28,19 @@ namespace Umbraco.Core.Persistence.Repositories
_languageRepository = languageRepository;
}
- ///
- /// Returns the repository cache options
- ///
- ///
- /// 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.
- ///
- protected override RepositoryCacheOptions RepositoryCacheOptions
+ private IRepositoryCachePolicyFactory _cachePolicyFactory;
+ protected override IRepositoryCachePolicyFactory 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(
+ 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)";
}
- ///
- /// Returns the repository cache options
- ///
- protected override RepositoryCacheOptions RepositoryCacheOptions
+ private IRepositoryCachePolicyFactory _cachePolicyFactory;
+ protected override IRepositoryCachePolicyFactory 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(
+ 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)";
}
- ///
- /// Returns the repository cache options
- ///
- protected override RepositoryCacheOptions RepositoryCacheOptions
+ private IRepositoryCachePolicyFactory _cachePolicyFactory;
+ protected override IRepositoryCachePolicyFactory 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(
+ RuntimeCache,
+ new RepositoryCachePolicyOptions
+ {
+ //allow zero to be cached
+ GetAllCacheAllowZeroCount = true
+ }));
}
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs
index 75a592c7fe..6abab73dd7 100644
--- a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs
@@ -18,26 +18,19 @@ namespace Umbraco.Core.Persistence.Repositories
internal class DomainRepository : PetaPocoRepositoryBase, 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
- };
+ {
}
- ///
- /// Returns the repository cache options
- ///
- protected override RepositoryCacheOptions RepositoryCacheOptions
+ private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory;
+ protected override IRepositoryCachePolicyFactory 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(RuntimeCache));
+ }
}
protected override IDomain PerformGet(int id)
diff --git a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs
index 53bf422d99..6fc9bd5ebc 100644
--- a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs
@@ -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;
-
- ///
- /// Returns the repository cache options
- ///
- protected override RepositoryCacheOptions RepositoryCacheOptions
+ private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory;
+ protected override IRepositoryCachePolicyFactory 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(RuntimeCache));
+ }
}
#region Overrides of RepositoryBase
diff --git a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs
index d1127e4ac6..1086b9cee0 100644
--- a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs
@@ -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 _cachePolicyFactory;
+ protected override IRepositoryCachePolicyFactory CachePolicyFactory
+ {
+ get
+ {
+ //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection
+ return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache));
+ }
+ }
protected override PublicAccessEntry PerformGet(Guid id)
{
@@ -94,15 +92,6 @@ namespace Umbraco.Core.Persistence.Repositories
get { throw new NotImplementedException(); }
}
- ///
- /// Returns the repository cache options
- ///
- protected override RepositoryCacheOptions RepositoryCacheOptions
- {
- get { return _options; }
- }
-
-
protected override void PersistNewItem(PublicAccessEntry entity)
{
entity.AddingEntity();
diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs
index c289802028..f6bcc46d06 100644
--- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs
@@ -82,7 +82,7 @@ namespace Umbraco.Core.Persistence.Repositories
{
}
- private readonly RepositoryCacheOptions _cacheOptions = new RepositoryCacheOptions();
+
///
/// 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(); }
}
+ private IRepositoryCachePolicyFactory _cachePolicyFactory;
+ ///
+ /// Returns the Cache Policy for the repository
+ ///
+ ///
+ /// The Cache Policy determines how each entity or entity collection is cached
+ ///
+ protected virtual IRepositoryCachePolicyFactory CachePolicyFactory
+ {
+ get
+ {
+ return _cachePolicyFactory ?? (_cachePolicyFactory = new DefaultRepositoryCachePolicyFactory(
+ RuntimeCache,
+ new RepositoryCachePolicyOptions(() =>
+ {
+ //Get count of all entities of current type (TEntity) to ensure cached result is correct
+ var query = Query.Builder.Where(x => x.Id != 0);
+ return PerformCount(query);
+ })));
+ }
+ }
+
///
/// Adds or Updates an entity of type TEntity
///
@@ -123,23 +145,16 @@ namespace Umbraco.Core.Persistence.Repositories
protected abstract TEntity PerformGet(TId id);
///
- /// 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
///
///
///
public TEntity Get(TId id)
{
- var cacheKey = GetCacheIdKey(id);
- var fromCache = RuntimeCache.GetCacheItem(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 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(GetCacheIdKey(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>(GetCacheTypeKey());
- allEntities = found == null ? new TEntity[] {} : found.WhereNotNull().ToArray();
- }
- else
- {
- allEntities = RuntimeCache.GetCacheItemsByKeySearch(GetCacheTypeKey())
- .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.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(GetCacheTypeKey());
- if (zeroCount != null && zeroCount.Any() == false)
- {
- //there is a zero count cache so return an empty list
- return Enumerable.Empty();
- }
- }
-
- }
-
- 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(), () => 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(), () => new DeepCloneableList(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(entity.Id), () => localCopy);
- }
- }
-
- return entityCollection;
+ return p.GetAll(ids, PerformGetAll);
+ }
}
-
- ///
- /// Returns the repository cache options
- ///
- protected virtual RepositoryCacheOptions RepositoryCacheOptions
- {
- get { return _cacheOptions; }
- }
-
+
protected abstract IEnumerable PerformGetByQuery(IQuery query);
///
/// Gets a list of entities by the passed in query
@@ -287,12 +204,10 @@ namespace Umbraco.Core.Persistence.Repositories
///
public bool Exists(TId id)
{
- var fromCache = RuntimeCache.GetCacheItem(GetCacheIdKey(id));
- if (fromCache != null)
+ using (var p = CachePolicyFactory.CreatePolicy())
{
- return true;
+ return p.Exists(id, PerformExists);
}
- return PerformExists(id);
}
protected abstract int PerformCount(IQuery query);
@@ -312,23 +227,12 @@ namespace Umbraco.Core.Persistence.Repositories
///
public virtual void PersistNewItem(IEntity entity)
{
- try
- {
- PersistNewItem((TEntity)entity);
- RuntimeCache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity);
- //If there's a GetAll zero count cache, ensure it is cleared
- RuntimeCache.ClearCacheItem(GetCacheTypeKey());
- }
- 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(entity.Id));
- //If there's a GetAll zero count cache, ensure it is cleared
- RuntimeCache.ClearCacheItem(GetCacheTypeKey());
- throw;
- }
+ var casted = (TEntity)entity;
+ using (var p = CachePolicyFactory.CreatePolicy())
+ {
+ p.CreateOrUpdate(casted, PersistNewItem);
+ }
}
///
@@ -337,23 +241,12 @@ namespace Umbraco.Core.Persistence.Repositories
///
public virtual void PersistUpdatedItem(IEntity entity)
{
- try
- {
- PersistUpdatedItem((TEntity)entity);
- RuntimeCache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity);
- //If there's a GetAll zero count cache, ensure it is cleared
- RuntimeCache.ClearCacheItem(GetCacheTypeKey());
- }
- 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(entity.Id));
- //If there's a GetAll zero count cache, ensure it is cleared
- RuntimeCache.ClearCacheItem(GetCacheTypeKey());
- throw;
- }
+ var casted = (TEntity)entity;
+ using (var p = CachePolicyFactory.CreatePolicy())
+ {
+ p.CreateOrUpdate(casted, PersistUpdatedItem);
+ }
}
///
@@ -362,10 +255,12 @@ namespace Umbraco.Core.Persistence.Repositories
///
public virtual void PersistDeletedItem(IEntity entity)
{
- PersistDeletedItem((TEntity)entity);
- RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.Id));
- //If there's a GetAll zero count cache, ensure it is cleared
- RuntimeCache.ClearCacheItem(GetCacheTypeKey());
+ var casted = (TEntity)entity;
+
+ using (var p = CachePolicyFactory.CreatePolicy())
+ {
+ p.Remove(casted, PersistDeletedItem);
+ }
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs
index 195672acb4..955e316bc3 100644
--- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs
@@ -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);
}
- ///
- /// Returns the repository cache options
- ///
- protected override RepositoryCacheOptions RepositoryCacheOptions
+ private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory;
+ protected override IRepositoryCachePolicyFactory 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(RuntimeCache));
+ }
}
#region Overrides of RepositoryBase
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index dd83553074..baa614c3ce 100644
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -156,12 +156,18 @@
+
+
+
+
+
+
@@ -169,7 +175,10 @@
+
+
+
@@ -457,7 +466,6 @@
-