diff --git a/build/BuildBelle.bat b/build/BuildBelle.bat
index 0d58204f65..6c11cc9fc5 100644
--- a/build/BuildBelle.bat
+++ b/build/BuildBelle.bat
@@ -1,4 +1,6 @@
@ECHO OFF
+SETLOCAL
+
SET release=%1
ECHO Installing Npm NuGet Package
@@ -11,12 +13,9 @@ ECHO Current folder: %CD%
for /f "delims=" %%A in ('dir %nuGetFolder%node.js.* /b') do set "nodePath=%nuGetFolder%%%A\"
for /f "delims=" %%A in ('dir %nuGetFolder%npm.js.* /b') do set "npmPath=%nuGetFolder%%%A\tools\"
-ECHO Temporarily adding Npm and Node to path
-SET oldPath=%PATH%
-
-path=%npmPath%;%nodePath%;%PATH%
-
-ECHO %path%
+ECHO Adding Npm and Node to path
+REM SETLOCAL is on, so changes to the path not persist to the actual user's path
+PATH=%npmPath%;%nodePath%;%PATH%
SET buildFolder=%CD%
@@ -29,8 +28,5 @@ call npm install -g grunt-cli --quiet
call npm install -g bower --quiet
call grunt build --buildversion=%release%
-ECHO Reset path to what it was before
-path=%oldPath%
-
ECHO Move back to the build folder
CD %buildFolder%
\ No newline at end of file
diff --git a/build/InstallGit.cmd b/build/InstallGit.cmd
index 2bd6d7cc35..b6ba71df9b 100644
--- a/build/InstallGit.cmd
+++ b/build/InstallGit.cmd
@@ -1,5 +1,6 @@
@ECHO OFF
-SET oldPath=%PATH%
+SETLOCAL
+REM SETLOCAL is on, so changes to the path not persist to the actual user's path
git.exe 2> NUL
if %ERRORLEVEL%==9009 GOTO :trydefaultpath
@@ -12,8 +13,7 @@ if %ERRORLEVEL%==9009 GOTO :showerror
GOTO :EOF
:showerror
-path=%oldPath%
-ECHO Git is not in your path and could not be found in C:\Program Files (x86)\Git\cmd
+ECHO Git is not in your path and could not be found in C:\Program Files (x86)\Git\cmd nor in C:\Program Files\Git\cmd
set /p install=" Do you want to install Git through Chocolatey [y/n]? " %=%
if %install%==y (
GOTO :installgit
@@ -29,5 +29,4 @@ GOTO :EOF
ECHO Installing Chocolatey first
@powershell -NoProfile -ExecutionPolicy unrestricted -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin
ECHO Installing Git through Chocolatey
-choco install git
-path=C:\Program Files (x86)\Git\cmd;%path%
+choco install git
\ No newline at end of file
diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec
index 6852b04510..148e65d369 100644
--- a/build/NuSpecs/UmbracoCms.Core.nuspec
+++ b/build/NuSpecs/UmbracoCms.Core.nuspec
@@ -33,8 +33,8 @@
-
-
+
+
diff --git a/build/NuSpecs/tools/trees.config.install.xdt b/build/NuSpecs/tools/trees.config.install.xdt
index f5a807b4bf..580c619547 100644
--- a/build/NuSpecs/tools/trees.config.install.xdt
+++ b/build/NuSpecs/tools/trees.config.install.xdt
@@ -95,19 +95,19 @@
xdt:Transform="SetAttributes()" />
-
-
-
diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs
index 28b314750a..3d330cc173 100644
--- a/src/SolutionInfo.cs
+++ b/src/SolutionInfo.cs
@@ -2,7 +2,7 @@
using System.Resources;
[assembly: AssemblyCompany("Umbraco")]
-[assembly: AssemblyCopyright("Copyright © Umbraco 2015")]
+[assembly: AssemblyCopyright("Copyright © Umbraco 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs
index a05abe2a50..e47ef04650 100644
--- a/src/Umbraco.Core/ApplicationContext.cs
+++ b/src/Umbraco.Core/ApplicationContext.cs
@@ -406,7 +406,8 @@ namespace Umbraco.Core
//clear the cache
if (ApplicationCache != null)
{
- ApplicationCache.ClearAllCache();
+ ApplicationCache.RuntimeCache.ClearAllCache();
+ ApplicationCache.IsolatedRuntimeCache.ClearAllCaches();
}
//reset all resolvers
ResolverCollection.ResetAll();
diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs
index 88d570beff..0c1a202b66 100644
--- a/src/Umbraco.Core/Cache/CacheKeys.cs
+++ b/src/Umbraco.Core/Cache/CacheKeys.cs
@@ -1,8 +1,9 @@
using System;
+using System.ComponentModel;
+using Umbraco.Core.CodeAnnotations;
namespace Umbraco.Core.Cache
{
-
///
/// Constants storing cache keys used in caching
///
@@ -12,52 +13,78 @@ namespace Umbraco.Core.Cache
public const string ApplicationsCacheKey = "ApplicationCache";
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string UserTypeCacheKey = "UserTypeCache";
+ [Obsolete("This is no longer used and will be removed from the codebase in the future - it is referenced but no cache is stored against this key")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string ContentItemCacheKey = "contentItem";
+ [UmbracoWillObsolete("This cache key is only used for the legacy 'library' caching, remove in v8")]
public const string MediaCacheKey = "UL_GetMedia";
public const string MacroXsltCacheKey = "macroXslt_";
+
+ [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
public const string MacroCacheKey = "UmbracoMacroCache";
+
public const string MacroHtmlCacheKey = "macroHtml_";
public const string MacroControlCacheKey = "macroControl_";
public const string MacroHtmlDateAddedCacheKey = "macroHtml_DateAdded_";
public const string MacroControlDateAddedCacheKey = "macroControl_DateAdded_";
+ [UmbracoWillObsolete("This cache key is only used for legacy 'library' member caching, remove in v8")]
public const string MemberLibraryCacheKey = "UL_GetMember";
+
+ [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
public const string MemberBusinessLogicCacheKey = "MemberCacheItem_";
-
+
+ [UmbracoWillObsolete("This cache key is only used for legacy template business logic caching, remove in v8")]
public const string TemplateFrontEndCacheKey = "template";
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string TemplateBusinessLogicCacheKey = "UmbracoTemplateCache";
+ [Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string UserContextCacheKey = "UmbracoUserContext";
+
public const string UserContextTimeoutCacheKey = "UmbracoUserContextTimeout";
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string UserCacheKey = "UmbracoUser";
public const string UserPermissionsCacheKey = "UmbracoUserPermissions";
+ [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
public const string ContentTypeCacheKey = "UmbracoContentType";
+ [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
public const string ContentTypePropertiesCacheKey = "ContentType_PropertyTypes_Content:";
+ [UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
public const string PropertyTypeCacheKey = "UmbracoPropertyTypeCache";
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string LanguageCacheKey = "UmbracoLanguageCache";
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string DomainCacheKey = "UmbracoDomainList";
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string StylesheetCacheKey = "UmbracoStylesheet";
+
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string StylesheetPropertyCacheKey = "UmbracoStylesheetProperty";
+ [Obsolete("This is no longer used and will be removed from the codebase in the future")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public const string DataTypeCacheKey = "UmbracoDataTypeDefinition";
public const string DataTypePreValuesCacheKey = "UmbracoPreVal";
diff --git a/src/Umbraco.Core/Cache/CacheRefresherBase.cs b/src/Umbraco.Core/Cache/CacheRefresherBase.cs
index 2931805b08..60ad69b6fc 100644
--- a/src/Umbraco.Core/Cache/CacheRefresherBase.cs
+++ b/src/Umbraco.Core/Cache/CacheRefresherBase.cs
@@ -2,6 +2,7 @@
using Umbraco.Core.Events;
using Umbraco.Core.Sync;
using umbraco.interfaces;
+using Umbraco.Core.Models.EntityBase;
namespace Umbraco.Core.Cache
{
@@ -63,5 +64,15 @@ namespace Umbraco.Core.Cache
{
OnCacheUpdated(Instance, new CacheRefresherEventArgs(id, MessageType.RefreshById));
}
+
+ ///
+ /// Clears the cache for all repository entities of this type
+ ///
+ ///
+ internal void ClearAllIsolatedCacheByEntityType()
+ where TEntity : class, IAggregateRoot
+ {
+ ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.ClearCache();
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs
similarity index 98%
rename from src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs
rename to src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs
index 0b5b42660d..861c6b803e 100644
--- a/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs
+++ b/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs
@@ -2,11 +2,10 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Caching;
-using Umbraco.Core.Cache;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
-namespace Umbraco.Core.Persistence.Repositories
+namespace Umbraco.Core.Cache
{
///
/// A wrapper for any IRuntimeCacheProvider that ensures that all inserts and returns
diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs
new file mode 100644
index 0000000000..fda57cefee
--- /dev/null
+++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs
@@ -0,0 +1,232 @@
+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 GetAllCacheAllowZeroCount cache, ensure it is cleared
+ Cache.ClearCacheItem(GetCacheTypeKey());
+ });
+
+ }
+ catch
+ {
+ //set the disposal action
+ SetCacheAction(() =>
+ {
+ //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 GetAllCacheAllowZeroCount 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 GetAllCacheAllowZeroCount 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/IsolatedRuntimeCache.cs b/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs
new file mode 100644
index 0000000000..103f90345d
--- /dev/null
+++ b/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Concurrent;
+
+namespace Umbraco.Core.Cache
+{
+ ///
+ /// Used to get/create/manipulate isolated runtime cache
+ ///
+ ///
+ /// This is useful for repository level caches to ensure that cache lookups by key are fast so
+ /// that the repository doesn't need to search through all keys on a global scale.
+ ///
+ public class IsolatedRuntimeCache
+ {
+ private readonly Func _cacheFactory;
+
+ ///
+ /// Constructor that allows specifying a factory for the type of runtime isolated cache to create
+ ///
+ ///
+ public IsolatedRuntimeCache(Func cacheFactory)
+ {
+ _cacheFactory = cacheFactory;
+ }
+
+ private readonly ConcurrentDictionary _isolatedCache = new ConcurrentDictionary();
+
+ ///
+ /// Returns an isolated runtime cache for a given type
+ ///
+ ///
+ ///
+ public IRuntimeCacheProvider GetOrCreateCache()
+ {
+ return _isolatedCache.GetOrAdd(typeof(T), type => _cacheFactory(type));
+ }
+
+ ///
+ /// Returns an isolated runtime cache for a given type
+ ///
+ ///
+ public IRuntimeCacheProvider GetOrCreateCache(Type type)
+ {
+ return _isolatedCache.GetOrAdd(type, t => _cacheFactory(t));
+ }
+
+ ///
+ /// Tries to get a cache by the type specified
+ ///
+ ///
+ ///
+ public Attempt GetCache()
+ {
+ IRuntimeCacheProvider cache;
+ if (_isolatedCache.TryGetValue(typeof(T), out cache))
+ {
+ return Attempt.Succeed(cache);
+ }
+ return Attempt.Fail();
+ }
+
+ ///
+ /// Clears all values inside this isolated runtime cache
+ ///
+ ///
+ ///
+ public void ClearCache()
+ {
+ IRuntimeCacheProvider cache;
+ if (_isolatedCache.TryGetValue(typeof(T), out cache))
+ {
+ cache.ClearAllCache();
+ }
+ }
+
+ ///
+ /// Clears all of the isolated caches
+ ///
+ public void ClearAllCaches()
+ {
+ foreach (var key in _isolatedCache.Keys)
+ {
+ IRuntimeCacheProvider cache;
+ if (_isolatedCache.TryRemove(key, out cache))
+ {
+ cache.ClearAllCache();
+ }
+ }
+ }
+ }
+}
\ 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..b24838bc3b
--- /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 SingleItemsOnlyRepositoryCachePolicy(_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 57%
rename from src/Umbraco.Core/Persistence/Repositories/RepositoryCacheOptions.cs
rename to src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs
index 9ac8aa6abd..e8c6ac02b0 100644
--- a/src/Umbraco.Core/Persistence/Repositories/RepositoryCacheOptions.cs
+++ b/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs
@@ -1,17 +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;
}
+ ///
+ /// 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
@@ -21,16 +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; }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs
new file mode 100644
index 0000000000..9566cd6e7f
--- /dev/null
+++ b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.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 SingleItemsOnlyRepositoryCachePolicy : DefaultRepositoryCachePolicy
+ where TEntity : class, IAggregateRoot
+ {
+ public SingleItemsOnlyRepositoryCachePolicy(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/CacheHelper.cs b/src/Umbraco.Core/CacheHelper.cs
index 51cf37aa23..303cf234fd 100644
--- a/src/Umbraco.Core/CacheHelper.cs
+++ b/src/Umbraco.Core/CacheHelper.cs
@@ -1,6 +1,8 @@
using System;
using System.Collections;
+using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
@@ -11,20 +13,19 @@ using Umbraco.Core.Logging;
namespace Umbraco.Core
{
-
///
/// Class that is exposed by the ApplicationContext for application wide caching purposes
///
public class CacheHelper
{
- private readonly bool _enableCache;
+ private readonly IsolatedRuntimeCache _isolatedCacheManager;
private readonly ICacheProvider _requestCache;
- private readonly ICacheProvider _nullRequestCache = new NullCacheProvider();
+ private static readonly ICacheProvider NullRequestCache = new NullCacheProvider();
private readonly ICacheProvider _staticCache;
- private readonly ICacheProvider _nullStaticCache = new NullCacheProvider();
- private readonly IRuntimeCacheProvider _httpCache;
- private readonly IRuntimeCacheProvider _nullHttpCache = new NullCacheProvider();
-
+ private static readonly ICacheProvider NullStaticCache = new NullCacheProvider();
+ private readonly IRuntimeCacheProvider _runtimeCache;
+ private static readonly IRuntimeCacheProvider NullRuntimeCache = new NullCacheProvider();
+
///
/// Creates a cache helper with disabled caches
///
@@ -34,7 +35,7 @@ namespace Umbraco.Core
///
public static CacheHelper CreateDisabledCacheHelper()
{
- return new CacheHelper(null, null, null, false);
+ return new CacheHelper(NullRuntimeCache, NullStaticCache, NullRequestCache, new IsolatedRuntimeCache(t => NullRuntimeCache));
}
///
@@ -44,7 +45,8 @@ namespace Umbraco.Core
: this(
new HttpRuntimeCacheProvider(HttpRuntime.Cache),
new StaticCacheProvider(),
- new HttpRequestCacheProvider())
+ new HttpRequestCacheProvider(),
+ new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider()))
{
}
@@ -56,51 +58,42 @@ namespace Umbraco.Core
: this(
new HttpRuntimeCacheProvider(cache),
new StaticCacheProvider(),
- new HttpRequestCacheProvider())
+ new HttpRequestCacheProvider(),
+ new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider()))
{
}
- ///
- /// Initializes a new instance based on the provided providers
- ///
- ///
- ///
- ///
+ [Obsolete("Use the constructor the specifies all dependencies")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public CacheHelper(
IRuntimeCacheProvider httpCacheProvider,
ICacheProvider staticCacheProvider,
ICacheProvider requestCacheProvider)
- : this(httpCacheProvider, staticCacheProvider, requestCacheProvider, true)
+ : this(httpCacheProvider, staticCacheProvider, requestCacheProvider, new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider()))
{
}
- ///
- /// Private ctor used for creating a disabled cache helper
- ///
- ///
- ///
- ///
- ///
- private CacheHelper(
+ ///
+ /// Initializes a new instance based on the provided providers
+ ///
+ ///
+ ///
+ ///
+ ///
+ public CacheHelper(
IRuntimeCacheProvider httpCacheProvider,
ICacheProvider staticCacheProvider,
- ICacheProvider requestCacheProvider,
- bool enableCache)
+ ICacheProvider requestCacheProvider,
+ IsolatedRuntimeCache isolatedCacheManager)
{
- if (enableCache)
- {
- _httpCache = httpCacheProvider;
- _staticCache = staticCacheProvider;
- _requestCache = requestCacheProvider;
- }
- else
- {
- _httpCache = null;
- _staticCache = null;
- _requestCache = null;
- }
-
- _enableCache = enableCache;
+ if (httpCacheProvider == null) throw new ArgumentNullException("httpCacheProvider");
+ if (staticCacheProvider == null) throw new ArgumentNullException("staticCacheProvider");
+ if (requestCacheProvider == null) throw new ArgumentNullException("requestCacheProvider");
+ if (isolatedCacheManager == null) throw new ArgumentNullException("isolatedCacheManager");
+ _runtimeCache = httpCacheProvider;
+ _staticCache = staticCacheProvider;
+ _requestCache = requestCacheProvider;
+ _isolatedCacheManager = isolatedCacheManager;
}
///
@@ -108,15 +101,15 @@ namespace Umbraco.Core
///
public ICacheProvider RequestCache
{
- get { return _enableCache ? _requestCache : _nullRequestCache; }
+ get { return _requestCache; }
}
-
+
///
/// Returns the current Runtime cache
///
public ICacheProvider StaticCache
{
- get { return _enableCache ? _staticCache : _nullStaticCache; }
+ get { return _staticCache; }
}
///
@@ -124,8 +117,16 @@ namespace Umbraco.Core
///
public IRuntimeCacheProvider RuntimeCache
{
- get { return _enableCache ? _httpCache : _nullHttpCache; }
+ get { return _runtimeCache; }
}
+
+ ///
+ /// Returns the current Isolated Runtime cache manager
+ ///
+ public IsolatedRuntimeCache IsolatedRuntimeCache
+ {
+ get { return _isolatedCacheManager; }
+ }
#region Legacy Runtime/Http Cache accessors
@@ -133,16 +134,11 @@ namespace Umbraco.Core
/// Clears the item in umbraco's runtime cache
///
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public void ClearAllCache()
{
- if (_enableCache == false)
- {
- _nullHttpCache.ClearAllCache();
- }
- else
- {
- _httpCache.ClearAllCache();
- }
+ _runtimeCache.ClearAllCache();
+ _isolatedCacheManager.ClearAllCaches();
}
///
@@ -150,16 +146,10 @@ namespace Umbraco.Core
///
/// Key
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public void ClearCacheItem(string key)
{
- if (_enableCache == false)
- {
- _nullHttpCache.ClearCacheItem(key);
- }
- else
- {
- _httpCache.ClearCacheItem(key);
- }
+ _runtimeCache.ClearCacheItem(key);
}
@@ -171,30 +161,17 @@ namespace Umbraco.Core
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
public void ClearCacheObjectTypes(string typeName)
{
- if (_enableCache == false)
- {
- _nullHttpCache.ClearCacheObjectTypes(typeName);
- }
- else
- {
- _httpCache.ClearCacheObjectTypes(typeName);
- }
+ _runtimeCache.ClearCacheObjectTypes(typeName);
}
///
/// Clears all objects in the System.Web.Cache with the System.Type specified
///
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public void ClearCacheObjectTypes()
{
- if (_enableCache == false)
- {
- _nullHttpCache.ClearCacheObjectTypes();
- }
- else
- {
- _httpCache.ClearCacheObjectTypes();
- }
+ _runtimeCache.ClearCacheObjectTypes();
}
///
@@ -202,16 +179,10 @@ namespace Umbraco.Core
///
/// The start of the key
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public void ClearCacheByKeySearch(string keyStartsWith)
{
- if (_enableCache == false)
- {
- _nullHttpCache.ClearCacheByKeySearch(keyStartsWith);
- }
- else
- {
- _httpCache.ClearCacheByKeySearch(keyStartsWith);
- }
+ _runtimeCache.ClearCacheByKeySearch(keyStartsWith);
}
///
@@ -219,29 +190,17 @@ namespace Umbraco.Core
///
///
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public void ClearCacheByKeyExpression(string regexString)
{
- if (_enableCache == false)
- {
- _nullHttpCache.ClearCacheByKeyExpression(regexString);
- }
- else
- {
- _httpCache.ClearCacheByKeyExpression(regexString);
- }
+ _runtimeCache.ClearCacheByKeyExpression(regexString);
}
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith)
{
- if (_enableCache == false)
- {
- return _nullHttpCache.GetCacheItemsByKeySearch(keyStartsWith);
- }
- else
- {
- return _httpCache.GetCacheItemsByKeySearch(keyStartsWith);
- }
+ return _runtimeCache.GetCacheItemsByKeySearch(keyStartsWith);
}
///
@@ -251,16 +210,10 @@ namespace Umbraco.Core
///
///
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public TT GetCacheItem(string cacheKey)
{
- if (_enableCache == false)
- {
- return _nullHttpCache.GetCacheItem(cacheKey);
- }
- else
- {
- return _httpCache.GetCacheItem(cacheKey);
- }
+ return _runtimeCache.GetCacheItem(cacheKey);
}
///
@@ -271,16 +224,11 @@ namespace Umbraco.Core
///
///
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public TT GetCacheItem(string cacheKey, Func getCacheItem)
{
- if (_enableCache == false)
- {
- return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem);
- }
- else
- {
- return _httpCache.GetCacheItem(cacheKey, getCacheItem);
- }
+ return _runtimeCache.GetCacheItem(cacheKey, getCacheItem);
+
}
///
@@ -292,17 +240,12 @@ namespace Umbraco.Core
///
///
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public TT GetCacheItem(string cacheKey,
TimeSpan timeout, Func getCacheItem)
{
- if (_enableCache == false)
- {
- return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, timeout);
- }
- else
- {
- return _httpCache.GetCacheItem(cacheKey, getCacheItem, timeout);
- }
+ return _runtimeCache.GetCacheItem(cacheKey, getCacheItem, timeout);
+
}
///
@@ -315,18 +258,13 @@ namespace Umbraco.Core
///
///
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public TT GetCacheItem(string cacheKey,
CacheItemRemovedCallback refreshAction, TimeSpan timeout,
Func getCacheItem)
{
- if (!_enableCache)
- {
- return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction);
- }
- else
- {
- return _httpCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction);
- }
+ return _runtimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction);
+
}
///
@@ -340,18 +278,13 @@ namespace Umbraco.Core
///
///
[Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public TT GetCacheItem(string cacheKey,
CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan timeout,
Func getCacheItem)
{
- if (_enableCache == false)
- {
- return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction);
- }
- else
- {
- return _httpCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction);
- }
+ return _runtimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction);
+
}
///
@@ -373,20 +306,13 @@ namespace Umbraco.Core
TimeSpan timeout,
Func getCacheItem)
{
- if (_enableCache == false)
+ var cache = _runtimeCache as HttpRuntimeCacheProvider;
+ if (cache != null)
{
- return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction, null);
- }
- else
- {
- var cache = _httpCache as HttpRuntimeCacheProvider;
- if (cache != null)
- {
- var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency);
- return result == null ? default(TT) : result.TryConvertTo().Result;
- }
- throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider));
+ var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency);
+ return result == null ? default(TT) : result.TryConvertTo().Result;
}
+ throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider));
}
///
@@ -404,20 +330,13 @@ namespace Umbraco.Core
CacheDependency cacheDependency,
Func getCacheItem)
{
- if (!_enableCache)
+ var cache = _runtimeCache as HttpRuntimeCacheProvider;
+ if (cache != null)
{
- return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem, null, false, priority, null, null);
- }
- else
- {
- var cache = _httpCache as HttpRuntimeCacheProvider;
- if (cache != null)
- {
- var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), null, false, priority, null, cacheDependency);
- return result == null ? default(TT) : result.TryConvertTo().Result;
- }
- throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider));
+ var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), null, false, priority, null, cacheDependency);
+ return result == null ? default(TT) : result.TryConvertTo().Result;
}
+ throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider));
}
///
@@ -427,18 +346,14 @@ namespace Umbraco.Core
///
///
///
+ [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public void InsertCacheItem(string cacheKey,
CacheItemPriority priority,
Func getCacheItem)
{
- if (_enableCache == false)
- {
- _nullHttpCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority);
- }
- else
- {
- _httpCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority);
- }
+ _runtimeCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority);
+
}
///
@@ -449,19 +364,14 @@ namespace Umbraco.Core
///
/// This will set an absolute expiration from now until the timeout
///
+ [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
public void InsertCacheItem(string cacheKey,
CacheItemPriority priority,
TimeSpan timeout,
Func getCacheItem)
{
- if (_enableCache == false)
- {
- _nullHttpCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority);
- }
- else
- {
- _httpCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority);
- }
+ _runtimeCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority);
}
///
@@ -480,19 +390,12 @@ namespace Umbraco.Core
TimeSpan timeout,
Func getCacheItem)
{
- if (_enableCache == false)
+ var cache = _runtimeCache as HttpRuntimeCacheProvider;
+ if (cache != null)
{
- _nullHttpCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority, dependentFiles:null);
- }
- else
- {
- var cache = _httpCache as HttpRuntimeCacheProvider;
- if (cache != null)
- {
- cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, null, cacheDependency);
- }
- throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider));
+ cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, null, cacheDependency);
}
+ throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider));
}
///
@@ -513,19 +416,12 @@ namespace Umbraco.Core
TimeSpan? timeout,
Func getCacheItem)
{
- if (_enableCache == false)
+ var cache = _runtimeCache as HttpRuntimeCacheProvider;
+ if (cache != null)
{
- _nullHttpCache.InsertCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction, null);
- }
- else
- {
- var cache = _httpCache as HttpRuntimeCacheProvider;
- if (cache != null)
- {
- cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency);
- }
- throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider));
+ cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency);
}
+ throw new InvalidOperationException("Cannot use this obsoleted overload when the current provider is not of type " + typeof(HttpRuntimeCacheProvider));
}
#endregion
diff --git a/src/Umbraco.Core/Collections/DeepCloneableList.cs b/src/Umbraco.Core/Collections/DeepCloneableList.cs
new file mode 100644
index 0000000000..365bf53b06
--- /dev/null
+++ b/src/Umbraco.Core/Collections/DeepCloneableList.cs
@@ -0,0 +1,108 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Umbraco.Core.Models;
+using Umbraco.Core.Models.EntityBase;
+
+namespace Umbraco.Core.Collections
+{
+ ///
+ /// A List that can be deep cloned with deep cloned elements and can reset the collection's items dirty flags
+ ///
+ ///
+ internal class DeepCloneableList : List, IDeepCloneable, IRememberBeingDirty
+ {
+ ///
+ /// Initializes a new instance of the class that is empty and has the default initial capacity.
+ ///
+ public DeepCloneableList()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class that contains elements copied from the specified collection and has sufficient capacity to accommodate the number of elements copied.
+ ///
+ /// The collection whose elements are copied to the new list. is null.
+ public DeepCloneableList(IEnumerable collection) : base(collection)
+ {
+ }
+
+ ///
+ /// Creates a new list and adds each element as a deep cloned element if it is of type IDeepCloneable
+ ///
+ ///
+ public object DeepClone()
+ {
+ var newList = new DeepCloneableList();
+ foreach (var item in this)
+ {
+ var dc = item as IDeepCloneable;
+ if (dc != null)
+ {
+ newList.Add((T) dc.DeepClone());
+ }
+ else
+ {
+ newList.Add(item);
+ }
+ }
+ return newList;
+ }
+
+ public bool IsDirty()
+ {
+ return this.OfType().Any(x => x.IsDirty());
+ }
+
+ public bool WasDirty()
+ {
+ return this.OfType().Any(x => x.WasDirty());
+ }
+
+ ///
+ /// Always returns false, the list has no properties we need to report
+ ///
+ ///
+ ///
+ public bool IsPropertyDirty(string propName)
+ {
+ return false;
+ }
+
+ ///
+ /// Always returns false, the list has no properties we need to report
+ ///
+ ///
+ ///
+ public bool WasPropertyDirty(string propertyName)
+ {
+ return false;
+ }
+
+ public void ResetDirtyProperties()
+ {
+ foreach (var dc in this.OfType())
+ {
+ dc.ResetDirtyProperties();
+ }
+ }
+
+ public void ForgetPreviouslyDirtyProperties()
+ {
+ foreach (var dc in this.OfType())
+ {
+ dc.ForgetPreviouslyDirtyProperties();
+ }
+ }
+
+ public void ResetDirtyProperties(bool rememberPreviouslyChangedProperties)
+ {
+ foreach (var dc in this.OfType())
+ {
+ dc.ResetDirtyProperties(rememberPreviouslyChangedProperties);
+ }
+ }
+ }
+}
diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs
index 49829513bb..d2dea09698 100644
--- a/src/Umbraco.Core/CoreBootManager.cs
+++ b/src/Umbraco.Core/CoreBootManager.cs
@@ -188,10 +188,16 @@ namespace Umbraco.Core
protected virtual CacheHelper CreateApplicationCache()
{
var cacheHelper = new CacheHelper(
- new ObjectCacheRuntimeCacheProvider(),
+ //we need to have the dep clone runtime cache provider to ensure
+ //all entities are cached properly (cloned in and cloned out)
+ new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()),
new StaticCacheProvider(),
//we have no request based cache when not running in web-based context
- new NullCacheProvider());
+ new NullCacheProvider(),
+ new IsolatedRuntimeCache(type =>
+ //we need to have the dep clone runtime cache provider to ensure
+ //all entities are cached properly (cloned in and cloned out)
+ new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider())));
return cacheHelper;
}
diff --git a/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs b/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs
index 452c05adb8..1be504ea45 100644
--- a/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs
+++ b/src/Umbraco.Core/Media/Exif/ExifPropertyCollection.cs
@@ -75,7 +75,7 @@ namespace Umbraco.Core.Media.Exif
{
if (items.ContainsKey (key))
items.Remove (key);
- if (key == ExifTag.WindowsTitle || key == ExifTag.WindowsTitle || key == ExifTag.WindowsComment || key == ExifTag.WindowsAuthor || key == ExifTag.WindowsKeywords || key == ExifTag.WindowsSubject) {
+ if (key == ExifTag.WindowsTitle || key == ExifTag.WindowsComment || key == ExifTag.WindowsAuthor || key == ExifTag.WindowsKeywords || key == ExifTag.WindowsSubject) {
items.Add (key, new WindowsByteString (key, value));
} else {
items.Add (key, new ExifAscii (key, value, parent.Encoding));
diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs
index 367e897c35..749c629d19 100644
--- a/src/Umbraco.Core/Models/DictionaryItem.cs
+++ b/src/Umbraco.Core/Models/DictionaryItem.cs
@@ -14,6 +14,7 @@ namespace Umbraco.Core.Models
[DataContract(IsReference = true)]
public class DictionaryItem : Entity, IDictionaryItem
{
+ public Func GetLanguage { get; set; }
private Guid? _parentId;
private string _itemKey;
private IEnumerable _translations;
@@ -78,7 +79,17 @@ namespace Umbraco.Core.Models
{
SetPropertyValueAndDetectChanges(o =>
{
- _translations = value;
+ var asArray = value.ToArray();
+ //ensure the language callback is set on each translation
+ if (GetLanguage != null)
+ {
+ foreach (var translation in asArray.OfType())
+ {
+ translation.GetLanguage = GetLanguage;
+ }
+ }
+
+ _translations = asArray;
return _translations;
}, _translations, TranslationsSelector,
//Custom comparer for enumerable
diff --git a/src/Umbraco.Core/Models/DictionaryTranslation.cs b/src/Umbraco.Core/Models/DictionaryTranslation.cs
index 782ff14413..59f96dbe85 100644
--- a/src/Umbraco.Core/Models/DictionaryTranslation.cs
+++ b/src/Umbraco.Core/Models/DictionaryTranslation.cs
@@ -13,13 +13,18 @@ namespace Umbraco.Core.Models
[DataContract(IsReference = true)]
public class DictionaryTranslation : Entity, IDictionaryTranslation
{
+ internal Func GetLanguage { get; set; }
+
private ILanguage _language;
private string _value;
+ //note: this will be memberwise cloned
+ private int _languageId;
public DictionaryTranslation(ILanguage language, string value)
{
if (language == null) throw new ArgumentNullException("language");
_language = language;
+ _languageId = _language.Id;
_value = value;
}
@@ -27,6 +32,20 @@ namespace Umbraco.Core.Models
{
if (language == null) throw new ArgumentNullException("language");
_language = language;
+ _languageId = _language.Id;
+ _value = value;
+ Key = uniqueId;
+ }
+
+ internal DictionaryTranslation(int languageId, string value)
+ {
+ _languageId = languageId;
+ _value = value;
+ }
+
+ internal DictionaryTranslation(int languageId, string value, Guid uniqueId)
+ {
+ _languageId = languageId;
_value = value;
Key = uniqueId;
}
@@ -37,20 +56,43 @@ namespace Umbraco.Core.Models
///
/// Gets or sets the for the translation
///
+ ///
+ /// Marked as DoNotClone - TODO: this member shouldn't really exist here in the first place, the DictionaryItem
+ /// class will have a deep hierarchy of objects which all get deep cloned which we don't want. This should have simply
+ /// just referenced a language ID not the actual language object. In v8 we need to fix this.
+ /// We're going to have to do the same hacky stuff we had to do with the Template/File contents so that this is returned
+ /// on a callback.
+ ///
[DataMember]
+ [DoNotClone]
public ILanguage Language
{
- get { return _language; }
+ get
+ {
+ if (_language != null)
+ return _language;
+
+ // else, must lazy-load
+ if (GetLanguage != null && _languageId > 0)
+ _language = GetLanguage(_languageId);
+ return _language;
+ }
set
{
SetPropertyValueAndDetectChanges(o =>
{
_language = value;
+ _languageId = _language == null ? -1 : _language.Id;
return _language;
}, _language, LanguageSelector);
}
}
+ public int LanguageId
+ {
+ get { return _languageId; }
+ }
+
///
/// Gets or sets the translated text
///
@@ -68,5 +110,23 @@ namespace Umbraco.Core.Models
}
}
+ public override object DeepClone()
+ {
+ var clone = (DictionaryTranslation)base.DeepClone();
+
+ // clear fields that were memberwise-cloned and that we don't want to clone
+ clone._language = null;
+
+ // turn off change tracking
+ clone.DisableChangeTracking();
+
+ // this shouldn't really be needed since we're not tracking
+ clone.ResetDirtyProperties(false);
+
+ // re-enable tracking
+ clone.EnableChangeTracking();
+
+ return clone;
+ }
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Models/IDictionaryTranslation.cs b/src/Umbraco.Core/Models/IDictionaryTranslation.cs
index cf813bf72f..25aa1e4395 100644
--- a/src/Umbraco.Core/Models/IDictionaryTranslation.cs
+++ b/src/Umbraco.Core/Models/IDictionaryTranslation.cs
@@ -12,6 +12,8 @@ namespace Umbraco.Core.Models
[DataMember]
ILanguage Language { get; set; }
+ int LanguageId { get; }
+
///
/// Gets or sets the translated text
///
diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs
index 90d173b49a..a8a1e80eb7 100644
--- a/src/Umbraco.Core/ObjectExtensions.cs
+++ b/src/Umbraco.Core/ObjectExtensions.cs
@@ -1,15 +1,10 @@
using System;
using System.Collections;
-using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
-using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
-using System.Runtime.Serialization;
-using System.Runtime.Serialization.Formatters.Binary;
-using System.Security;
using System.Xml;
namespace Umbraco.Core
@@ -51,7 +46,7 @@ namespace Umbraco.Core
public static Attempt TryConvertTo(this object input)
{
var result = TryConvertTo(input, typeof(T));
- if (!result.Success)
+ if (result.Success == false)
{
//just try a straight up conversion
try
@@ -64,7 +59,7 @@ namespace Umbraco.Core
return Attempt.Fail(e);
}
}
- return !result.Success ? Attempt.Fail() : Attempt.Succeed((T)result.Result);
+ return result.Success == false ? Attempt.Fail() : Attempt.Succeed((T)result.Result);
}
///
@@ -117,7 +112,7 @@ namespace Umbraco.Core
}
// we've already dealed with nullables, so any other generic types need to fall through
- if (!destinationType.IsGenericType)
+ if (destinationType.IsGenericType == false)
{
if (input is string)
{
@@ -335,11 +330,11 @@ namespace Umbraco.Core
return null; // we can't decide...
}
- private readonly static char[] NumberDecimalSeparatorsToNormalize = new[] {'.', ','};
+ private static readonly char[] NumberDecimalSeparatorsToNormalize = new[] {'.', ','};
private static string NormalizeNumberDecimalSeparator(string s)
{
- var normalized = System.Threading.Thread.CurrentThread.CurrentUICulture.NumberFormat.NumberDecimalSeparator[0];
+ var normalized = System.Threading.Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator[0];
return s.ReplaceMany(NumberDecimalSeparatorsToNormalize, normalized);
}
@@ -442,7 +437,7 @@ namespace Umbraco.Core
{
var props = TypeDescriptor.GetProperties(o);
var d = new Dictionary();
- foreach (var prop in props.Cast().Where(x => !ignoreProperties.Contains(x.Name)))
+ foreach (var prop in props.Cast().Where(x => ignoreProperties.Contains(x.Name) == false))
{
var val = prop.GetValue(o);
if (val != null)
@@ -478,13 +473,13 @@ namespace Umbraco.Core
var items = (from object enumItem in enumerable let value = GetEnumPropertyDebugString(enumItem, levels) where value != null select value).Take(10).ToList();
- return items.Count() > 0
+ return items.Any()
? "{{ {0} }}".InvariantFormat(String.Join(", ", items))
: null;
}
var props = obj.GetType().GetProperties();
- if ((props.Count() == 2) && props[0].Name == "Key" && props[1].Name == "Value" && levels > -2)
+ if ((props.Length == 2) && props[0].Name == "Key" && props[1].Name == "Value" && levels > -2)
{
try
{
@@ -500,12 +495,12 @@ namespace Umbraco.Core
if (levels > -1)
{
var items =
- from propertyInfo in props
+ (from propertyInfo in props
let value = GetPropertyDebugString(propertyInfo, obj, levels)
where value != null
- select "{0}={1}".InvariantFormat(propertyInfo.Name, value);
+ select "{0}={1}".InvariantFormat(propertyInfo.Name, value)).ToArray();
- return items.Count() > 0
+ return items.Any()
? "[{0}]:{{ {1} }}".InvariantFormat(obj.GetType().Name, String.Join(", ", items))
: null;
}
diff --git a/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs b/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs
index 14dca6b366..1b9d73bdd4 100644
--- a/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs
+++ b/src/Umbraco.Core/Persistence/Factories/DictionaryItemFactory.cs
@@ -42,7 +42,7 @@ namespace Umbraco.Core.Persistence.Factories
{
var text = new LanguageTextDto
{
- LanguageId = translation.Language.Id,
+ LanguageId = translation.LanguageId,
UniqueId = translation.Key,
Value = translation.Value
};
diff --git a/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs b/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs
index 8dca2494d0..65297b9529 100644
--- a/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs
+++ b/src/Umbraco.Core/Persistence/Factories/DictionaryTranslationFactory.cs
@@ -7,20 +7,19 @@ namespace Umbraco.Core.Persistence.Factories
internal class DictionaryTranslationFactory
{
private readonly Guid _uniqueId;
- private ILanguage _language;
- public DictionaryTranslationFactory(Guid uniqueId, ILanguage language)
+ public DictionaryTranslationFactory(Guid uniqueId)
{
_uniqueId = uniqueId;
- _language = language;
}
#region Implementation of IEntityFactory
public IDictionaryTranslation BuildEntity(LanguageTextDto dto)
{
- var item = new DictionaryTranslation(_language, dto.Value, _uniqueId)
+ var item = new DictionaryTranslation(dto.LanguageId, dto.Value, _uniqueId)
{Id = dto.PrimaryKey};
+
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
item.ResetDirtyProperties(false);
@@ -31,7 +30,7 @@ namespace Umbraco.Core.Persistence.Factories
{
var text = new LanguageTextDto
{
- LanguageId = entity.Language.Id,
+ LanguageId = entity.LanguageId,
UniqueId = _uniqueId,
Value = entity.Value
};
diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs
index 499e2ba849..398308c832 100644
--- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs
@@ -26,17 +26,15 @@ namespace Umbraco.Core.Persistence.Repositories
///
internal class DataTypeDefinitionRepository : PetaPocoRepositoryBase, IDataTypeDefinitionRepository
{
- private readonly CacheHelper _cacheHelper;
private readonly IContentTypeRepository _contentTypeRepository;
private readonly DataTypePreValueRepository _preValRepository;
- public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, CacheHelper cache, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider sqlSyntax,
+ public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax,
IContentTypeRepository contentTypeRepository)
: base(work, cache, logger, sqlSyntax)
{
- _cacheHelper = cacheHelper;
_contentTypeRepository = contentTypeRepository;
- _preValRepository = new DataTypePreValueRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax);
+ _preValRepository = new DataTypePreValueRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax);
}
#region Overrides of RepositoryBase
@@ -239,7 +237,7 @@ AND umbracoNode.id <> @id",
//NOTE: This is a special case, we need to clear the custom cache for pre-values here so they are not stale if devs
// are querying for them in the Saved event (before the distributed call cache is clearing it)
- _cacheHelper.RuntimeCache.ClearCacheItem(GetPrefixedCacheKey(entity.Id));
+ RuntimeCache.ClearCacheItem(GetPrefixedCacheKey(entity.Id));
entity.ResetDirtyProperties();
}
@@ -278,7 +276,7 @@ AND umbracoNode.id <> @id",
public PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId)
{
- var cached = _cacheHelper.RuntimeCache.GetCacheItemsByKeySearch(GetPrefixedCacheKey(dataTypeId));
+ var cached = RuntimeCache.GetCacheItemsByKeySearch(GetPrefixedCacheKey(dataTypeId));
if (cached != null && cached.Any())
{
//return from the cache, ensure it's a cloned result
@@ -297,7 +295,7 @@ AND umbracoNode.id <> @id",
{
//We need to see if we can find the cached PreValueCollection based on the cache key above
- var cached = _cacheHelper.RuntimeCache.GetCacheItemsByKeyExpression(GetCacheKeyRegex(preValueId));
+ var cached = RuntimeCache.GetCacheItemsByKeyExpression(GetCacheKeyRegex(preValueId));
if (cached != null && cached.Any())
{
//return from the cache
@@ -457,7 +455,7 @@ AND umbracoNode.id <> @id",
+ string.Join(",", collection.FormatAsDictionary().Select(x => x.Value.Id).ToArray());
//store into cache
- _cacheHelper.RuntimeCache.InsertCacheItem(key, () => collection,
+ RuntimeCache.InsertCacheItem(key, () => collection,
//30 mins
new TimeSpan(0, 0, 30),
//sliding is true
diff --git a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs
index 1985875717..971efc4f2d 100644
--- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.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;
@@ -19,30 +20,40 @@ namespace Umbraco.Core.Persistence.Repositories
///
internal class DictionaryRepository : PetaPocoRepositoryBase, IDictionaryRepository
{
- private readonly ILanguageRepository _languageRepository;
-
- public DictionaryRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider syntax, ILanguageRepository languageRepository)
+ public DictionaryRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider syntax)
: base(work, cache, logger, syntax)
- {
- _languageRepository = languageRepository;
- }
+ {
+ }
+
+ private IRepositoryCachePolicyFactory _cachePolicyFactory;
+ protected override IRepositoryCachePolicyFactory CachePolicyFactory
+ {
+ get
+ {
+ //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
+ }));
+ }
+ }
#region Overrides of RepositoryBase
protected override IDictionaryItem PerformGet(int id)
{
var sql = GetBaseQuery(false)
- .Where(GetBaseWhereClause(), new {Id = id})
+ .Where(GetBaseWhereClause(), new { Id = id })
.OrderBy(x => x.UniqueId, SqlSyntax);
var dto = Database.Fetch(new DictionaryLanguageTextRelator().Map, sql).FirstOrDefault();
if (dto == null)
return null;
-
- //This will be cached
- var allLanguages = _languageRepository.GetAll().ToArray();
-
- var entity = ConvertFromDto(dto, allLanguages);
+
+ var entity = ConvertFromDto(dto);
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
@@ -56,14 +67,11 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = GetBaseQuery(false).Where("cmsDictionary.pk > 0");
if (ids.Any())
{
- sql.Where("cmsDictionary.pk in (@ids)", new { ids = ids });
+ sql.Where("cmsDictionary.pk in (@ids)", new { ids = ids });
}
- //This will be cached
- var allLanguages = _languageRepository.GetAll().ToArray();
-
return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql)
- .Select(dto => ConvertFromDto(dto, allLanguages));
+ .Select(dto => ConvertFromDto(dto));
}
protected override IEnumerable PerformGetByQuery(IQuery query)
@@ -72,12 +80,9 @@ namespace Umbraco.Core.Persistence.Repositories
var translator = new SqlTranslator(sqlClause, query);
var sql = translator.Translate();
sql.OrderBy(x => x.UniqueId, SqlSyntax);
-
- //This will be cached
- var allLanguages = _languageRepository.GetAll().ToArray();
-
+
return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql)
- .Select(x => ConvertFromDto(x, allLanguages));
+ .Select(x => ConvertFromDto(x));
}
#endregion
@@ -87,7 +92,7 @@ namespace Umbraco.Core.Persistence.Repositories
protected override Sql GetBaseQuery(bool isCount)
{
var sql = new Sql();
- if(isCount)
+ if (isCount)
{
sql.Select("COUNT(*)")
.From(SqlSyntax);
@@ -123,26 +128,28 @@ namespace Umbraco.Core.Persistence.Repositories
protected override void PersistNewItem(IDictionaryItem entity)
{
- ((DictionaryItem)entity).AddingEntity();
+ var dictionaryItem = ((DictionaryItem) entity);
- foreach (var translation in entity.Translations)
+ dictionaryItem.AddingEntity();
+
+ foreach (var translation in dictionaryItem.Translations)
translation.Value = translation.Value.ToValidXmlString();
var factory = new DictionaryItemFactory();
- var dto = factory.BuildDto(entity);
+ var dto = factory.BuildDto(dictionaryItem);
var id = Convert.ToInt32(Database.Insert(dto));
- entity.Id = id;
+ dictionaryItem.Id = id;
- var translationFactory = new DictionaryTranslationFactory(entity.Key, null);
- foreach (var translation in entity.Translations)
+ var translationFactory = new DictionaryTranslationFactory(dictionaryItem.Key);
+ foreach (var translation in dictionaryItem.Translations)
{
var textDto = translationFactory.BuildDto(translation);
translation.Id = Convert.ToInt32(Database.Insert(textDto));
- translation.Key = entity.Key;
+ translation.Key = dictionaryItem.Key;
}
- entity.ResetDirtyProperties();
+ dictionaryItem.ResetDirtyProperties();
}
protected override void PersistUpdatedItem(IDictionaryItem entity)
@@ -157,11 +164,11 @@ namespace Umbraco.Core.Persistence.Repositories
Database.Update(dto);
- var translationFactory = new DictionaryTranslationFactory(entity.Key, null);
+ var translationFactory = new DictionaryTranslationFactory(entity.Key);
foreach (var translation in entity.Translations)
{
var textDto = translationFactory.BuildDto(translation);
- if(translation.HasIdentity)
+ if (translation.HasIdentity)
{
Database.Update(textDto);
}
@@ -183,7 +190,7 @@ namespace Umbraco.Core.Persistence.Repositories
{
RecursiveDelete(entity.Key);
- Database.Delete("WHERE UniqueId = @Id", new { Id = entity.Key});
+ Database.Delete("WHERE UniqueId = @Id", new { Id = entity.Key });
Database.Delete("WHERE id = @Id", new { Id = entity.Key });
//Clear the cache entries that exist by uniqueid/item key
@@ -193,7 +200,7 @@ namespace Umbraco.Core.Persistence.Repositories
private void RecursiveDelete(Guid parentId)
{
- var list = Database.Fetch("WHERE parent = @ParentId", new {ParentId = parentId});
+ var list = Database.Fetch("WHERE parent = @ParentId", new { ParentId = parentId });
foreach (var dto in list)
{
RecursiveDelete(dto.UniqueId);
@@ -209,20 +216,18 @@ namespace Umbraco.Core.Persistence.Repositories
#endregion
- protected IDictionaryItem ConvertFromDto(DictionaryDto dto, ILanguage[] allLanguages)
+ protected IDictionaryItem ConvertFromDto(DictionaryDto dto)
{
var factory = new DictionaryItemFactory();
var entity = factory.BuildEntity(dto);
var list = new List();
foreach (var textDto in dto.LanguageTextDtos)
- {
- //Assuming this is cached!
- var language = allLanguages.FirstOrDefault(x => x.Id == textDto.LanguageId);
- if (language == null)
+ {
+ if (textDto.LanguageId <= 0)
continue;
- var translationFactory = new DictionaryTranslationFactory(dto.UniqueId, language);
+ var translationFactory = new DictionaryTranslationFactory(dto.UniqueId);
list.Add(translationFactory.BuildEntity(textDto));
}
entity.Translations = list;
@@ -234,7 +239,7 @@ namespace Umbraco.Core.Persistence.Repositories
{
using (var uniqueIdRepo = new DictionaryByUniqueIdRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
{
- return uniqueIdRepo.Get(uniqueId);
+ return uniqueIdRepo.Get(uniqueId);
}
}
@@ -242,10 +247,10 @@ namespace Umbraco.Core.Persistence.Repositories
{
using (var keyRepo = new DictionaryByKeyRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
{
- return keyRepo.Get(key);
+ return keyRepo.Get(key);
}
}
-
+
private IEnumerable GetRootDictionaryItems()
{
var query = Query.Builder.Where(x => x.ParentId == null);
@@ -254,9 +259,6 @@ namespace Umbraco.Core.Persistence.Repositories
public IEnumerable GetDictionaryItemDescendants(Guid? parentId)
{
- //This will be cached
- var allLanguages = _languageRepository.GetAll().ToArray();
-
//This methods will look up children at each level, since we do not store a path for dictionary (ATM), we need to do a recursive
// lookup to get descendants. Currently this is the most efficient way to do it
@@ -275,7 +277,7 @@ namespace Umbraco.Core.Persistence.Repositories
sql.OrderBy(x => x.UniqueId, SqlSyntax);
return Database.Fetch(new DictionaryLanguageTextRelator().Map, sql)
- .Select(x => ConvertFromDto(x, allLanguages));
+ .Select(x => ConvertFromDto(x));
});
};
@@ -284,7 +286,7 @@ namespace Umbraco.Core.Persistence.Repositories
: getItemsFromParents(new[] { parentId.Value });
return childItems.SelectRecursive(items => getItemsFromParents(items.Select(x => x.Key).ToArray())).SelectMany(items => items);
-
+
}
private class DictionaryByUniqueIdRepository : SimpleGetRepository
@@ -314,20 +316,34 @@ namespace Umbraco.Core.Persistence.Repositories
protected override IDictionaryItem ConvertToEntity(DictionaryDto dto)
{
- //This will be cached
- var allLanguages = _dictionaryRepository._languageRepository.GetAll().ToArray();
- return _dictionaryRepository.ConvertFromDto(dto, allLanguages);
+ return _dictionaryRepository.ConvertFromDto(dto);
}
protected override object GetBaseWhereClauseArguments(Guid id)
{
- return new {Id = id};
+ return new { Id = id };
}
protected override string GetWhereInClauseForGetAll()
{
return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("id") + " in (@ids)";
}
+
+ private IRepositoryCachePolicyFactory _cachePolicyFactory;
+ protected override IRepositoryCachePolicyFactory CachePolicyFactory
+ {
+ get
+ {
+ //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
+ }));
+ }
+ }
}
private class DictionaryByKeyRepository : SimpleGetRepository
@@ -357,9 +373,7 @@ namespace Umbraco.Core.Persistence.Repositories
protected override IDictionaryItem ConvertToEntity(DictionaryDto dto)
{
- //This will be cached
- var allLanguages = _dictionaryRepository._languageRepository.GetAll().ToArray();
- return _dictionaryRepository.ConvertFromDto(dto, allLanguages);
+ return _dictionaryRepository.ConvertFromDto(dto);
}
protected override object GetBaseWhereClauseArguments(string id)
@@ -371,17 +385,24 @@ namespace Umbraco.Core.Persistence.Repositories
{
return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("key") + " in (@ids)";
}
+
+ private IRepositoryCachePolicyFactory _cachePolicyFactory;
+ protected override IRepositoryCachePolicyFactory CachePolicyFactory
+ {
+ get
+ {
+ //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
+ }));
+ }
+ }
}
- ///
- /// Dispose disposable properties
- ///
- ///
- /// Ensure the unit of work is disposed
- ///
- protected override void DisposeResources()
- {
- _languageRepository.Dispose();
- }
+
}
}
\ No newline at end of file
diff --git a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs
index 21ba8b4baf..6abab73dd7 100644
--- a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs
@@ -18,25 +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
- };
+ {
}
- ///
- /// 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/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs
index fcdd2bd3d2..fd693df182 100644
--- a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs
@@ -47,6 +47,12 @@ namespace Umbraco.Core.Persistence.Repositories
return nodeDto == null ? null : CreateEntity(nodeDto);
}
+ public IEnumerable Get(string name, int level, Guid umbracoObjectTypeId)
+ {
+ var sql = GetBaseQuery(false).Where("text=@name AND level=@level AND nodeObjectType=@umbracoObjectTypeId", new { name, level, umbracoObjectTypeId });
+ return Database.Fetch(sql).Select(CreateEntity);
+ }
+
protected override IEnumerable PerformGetAll(params int[] ids)
{
throw new NotImplementedException();
diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs
index 147e0b40f8..48b0cf66b7 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.IO;
using Umbraco.Core.Models;
@@ -23,6 +24,7 @@ namespace Umbraco.Core.Persistence.Repositories
///
///
[Obsolete("Use GetDescendants instead")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
TemplateNode GetTemplateNode(string alias);
///
@@ -32,6 +34,7 @@ namespace Umbraco.Core.Persistence.Repositories
///
///
[Obsolete("Use GetDescendants instead")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
TemplateNode FindTemplateInTree(TemplateNode anyNode, string alias);
///
diff --git a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs
index 2f379b4587..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,23 +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
- };
+ {
}
- 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
@@ -159,13 +154,13 @@ namespace Umbraco.Core.Persistence.Repositories
public ILanguage GetByCultureName(string cultureName)
{
- //use the underlying GetAll which will force cache all domains
+ //use the underlying GetAll which will force cache all languages
return GetAll().FirstOrDefault(x => x.CultureName.InvariantEquals(cultureName));
}
public ILanguage GetByIsoCode(string isoCode)
{
- //use the underlying GetAll which will force cache all domains
+ //use the underlying GetAll which will force cache all languages
return GetAll().FirstOrDefault(x => x.IsoCode.InvariantEquals(isoCode));
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs
index 264409ddf4..40121223a2 100644
--- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs
@@ -20,13 +20,9 @@ namespace Umbraco.Core.Persistence.Repositories
internal class MemberGroupRepository : PetaPocoRepositoryBase, IMemberGroupRepository
{
- private readonly CacheHelper _cacheHelper;
-
- public MemberGroupRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, CacheHelper cacheHelper)
+ public MemberGroupRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{
- if (cacheHelper == null) throw new ArgumentNullException("cacheHelper");
- _cacheHelper = cacheHelper;
}
private readonly MemberGroupFactory _modelFactory = new MemberGroupFactory();
@@ -135,7 +131,7 @@ namespace Umbraco.Core.Persistence.Repositories
public IMemberGroup GetByName(string name)
{
- return _cacheHelper.RuntimeCache.GetCacheItem(
+ return RuntimeCache.GetCacheItem(
string.Format("{0}.{1}", typeof (IMemberGroup).FullName, name),
() =>
{
diff --git a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs
index 0463312982..aa671dccec 100644
--- a/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/PermissionRepository.cs
@@ -26,13 +26,14 @@ namespace Umbraco.Core.Persistence.Repositories
where TEntity : class, IAggregateRoot
{
private readonly IDatabaseUnitOfWork _unitOfWork;
- private readonly CacheHelper _cache;
+ private readonly IRuntimeCacheProvider _runtimeCache;
private readonly ISqlSyntaxProvider _sqlSyntax;
internal PermissionRepository(IDatabaseUnitOfWork unitOfWork, CacheHelper cache, ISqlSyntaxProvider sqlSyntax)
{
_unitOfWork = unitOfWork;
- _cache = cache;
+ //Make this repository use an isolated cache
+ _runtimeCache = cache.IsolatedRuntimeCache.GetOrCreateCache();
_sqlSyntax = sqlSyntax;
}
@@ -45,7 +46,7 @@ namespace Umbraco.Core.Persistence.Repositories
public IEnumerable GetUserPermissionsForEntities(int userId, params int[] entityIds)
{
var entityIdKey = string.Join(",", entityIds.Select(x => x.ToString(CultureInfo.InvariantCulture)));
- return _cache.RuntimeCache.GetCacheItem>(
+ return _runtimeCache.GetCacheItem>(
string.Format("{0}{1}{2}", CacheKeys.UserPermissionsCacheKey, userId, entityIdKey),
() =>
{
diff --git a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs
index 295ff8b408..1086b9cee0 100644
--- a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs
@@ -17,33 +17,23 @@ 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,
- //Set to 1000 just to ensure that all of them are cached, The GetAll on this repository gets called *A lot*, we want max performance
- GetAllCacheThresholdLimit = 1000,
- //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)
{
- var sql = GetBaseQuery(false);
- sql.Where(GetBaseWhereClause(), new { Id = id });
-
- var taskDto = Database.Fetch(new AccessRulesRelator().Map, sql).FirstOrDefault();
- if (taskDto == null)
- return null;
-
- var factory = new PublicAccessEntryFactory();
- var entity = factory.BuildEntity(taskDto);
- return entity;
+ //return from GetAll - this will be cached as a collection
+ return GetAll().FirstOrDefault(x => x.Key == id);
}
protected override IEnumerable PerformGetAll(params Guid[] ids)
@@ -102,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 124aaeb035..f6bcc46d06 100644
--- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs
@@ -1,7 +1,9 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Cache;
+using Umbraco.Core.Collections;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.EntityBase;
@@ -22,11 +24,7 @@ namespace Umbraco.Core.Persistence.Repositories
if (logger == null) throw new ArgumentNullException("logger");
Logger = logger;
_work = work;
-
- //IMPORTANT: We will force the DeepCloneRuntimeCacheProvider to be used here which is a wrapper for the underlying
- // runtime cache to ensure that anything that can be deep cloned in/out is done so, this also ensures that our tracks
- // changes entities are reset.
- _cache = new CacheHelper(new DeepCloneRuntimeCacheProvider(cache.RuntimeCache), cache.StaticCache, cache.RequestCache);
+ _cache = cache;
}
///
@@ -84,9 +82,37 @@ namespace Umbraco.Core.Persistence.Repositories
{
}
- private readonly RepositoryCacheOptions _cacheOptions = new RepositoryCacheOptions();
+
- #region IRepository Members
+ ///
+ /// The runtime cache used for this repo by default is the isolated cache for this type
+ ///
+ protected override IRuntimeCacheProvider RuntimeCache
+ {
+ 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
@@ -119,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);
@@ -158,88 +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
- {
- var 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();
-
- //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
-
- if (entityCollection.Length > RepositoryCacheOptions.GetAllCacheThresholdLimit)
- return entityCollection;
-
- if (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[] {});
- }
-
- 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
@@ -261,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);
@@ -279,34 +220,19 @@ namespace Umbraco.Core.Persistence.Repositories
{
return PerformCount(query);
}
-
- #endregion
-
- #region IUnitOfWorkRepository Members
-
+
///
/// Unit of work method that tells the repository to persist the new entity
///
///
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);
+ }
}
///
@@ -315,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);
+ }
}
///
@@ -340,21 +255,19 @@ 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);
+ }
}
-
- #endregion
-
- #region Abstract IUnitOfWorkRepository Methods
+
protected abstract void PersistNewItem(TEntity item);
protected abstract void PersistUpdatedItem(TEntity item);
protected abstract void PersistDeletedItem(TEntity item);
- #endregion
///
/// Dispose disposable properties
diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs
index da18a16598..7addacdd87 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,41 +41,26 @@ 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 gets called a lot, we want to ensure that all templates are in the cache, default is 100 which
- // would normally be fine but we'll increase it in case people have a ton of templates.
- GetAllCacheThresholdLimit = 500
- };
+ _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
protected override ITemplate PerformGet(int id)
{
- var sql = GetBaseQuery(false).Where(x => x.NodeId == id);
- var result = Database.Fetch(sql).FirstOrDefault();
- if (result == null) return null;
-
- //look up the simple template definitions that have a master template assigned, this is used
- // later to populate the template item's properties
- var childIds = GetAxisDefinitions(result).ToArray();
-
- return MapFromDto(result, childIds);
+ //use the underlying GetAll which will force cache all templates
+ return base.GetAll().FirstOrDefault(x => x.Id == id);
}
protected override IEnumerable PerformGetAll(params int[] ids)
@@ -496,126 +481,102 @@ namespace Umbraco.Core.Persistence.Repositories
public ITemplate Get(string alias)
{
- var sql = GetBaseQuery(false).Where(x => x.Alias == alias);
-
- var dto = Database.Fetch(sql).FirstOrDefault();
-
- if (dto == null)
- return null;
-
- return MapFromDto(dto, GetAxisDefinitions(dto).ToArray());
+ return GetAll(alias).FirstOrDefault();
}
public IEnumerable GetAll(params string[] aliases)
{
- var sql = GetBaseQuery(false);
+ //We must call the base (normal) GetAll method
+ // which is cached. This is a specialized method and unfortunatley with the params[] it
+ // overlaps with the normal GetAll method.
+ if (aliases.Any() == false) return base.GetAll();
- if (aliases.Any())
- {
- sql.Where("cmsTemplate.alias IN (@aliases)", new {aliases = aliases});
- }
-
- var dtos = Database.Fetch(sql).ToArray();
- if (dtos.Length == 0) return Enumerable.Empty();
-
- var axisDefos = GetAxisDefinitions(dtos).ToArray();
- return dtos.Select(x => MapFromDto(x, axisDefos));
+ //return from base.GetAll, this is all cached
+ return base.GetAll().Where(x => aliases.Contains(x.Alias));
}
public IEnumerable GetChildren(int masterTemplateId)
{
- var sql = GetBaseQuery(false);
- if (masterTemplateId <= 0)
- {
- sql.Where(x => x.ParentId <= 0);
- }
- else
- {
- sql.Where(x => x.ParentId == masterTemplateId);
- }
+ //return from base.GetAll, this is all cached
+ var all = base.GetAll().ToArray();
- var dtos = Database.Fetch(sql).ToArray();
- if (dtos.Length == 0) return Enumerable.Empty();
+ if (masterTemplateId <= 0) return all.Where(x => x.MasterTemplateAlias.IsNullOrWhiteSpace());
- var axisDefos = GetAxisDefinitions(dtos).ToArray();
- return dtos.Select(x => MapFromDto(x, axisDefos));
+ var parent = all.FirstOrDefault(x => x.Id == masterTemplateId);
+ if (parent == null) return Enumerable.Empty();
+
+ var children = all.Where(x => x.MasterTemplateAlias == parent.Alias);
+ return children;
}
public IEnumerable GetChildren(string alias)
{
- var sql = GetBaseQuery(false);
- if (alias.IsNullOrWhiteSpace())
- {
- sql.Where(x => x.ParentId <= 0);
- }
- else
- {
- //unfortunately SQLCE doesn't support scalar subqueries in the where clause, otherwise we could have done this
- // in a single query, now we have to lookup the path to acheive the same thing
- var parent = Database.ExecuteScalar(new Sql().Select("nodeId").From(SqlSyntax).Where(dto => dto.Alias == alias));
- if (parent.HasValue == false) return Enumerable.Empty();
-
- sql.Where(x => x.ParentId == parent.Value);
- }
-
- var dtos = Database.Fetch(sql).ToArray();
- if (dtos.Length == 0) return Enumerable.Empty();
-
- var axisDefos = GetAxisDefinitions(dtos).ToArray();
- return dtos.Select(x => MapFromDto(x, axisDefos));
+ //return from base.GetAll, this is all cached
+ return base.GetAll().Where(x => alias.IsNullOrWhiteSpace()
+ ? x.MasterTemplateAlias.IsNullOrWhiteSpace()
+ : x.MasterTemplateAlias == alias);
}
public IEnumerable GetDescendants(int masterTemplateId)
{
- var sql = GetBaseQuery(false);
+ //return from base.GetAll, this is all cached
+ var all = base.GetAll().ToArray();
+ var descendants = new List();
if (masterTemplateId > 0)
{
- //unfortunately SQLCE doesn't support scalar subqueries in the where clause, otherwise we could have done this
- // in a single query, now we have to lookup the path to acheive the same thing
- var path = Database.ExecuteScalar(
- new Sql().Select(SqlSyntax.GetQuotedColumnName("path"))
- .From(SqlSyntax)
- .InnerJoin(SqlSyntax)
- .On(SqlSyntax, dto => dto.NodeId, dto => dto.NodeId)
- .Where(dto => dto.NodeId == masterTemplateId));
-
- if (path.IsNullOrWhiteSpace()) return Enumerable.Empty();
-
- sql.Where(@"(umbracoNode." + SqlSyntax.GetQuotedColumnName("path") + @" LIKE @query)", new { query = path + ",%" });
+ var parent = all.FirstOrDefault(x => x.Id == masterTemplateId);
+ if (parent == null) return Enumerable.Empty();
+ //recursively add all children with a level
+ AddChildren(all, descendants, parent.Alias);
+ }
+ else
+ {
+ descendants.AddRange(all.Where(x => x.MasterTemplateAlias.IsNullOrWhiteSpace()));
+ foreach (var parent in descendants)
+ {
+ //recursively add all children with a level
+ AddChildren(all, descendants, parent.Alias);
+ }
}
- sql.OrderBy("umbracoNode." + SqlSyntax.GetQuotedColumnName("level"));
-
- var dtos = Database.Fetch(sql).ToArray();
- if (dtos.Length == 0) return Enumerable.Empty();
-
- var axisDefos = GetAxisDefinitions(dtos).ToArray();
- return dtos.Select(x => MapFromDto(x, axisDefos));
-
+ //return the list - it will be naturally ordered by level
+ return descendants;
}
-
+
public IEnumerable GetDescendants(string alias)
{
- var sql = GetBaseQuery(false);
+ var all = base.GetAll().ToArray();
+ var descendants = new List();
if (alias.IsNullOrWhiteSpace() == false)
{
- //unfortunately SQLCE doesn't support scalar subqueries in the where clause, otherwise we could have done this
- // in a single query, now we have to lookup the path to acheive the same thing
- var path = Database.ExecuteScalar