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/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/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index 7e59726c80..dcb67a337f 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/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/Interfaces/ITemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/ITemplateRepository.cs index 14957ac49f..3faba9e282 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 Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories @@ -22,6 +23,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// [Obsolete("Use GetDescendants instead")] + [EditorBrowsable(EditorBrowsableState.Never)] TemplateNode GetTemplateNode(string alias); /// @@ -31,6 +33,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 2ea6d79d43..955e316bc3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Text; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; @@ -32,7 +33,6 @@ namespace Umbraco.Core.Persistence.Repositories private readonly ITemplatesSection _templateConfig; private readonly ViewHelper _viewHelper; private readonly MasterPageHelper _masterPageHelper; - private readonly RepositoryCacheOptions _cacheOptions; internal TemplateRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IFileSystem masterpageFileSystem, IFileSystem viewFileSystem, ITemplatesSection templateConfig) : base(work, cache, logger, sqlSyntax) @@ -41,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) @@ -479,126 +464,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( - "SELECT umbracoNode.path FROM cmsTemplate INNER JOIN umbracoNode ON cmsTemplate.nodeId = umbracoNode.id WHERE cmsTemplate.alias = @alias", new { alias = alias }); - - if (path.IsNullOrWhiteSpace()) return Enumerable.Empty(); - - sql.Where(@"(umbracoNode." + SqlSyntax.GetQuotedColumnName("path") + @" LIKE @query)", new {query = path + ",%" }); + var parent = all.FirstOrDefault(x => x.Alias == alias); + if (parent == null) return Enumerable.Empty(); + //recursively add all children + 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); + } + } + //return the list - it will be naturally ordered by level + return descendants; + } - 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)); + private void AddChildren(ITemplate[] all, List descendants, string masterAlias) + { + var c = all.Where(x => x.MasterTemplateAlias == masterAlias).ToArray(); + descendants.AddRange(c); + if (c.Any() == false) return; + //recurse through all children + foreach (var child in c) + { + AddChildren(all, descendants, child.Alias); + } } /// @@ -610,7 +571,7 @@ namespace Umbraco.Core.Persistence.Repositories public TemplateNode GetTemplateNode(string alias) { //first get all template objects - var allTemplates = GetAll().ToArray(); + var allTemplates = base.GetAll().ToArray(); var selfTemplate = allTemplates.SingleOrDefault(x => x.Alias == alias); if (selfTemplate == null) @@ -633,6 +594,7 @@ namespace Umbraco.Core.Persistence.Repositories return FindTemplateInTree(topNode, alias); } + [Obsolete("Only used by obsolete code")] private static TemplateNode WalkTree(TemplateNode current, string alias) { //now walk the tree to find the node @@ -683,7 +645,7 @@ namespace Umbraco.Core.Persistence.Repositories { var engine = _templateConfig.DefaultRenderingEngine; var viewHelper = new ViewHelper(_viewsFileSystem); - if (!viewHelper.ViewExists(template)) + if (viewHelper.ViewExists(template) == false) { if (template.Content.IsNullOrWhiteSpace() == false && MasterPageHelper.IsMasterPageSyntax(template.Content)) { diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index faad2bd1fa..a37472b09a 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -1,5 +1,6 @@ using Umbraco.Core.Configuration; using System; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -18,6 +19,7 @@ namespace Umbraco.Core.Persistence private readonly ILogger _logger; private readonly ISqlSyntaxProvider _sqlSyntax; private readonly CacheHelper _cacheHelper; + private readonly CacheHelper _noCache; private readonly IUmbracoSettingsSection _settings; #region Ctors @@ -30,6 +32,29 @@ namespace Umbraco.Core.Persistence if (settings == null) throw new ArgumentNullException("settings"); _cacheHelper = cacheHelper; + + //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. + if ((_cacheHelper.RuntimeCache is DeepCloneRuntimeCacheProvider) == false) + { + var originalHelper = cacheHelper; + + _cacheHelper = new CacheHelper( + new DeepCloneRuntimeCacheProvider(originalHelper.RuntimeCache), + originalHelper.StaticCache, + originalHelper.RequestCache, + new IsolatedRuntimeCache(type => + { + var cache = originalHelper.IsolatedRuntimeCache.GetOrCreateCache(type); + return (cache is DeepCloneRuntimeCacheProvider) == false + //wrap the original if it's not DeepCloneRuntimeCacheProvider + ? new DeepCloneRuntimeCacheProvider(cache) + : cache; + })); + } + + _noCache = CacheHelper.CreateDisabledCacheHelper(); _logger = logger; _sqlSyntax = sqlSyntax; _settings = settings; @@ -53,6 +78,7 @@ namespace Umbraco.Core.Persistence { if (cacheHelper == null) throw new ArgumentNullException("cacheHelper"); _cacheHelper = cacheHelper; + _noCache = CacheHelper.CreateDisabledCacheHelper(); } [Obsolete("Use the ctor specifying all dependencies instead")] @@ -80,15 +106,15 @@ namespace Umbraco.Core.Persistence public virtual ITaskRepository CreateTaskRepository(IDatabaseUnitOfWork uow) { - return new TaskRepository(uow, - CacheHelper.CreateDisabledCacheHelper(), //never cache + return new TaskRepository(uow, + _noCache, //never cache _logger, _sqlSyntax); } public virtual IAuditRepository CreateAuditRepository(IDatabaseUnitOfWork uow) { return new AuditRepository(uow, - CacheHelper.CreateDisabledCacheHelper(), //never cache + _noCache, //never cache _logger, _sqlSyntax); } @@ -128,7 +154,6 @@ namespace Umbraco.Core.Persistence { return new DataTypeDefinitionRepository( uow, - _cacheHelper, _cacheHelper, _logger, _sqlSyntax, CreateContentTypeRepository(uow)); @@ -140,8 +165,7 @@ namespace Umbraco.Core.Persistence uow, _cacheHelper, _logger, - _sqlSyntax, - CreateLanguageRepository(uow)); + _sqlSyntax); } public virtual ILanguageRepository CreateLanguageRepository(IDatabaseUnitOfWork uow) @@ -175,7 +199,7 @@ namespace Umbraco.Core.Persistence { return new RelationRepository( uow, - CacheHelper.CreateDisabledCacheHelper(), //never cache + _noCache, //never cache _logger, _sqlSyntax, CreateRelationTypeRepository(uow)); } @@ -184,7 +208,7 @@ namespace Umbraco.Core.Persistence { return new RelationTypeRepository( uow, - CacheHelper.CreateDisabledCacheHelper(), //never cache + _noCache, //never cache _logger, _sqlSyntax); } @@ -222,7 +246,7 @@ namespace Umbraco.Core.Persistence { return new MigrationEntryRepository( uow, - CacheHelper.CreateDisabledCacheHelper(), //never cache + _noCache, //never cache _logger, _sqlSyntax); } @@ -283,8 +307,7 @@ namespace Umbraco.Core.Persistence { return new MemberGroupRepository(uow, _cacheHelper, - _logger, _sqlSyntax, - _cacheHelper); + _logger, _sqlSyntax); } public virtual IEntityRepository CreateEntityRepository(IDatabaseUnitOfWork uow) @@ -299,8 +322,8 @@ namespace Umbraco.Core.Persistence public ITaskTypeRepository CreateTaskTypeRepository(IDatabaseUnitOfWork uow) { - return new TaskTypeRepository(uow, - CacheHelper.CreateDisabledCacheHelper(), //never cache + return new TaskTypeRepository(uow, + _noCache, //never cache _logger, _sqlSyntax); } diff --git a/src/Umbraco.Core/Services/ApplicationTreeService.cs b/src/Umbraco.Core/Services/ApplicationTreeService.cs index f640fa8d22..c9ce839450 100644 --- a/src/Umbraco.Core/Services/ApplicationTreeService.cs +++ b/src/Umbraco.Core/Services/ApplicationTreeService.cs @@ -315,7 +315,7 @@ namespace Umbraco.Core.Services //remove the cache now that it has changed SD: I'm leaving this here even though it // is taken care of by events as well, I think unit tests may rely on it being cleared here. - _cache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey); + _cache.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey); } } } diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index c3d6c3f2de..cbda2fce80 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; using System.Linq; using System.Runtime.Remoting.Messaging; @@ -385,6 +386,8 @@ namespace Umbraco.Core.Services /// /// /// + [Obsolete("Use GetDescendants instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public TemplateNode GetTemplateNode(string alias) { using (var repository = _repositoryFactory.CreateTemplateRepository(_dataUowProvider.GetUnitOfWork())) @@ -399,6 +402,8 @@ namespace Umbraco.Core.Services /// /// /// + [Obsolete("Use GetDescendants instead")] + [EditorBrowsable(EditorBrowsableState.Never)] public TemplateNode FindTemplateInTree(TemplateNode anyNode, string alias) { using (var repository = _repositoryFactory.CreateTemplateRepository(_dataUowProvider.GetUnitOfWork())) diff --git a/src/Umbraco.Core/Services/LocalizationService.cs b/src/Umbraco.Core/Services/LocalizationService.cs index 6d6cf0c101..261db8672b 100644 --- a/src/Umbraco.Core/Services/LocalizationService.cs +++ b/src/Umbraco.Core/Services/LocalizationService.cs @@ -94,6 +94,9 @@ namespace Umbraco.Core.Services repository.AddOrUpdate(item); uow.Commit(); + //ensure the lazy Language callback is assigned + EnsureDictionaryItemLanguageCallback(item); + SavedDictionaryItem.RaiseEvent(new SaveEventArgs(item), this); return item; @@ -109,7 +112,10 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateDictionaryRepository(UowProvider.GetUnitOfWork())) { - return repository.Get(id); + var item = repository.Get(id); + //ensure the lazy Language callback is assigned + EnsureDictionaryItemLanguageCallback(item); + return item; } } @@ -122,7 +128,10 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateDictionaryRepository(UowProvider.GetUnitOfWork())) { - return repository.Get(id); + var item = repository.Get(id); + //ensure the lazy Language callback is assigned + EnsureDictionaryItemLanguageCallback(item); + return item; } } @@ -135,7 +144,10 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateDictionaryRepository(UowProvider.GetUnitOfWork())) { - return repository.Get(key); + var item = repository.Get(key); + //ensure the lazy Language callback is assigned + EnsureDictionaryItemLanguageCallback(item); + return item; } } @@ -149,8 +161,9 @@ namespace Umbraco.Core.Services using (var repository = RepositoryFactory.CreateDictionaryRepository(UowProvider.GetUnitOfWork())) { var query = Query.Builder.Where(x => x.ParentId == parentId); - var items = repository.GetByQuery(query); - + var items = repository.GetByQuery(query).ToArray(); + //ensure the lazy Language callback is assigned + items.ForEach(EnsureDictionaryItemLanguageCallback); return items; } } @@ -164,7 +177,10 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateDictionaryRepository(UowProvider.GetUnitOfWork())) { - return repository.GetDictionaryItemDescendants(parentId); + var items = repository.GetDictionaryItemDescendants(parentId).ToArray(); + //ensure the lazy Language callback is assigned + items.ForEach(EnsureDictionaryItemLanguageCallback); + return items; } } @@ -177,8 +193,9 @@ namespace Umbraco.Core.Services using (var repository = RepositoryFactory.CreateDictionaryRepository(UowProvider.GetUnitOfWork())) { var query = Query.Builder.Where(x => x.ParentId == null); - var items = repository.GetByQuery(query); - + var items = repository.GetByQuery(query).ToArray(); + //ensure the lazy Language callback is assigned + items.ForEach(EnsureDictionaryItemLanguageCallback); return items; } } @@ -211,6 +228,8 @@ namespace Umbraco.Core.Services { repository.AddOrUpdate(dictionaryItem); uow.Commit(); + //ensure the lazy Language callback is assigned + EnsureDictionaryItemLanguageCallback(dictionaryItem); } SavedDictionaryItem.RaiseEvent(new SaveEventArgs(dictionaryItem, false), this); @@ -265,10 +284,6 @@ namespace Umbraco.Core.Services using (var repository = RepositoryFactory.CreateLanguageRepository(UowProvider.GetUnitOfWork())) { return repository.GetByCultureName(cultureName); - //var query = Query.Builder.Where(x => x.CultureName == cultureName); - //var items = repository.GetByQuery(query); - - //return items.FirstOrDefault(); } } @@ -282,10 +297,6 @@ namespace Umbraco.Core.Services using (var repository = RepositoryFactory.CreateLanguageRepository(UowProvider.GetUnitOfWork())) { return repository.GetByIsoCode(isoCode); - //var query = Query.Builder.Where(x => x.IsoCode == isoCode); - //var items = repository.GetByQuery(query); - - //return items.FirstOrDefault(); } } @@ -357,6 +368,24 @@ namespace Umbraco.Core.Services } } + /// + /// This is here to take care of a hack - the DictionaryTranslation model contains an ILanguage reference which we don't want but + /// we cannot remove it because it would be a large breaking change, so we need to make sure it's resolved lazily. This is because + /// if developers have a lot of dictionary items and translations, the caching and cloning size gets much much larger because of + /// the large object graphs. So now we don't cache or clone the attached ILanguage + /// + private void EnsureDictionaryItemLanguageCallback(IDictionaryItem d) + { + var item = d as DictionaryItem; + if (item == null) return; + + item.GetLanguage = GetLanguageById; + foreach (var trans in item.Translations.OfType()) + { + trans.GetLanguage = GetLanguageById; + } + } + #region Event Handlers /// /// Occurs before Delete diff --git a/src/Umbraco.Core/Services/SectionService.cs b/src/Umbraco.Core/Services/SectionService.cs index cc25387a00..b810840873 100644 --- a/src/Umbraco.Core/Services/SectionService.cs +++ b/src/Umbraco.Core/Services/SectionService.cs @@ -171,7 +171,7 @@ namespace Umbraco.Core.Services //remove the cache so it gets re-read ... SD: I'm leaving this here even though it // is taken care of by events as well, I think unit tests may rely on it being cleared here. - _cache.ClearCacheItem(CacheKeys.ApplicationsCacheKey); + _cache.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationsCacheKey); } } } diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index f8a78aed3d..9d0da8aca2 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -286,6 +286,7 @@ namespace Umbraco.Core.Services _entityService = new Lazy(() => new EntityService( provider, repositoryFactory, logger, eventMessagesFactory, _contentService.Value, _contentTypeService.Value, _mediaService.Value, _dataTypeService.Value, _memberService.Value, _memberTypeService.Value, + //TODO: Consider making this an isolated cache instead of using the global one cache.RuntimeCache)); if (_relationService == null) diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 76cafe293a..f1c2eb9c67 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -156,19 +156,29 @@ + + + + + + + + + + @@ -176,6 +186,7 @@ + diff --git a/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs b/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs index 44deb1da40..63225f6725 100644 --- a/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs +++ b/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs @@ -4,9 +4,11 @@ using System.Web; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Collections; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.Repositories; +using Umbraco.Tests.Collections; namespace Umbraco.Tests.Cache { @@ -36,6 +38,23 @@ namespace Umbraco.Tests.Cache get { return _provider; } } + [Test] + public void Clones_List() + { + var original = new DeepCloneableList(); + original.Add(new DeepCloneableListTests.TestClone()); + original.Add(new DeepCloneableListTests.TestClone()); + original.Add(new DeepCloneableListTests.TestClone()); + + var val = _provider.GetCacheItem>("test", () => original); + + Assert.AreEqual(original.Count, val.Count); + foreach (var item in val) + { + Assert.IsTrue(item.IsClone); + } + } + [Test] public void Ensures_Cloned_And_Reset() { diff --git a/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs b/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs new file mode 100644 index 0000000000..32381b593b --- /dev/null +++ b/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Web.Caching; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Cache; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Cache +{ + [TestFixture] + public class DefaultCachePolicyTests + { + [Test] + public void Caches_Single() + { + var isCached = false; + var cache = new Mock(); + cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .Callback(() => + { + isCached = true; + }); + + var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, new RepositoryCachePolicyOptions()); + using (defaultPolicy) + { + var found = defaultPolicy.Get(1, o => new AuditItem(1, "blah", AuditType.Copy, 123)); + } + Assert.IsTrue(isCached); + } + + [Test] + public void Get_Single_From_Cache() + { + var cache = new Mock(); + cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem(1, "blah", AuditType.Copy, 123)); + + var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, new RepositoryCachePolicyOptions()); + using (defaultPolicy) + { + var found = defaultPolicy.Get(1, o => (AuditItem) null); + Assert.IsNotNull(found); + } + } + + [Test] + public void Caches_Per_Id_For_Get_All() + { + var cached = new List(); + var cache = new Mock(); + cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string cacheKey, Func o, TimeSpan? t, bool b, CacheItemPriority cip, CacheItemRemovedCallback circ, string[] s) => + { + cached.Add(cacheKey); + }); + cache.Setup(x => x.GetCacheItemsByKeySearch(It.IsAny())).Returns(new AuditItem[] {}); + + var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, new RepositoryCachePolicyOptions()); + using (defaultPolicy) + { + var found = defaultPolicy.GetAll(new object[] {}, o => new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }); + } + + Assert.AreEqual(2, cached.Count); + } + + [Test] + public void Get_All_Without_Ids_From_Cache() + { + var cache = new Mock(); + cache.Setup(x => x.GetCacheItemsByKeySearch(It.IsAny())).Returns(new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }); + + var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, new RepositoryCachePolicyOptions()); + using (defaultPolicy) + { + var found = defaultPolicy.GetAll(new object[] {}, o => new[] {(AuditItem) null}); + Assert.AreEqual(2, found.Length); + } + } + + [Test] + public void If_CreateOrUpdate_Throws_Cache_Is_Removed() + { + var cacheCleared = false; + var cache = new Mock(); + cache.Setup(x => x.ClearCacheItem(It.IsAny())) + .Callback(() => + { + cacheCleared = true; + }); + + var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, new RepositoryCachePolicyOptions()); + try + { + using (defaultPolicy) + { + defaultPolicy.CreateOrUpdate(new AuditItem(1, "blah", AuditType.Copy, 123), item => + { + throw new Exception("blah!"); + }); + } + } + catch + { + //we need this catch or nunit throw up + } + finally + { + Assert.IsTrue(cacheCleared); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs new file mode 100644 index 0000000000..7fd894e2a9 --- /dev/null +++ b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Web.Caching; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Cache; +using Umbraco.Core.Collections; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Cache +{ + [TestFixture] + public class FullDataSetCachePolicyTests + { + [Test] + public void Get_All_Caches_As_Single_List() + { + var cached = new List(); + IList list = null; + + var cache = new Mock(); + cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string cacheKey, Func o, TimeSpan? t, bool b, CacheItemPriority cip, CacheItemRemovedCallback circ, string[] s) => + { + cached.Add(cacheKey); + + list = o() as IList; + }); + cache.Setup(x => x.GetCacheItemsByKeySearch(It.IsAny())).Returns(new AuditItem[] { }); + + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + using (defaultPolicy) + { + var found = defaultPolicy.GetAll(new object[] { }, o => new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }); + } + + Assert.AreEqual(1, cached.Count); + Assert.IsNotNull(list); + } + + [Test] + public void Get_All_Without_Ids_From_Cache() + { + var cache = new Mock(); + + cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(() => new DeepCloneableList + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }); + + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + using (defaultPolicy) + { + var found = defaultPolicy.GetAll(new object[] { }, o => new[] { (AuditItem)null }); + Assert.AreEqual(2, found.Length); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs b/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs new file mode 100644 index 0000000000..b8e77e9267 --- /dev/null +++ b/src/Umbraco.Tests/Cache/SingleItemsOnlyCachePolicyTests.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Web.Caching; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Cache; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Cache +{ + [TestFixture] + public class SingleItemsOnlyCachePolicyTests + { + [Test] + public void Get_All_Doesnt_Cache() + { + var cached = new List(); + var cache = new Mock(); + cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string cacheKey, Func o, TimeSpan? t, bool b, CacheItemPriority cip, CacheItemRemovedCallback circ, string[] s) => + { + cached.Add(cacheKey); + }); + cache.Setup(x => x.GetCacheItemsByKeySearch(It.IsAny())).Returns(new AuditItem[] { }); + + var defaultPolicy = new SingleItemsOnlyRepositoryCachePolicy(cache.Object, new RepositoryCachePolicyOptions()); + using (defaultPolicy) + { + var found = defaultPolicy.GetAll(new object[] { }, o => new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }); + } + + Assert.AreEqual(0, cached.Count); + } + + [Test] + public void Caches_Single() + { + var isCached = false; + var cache = new Mock(); + cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .Callback(() => + { + isCached = true; + }); + + var defaultPolicy = new SingleItemsOnlyRepositoryCachePolicy(cache.Object, new RepositoryCachePolicyOptions()); + using (defaultPolicy) + { + var found = defaultPolicy.Get(1, o => new AuditItem(1, "blah", AuditType.Copy, 123)); + } + Assert.IsTrue(isCached); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Collections/DeepCloneableListTests.cs b/src/Umbraco.Tests/Collections/DeepCloneableListTests.cs new file mode 100644 index 0000000000..fcc50df60c --- /dev/null +++ b/src/Umbraco.Tests/Collections/DeepCloneableListTests.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using Umbraco.Core.Collections; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Collections +{ + [TestFixture] + public class DeepCloneableListTests + { + [Test] + public void Deep_Clones_All_Elements() + { + var list = new DeepCloneableList(); + list.Add(new TestClone()); + list.Add(new TestClone()); + list.Add(new TestClone()); + + var cloned = list.DeepClone() as DeepCloneableList; + + Assert.IsNotNull(cloned); + Assert.AreNotSame(list, cloned); + Assert.AreEqual(list.Count, cloned.Count); + } + + [Test] + public void Clones_Each_Item() + { + var list = new DeepCloneableList(); + list.Add(new TestClone()); + list.Add(new TestClone()); + list.Add(new TestClone()); + + var cloned = (DeepCloneableList) list.DeepClone(); + + foreach (var item in cloned) + { + Assert.IsTrue(item.IsClone); + } + } + + [Test] + public void Cloned_Sequence_Equals() + { + var list = new DeepCloneableList(); + list.Add(new TestClone()); + list.Add(new TestClone()); + list.Add(new TestClone()); + + var cloned = (DeepCloneableList)list.DeepClone(); + + //Test that each item in the sequence is equal - based on the equality comparer of TestClone (i.e. it's ID) + Assert.IsTrue(list.SequenceEqual(cloned)); + + //Test that each instance in the list is not the same one + foreach (var item in list) + { + var clone = cloned.Single(x => x.Id == item.Id); + Assert.AreNotSame(item, clone); + } + } + + public class TestClone : IDeepCloneable, IEquatable + { + public TestClone(Guid id) + { + Id = id; + IsClone = true; + } + + public TestClone() + { + Id = Guid.NewGuid(); + } + + public Guid Id { get; private set; } + public bool IsClone { get; private set; } + + public object DeepClone() + { + return new TestClone(Id); + } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + public bool Equals(TestClone other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id.Equals(other.Id); + } + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// + /// true if the specified object is equal to the current object; otherwise, false. + /// + /// The object to compare with the current object. + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((TestClone)obj); + } + + /// + /// Serves as the default hash function. + /// + /// + /// A hash code for the current object. + /// + public override int GetHashCode() + { + return Id.GetHashCode(); + } + + public static bool operator ==(TestClone left, TestClone right) + { + return Equals(left, right); + } + + public static bool operator !=(TestClone left, TestClone right) + { + return Equals(left, right) == false; + } + } + } +} diff --git a/src/Umbraco.Tests/Macros/MacroTests.cs b/src/Umbraco.Tests/Macros/MacroTests.cs index 4f0b91d4ca..23c1c7f1f1 100644 --- a/src/Umbraco.Tests/Macros/MacroTests.cs +++ b/src/Umbraco.Tests/Macros/MacroTests.cs @@ -27,7 +27,8 @@ namespace Umbraco.Tests.Macros var cacheHelper = new CacheHelper( new ObjectCacheRuntimeCacheProvider(), new StaticCacheProvider(), - new NullCacheProvider()); + new NullCacheProvider(), + new IsolatedRuntimeCache(type => new ObjectCacheRuntimeCacheProvider())); ApplicationContext.Current = new ApplicationContext(cacheHelper, new ProfilingLogger(Mock.Of(), Mock.Of())); UmbracoConfig.For.SetUmbracoSettings(SettingsForTests.GetDefault()); @@ -36,7 +37,7 @@ namespace Umbraco.Tests.Macros [TearDown] public void TearDown() { - ApplicationContext.Current.ApplicationCache.ClearAllCache(); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearAllCache(); ApplicationContext.Current.DisposeIfDisposable(); ApplicationContext.Current = null; } @@ -136,11 +137,11 @@ namespace Umbraco.Tests.Macros public void Macro_Needs_Removing_Based_On_Macro_File(int minutesToNow, bool expectedResult) { var now = DateTime.Now; - ApplicationContext.Current.ApplicationCache.InsertCacheItem( + ApplicationContext.Current.ApplicationCache.RuntimeCache.InsertCacheItem( "TestDate", - CacheItemPriority.NotRemovable, - new TimeSpan(0, 0, 60), - () => now.AddMinutes(minutesToNow)); //add a datetime value of 'now' with the minutes offset + priority: CacheItemPriority.NotRemovable, + timeout: new TimeSpan(0, 0, 60), + getCacheItem: () => now.AddMinutes(minutesToNow)); //add a datetime value of 'now' with the minutes offset //now we need to update a file's date to 'now' to compare var path = Path.Combine(TestHelpers.TestHelper.CurrentAssemblyDirectory, "temp.txt"); diff --git a/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs b/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs index d0a8c21581..46d6ea1022 100644 --- a/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs +++ b/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Serialization; @@ -36,12 +37,15 @@ namespace Umbraco.Tests.Models Assert.AreEqual(clone.Key, item.Key); Assert.AreEqual(clone.UpdateDate, item.UpdateDate); Assert.AreNotSame(clone.Language, item.Language); - Assert.AreEqual(clone.Language, item.Language); + //This is null because we are ignoring it from cloning due to caching/cloning issues - we don't really want + // this entity attached to this item but we're stuck with it for now + Assert.IsNull(clone.Language); + Assert.AreEqual(clone.LanguageId, item.LanguageId); Assert.AreEqual(clone.Value, item.Value); //This double verifies by reflection var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) + foreach (var propertyInfo in allProps.Where(x => x.Name != "Language")) { Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index aa0b8a8ffe..49f3abc97f 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -35,7 +35,6 @@ namespace Umbraco.Tests.Persistence.Repositories { var dataTypeDefinitionRepository = new DataTypeDefinitionRepository( unitOfWork, CacheHelper.CreateDisabledCacheHelper(), - CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, new ContentTypeRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, new TemplateRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of()))); @@ -503,10 +502,14 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - var cache = new CacheHelper(new ObjectCacheRuntimeCacheProvider(), new StaticCacheProvider(), new StaticCacheProvider()); + var cache = new CacheHelper( + new ObjectCacheRuntimeCacheProvider(), + new StaticCacheProvider(), + new StaticCacheProvider(), + new IsolatedRuntimeCache(type => new ObjectCacheRuntimeCacheProvider())); Func creator = () => new DataTypeDefinitionRepository( - unitOfWork, CacheHelper.CreateDisabledCacheHelper(), + unitOfWork, cache, Mock.Of(), SqlSyntax, new ContentTypeRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, @@ -529,7 +532,8 @@ namespace Umbraco.Tests.Persistence.Repositories var collection = repository.GetPreValuesCollectionByDataTypeId(dtd.Id); } - var cached = cache.RuntimeCache.GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-"); + var cached = cache.IsolatedRuntimeCache.GetCache().Result + .GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-"); Assert.IsNotNull(cached); Assert.AreEqual(1, cached.Count()); @@ -542,10 +546,14 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - var cache = new CacheHelper(new ObjectCacheRuntimeCacheProvider(), new StaticCacheProvider(), new StaticCacheProvider()); + var cache = new CacheHelper( + new ObjectCacheRuntimeCacheProvider(), + new StaticCacheProvider(), + new StaticCacheProvider(), + new IsolatedRuntimeCache(type => new ObjectCacheRuntimeCacheProvider())); Func creator = () => new DataTypeDefinitionRepository( - unitOfWork, CacheHelper.CreateDisabledCacheHelper(), + unitOfWork, cache, Mock.Of(), SqlSyntax, new ContentTypeRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, @@ -568,7 +576,8 @@ namespace Umbraco.Tests.Persistence.Repositories var val = repository.GetPreValueAsString(Convert.ToInt32(id)); } - var cached = cache.RuntimeCache.GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-"); + var cached = cache.IsolatedRuntimeCache.GetCache().Result + .GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-"); Assert.IsNotNull(cached); Assert.AreEqual(1, cached.Count()); diff --git a/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs index d21da8c6e2..6b56181eac 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DictionaryRepositoryTest.cs @@ -27,10 +27,9 @@ namespace Umbraco.Tests.Persistence.Repositories CreateTestData(); } - private DictionaryRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out LanguageRepository languageRepository) + private DictionaryRepository CreateRepository(IDatabaseUnitOfWork unitOfWork) { - languageRepository = new LanguageRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); - var dictionaryRepository = new DictionaryRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), new SqlCeSyntaxProvider(), languageRepository); + var dictionaryRepository = new DictionaryRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), new SqlCeSyntaxProvider()); return dictionaryRepository; } @@ -41,8 +40,8 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + + using (var repository = CreateRepository(unitOfWork)) { var dictionaryItem = (IDictionaryItem)new DictionaryItem("Testing1235") { @@ -74,8 +73,8 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + + using (var repository = CreateRepository(unitOfWork)) { var dictionaryItem = (IDictionaryItem)new DictionaryItem("Testing1235") { @@ -107,8 +106,8 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + + using (var repository = CreateRepository(unitOfWork)) { var dictionaryItem = (IDictionaryItem)new DictionaryItem("Testing1235") { @@ -141,8 +140,8 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + + using (var repository = CreateRepository(unitOfWork)) { var dictionaryItem = (IDictionaryItem) new DictionaryItem("Testing1235"); @@ -168,8 +167,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + using (var repository = CreateRepository(unitOfWork)) { // Act @@ -190,8 +188,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + using (var repository = CreateRepository(unitOfWork)) { // Act @@ -211,8 +208,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + using (var repository = CreateRepository(unitOfWork)) { // Act @@ -232,8 +228,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + using (var repository = CreateRepository(unitOfWork)) { // Act @@ -251,8 +246,8 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + using (var languageRepository = new LanguageRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax)) + using (var repository = CreateRepository(unitOfWork)) { var language = languageRepository.Get(1); @@ -282,8 +277,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + using (var repository = CreateRepository(unitOfWork)) { // Act @@ -310,8 +304,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - var languageRepository = new LanguageRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); - var repository = new DictionaryRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), new SqlCeSyntaxProvider(), languageRepository); + var repository = new DictionaryRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), new SqlCeSyntaxProvider()); var languageNo = new Language("nb-NO") { CultureName = "nb-NO" }; ServiceContext.LocalizationService.Save(languageNo); @@ -325,12 +318,12 @@ namespace Umbraco.Tests.Persistence.Repositories repository.AddOrUpdate(item); unitOfWork.Commit(); - var dictionaryItem = repository.Get(1); - + var dictionaryItem = (DictionaryItem)repository.Get(1); + // Assert Assert.That(dictionaryItem, Is.Not.Null); Assert.That(dictionaryItem.Translations.Count(), Is.EqualTo(3)); - Assert.That(dictionaryItem.Translations.Single(t => t.Language.IsoCode == "nb-NO").Value, Is.EqualTo("Les mer")); + Assert.That(dictionaryItem.Translations.Single(t => t.LanguageId == languageNo.Id).Value, Is.EqualTo("Les mer")); } [Test] @@ -339,8 +332,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + using (var repository = CreateRepository(unitOfWork)) { // Act @@ -361,8 +353,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - LanguageRepository languageRepository; - using (var repository = CreateRepository(unitOfWork, out languageRepository)) + using (var repository = CreateRepository(unitOfWork)) { // Act diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs index 64e8587e00..0d3b4e4e47 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberRepositoryTest.cs @@ -37,7 +37,7 @@ namespace Umbraco.Tests.Persistence.Repositories private MemberRepository CreateRepository(IDatabaseUnitOfWork unitOfWork, out MemberTypeRepository memberTypeRepository, out MemberGroupRepository memberGroupRepository) { memberTypeRepository = new MemberTypeRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); - memberGroupRepository = new MemberGroupRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, CacheHelper.CreateDisabledCacheHelper()); + memberGroupRepository = new MemberGroupRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); var tagRepo = new TagRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax); var repository = new MemberRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, memberTypeRepository, memberGroupRepository, tagRepo, Mock.Of()); return repository; diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index ccd42301a3..5bb59eaa04 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -176,6 +176,10 @@ + + + + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html index 8d44b008a1..b2373f2d12 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/boolean/boolean.html @@ -1,3 +1,3 @@
-
+ \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html index 898afe88a3..e6d49e5c6c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/datepicker/datepicker.html @@ -2,7 +2,7 @@
- - - Required Invalid email -
+ \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/integer/integer.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/integer/integer.html index ff82be99b2..2108340d5c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/integer/integer.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/integer/integer.html @@ -2,8 +2,9 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.html index b1049a2309..e9fbde9027 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/tags/tags.html @@ -14,6 +14,7 @@ False ..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll - - ..\packages\ImageProcessor.2.3.0.0\lib\net45\ImageProcessor.dll + + ..\packages\ImageProcessor.2.3.2.0\lib\net45\ImageProcessor.dll True - - ..\packages\ImageProcessor.Web.4.4.0.0\lib\net45\ImageProcessor.Web.dll + + ..\packages\ImageProcessor.Web.4.5.0.0\lib\net45\ImageProcessor.Web.dll True @@ -334,8 +334,9 @@ umbraco.providers - - ..\packages\UrlRewritingNet.UrlRewriter.2.0.60829.1\lib\UrlRewritingNet.UrlRewriter.dll + + ..\packages\UrlRewritingNet.UrlRewriter.2.0.7\lib\UrlRewritingNet.UrlRewriter.dll + True diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index e235107f13..f5d0ff53b6 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -5,8 +5,8 @@ - - + + @@ -32,5 +32,5 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/ApplicationCacheRefresher.cs b/src/Umbraco.Web/Cache/ApplicationCacheRefresher.cs index 8b742b1b2c..5c1ec9e1d4 100644 --- a/src/Umbraco.Web/Cache/ApplicationCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ApplicationCacheRefresher.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web.Cache public override void RefreshAll() { - ApplicationContext.Current.ApplicationCache.ClearCacheItem(CacheKeys.ApplicationsCacheKey); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationsCacheKey); base.RefreshAll(); } @@ -38,7 +38,7 @@ namespace Umbraco.Web.Cache public override void Remove(int id) { - ApplicationContext.Current.ApplicationCache.ClearCacheItem(CacheKeys.ApplicationsCacheKey); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationsCacheKey); base.Remove(id); } diff --git a/src/Umbraco.Web/Cache/ApplicationTreeCacheRefresher.cs b/src/Umbraco.Web/Cache/ApplicationTreeCacheRefresher.cs index e267b441a2..2f1ab4b891 100644 --- a/src/Umbraco.Web/Cache/ApplicationTreeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ApplicationTreeCacheRefresher.cs @@ -26,7 +26,7 @@ namespace Umbraco.Web.Cache public override void RefreshAll() { - ApplicationContext.Current.ApplicationCache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey); base.RefreshAll(); } @@ -38,7 +38,7 @@ namespace Umbraco.Web.Cache public override void Remove(int id) { - ApplicationContext.Current.ApplicationCache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(CacheKeys.ApplicationTreeCacheKey); base.Remove(id); } diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index c552fb6a3b..15e01fd430 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -125,8 +125,9 @@ namespace Umbraco.Web.Cache //public access events PublicAccessService.Saved += PublicAccessService_Saved; + PublicAccessService.Deleted += PublicAccessService_Deleted; ; } - + #region Publishing void PublishingStrategy_UnPublished(IPublishingStrategy sender, PublishEventArgs e) @@ -211,6 +212,11 @@ namespace Umbraco.Web.Cache DistributedCache.Instance.RefreshPublicAccess(); } + private void PublicAccessService_Deleted(IPublicAccessService sender, DeleteEventArgs e) + { + DistributedCache.Instance.RefreshPublicAccess(); + } + #endregion #region Content service event handlers diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 502e215c90..9664681bb7 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -126,9 +126,9 @@ namespace Umbraco.Web.Cache public override void RefreshAll() { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); - + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + //all property type cache ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.PropertyTypeCacheKey); //all content type property cache @@ -179,7 +179,7 @@ namespace Umbraco.Web.Cache /// - InMemoryCacheProvider.Current.Clear(); /// - RoutesCache.Clear(); /// - private static void ClearContentTypeCache(JsonPayload[] payloads) + private void ClearContentTypeCache(JsonPayload[] payloads) { var needsContentRefresh = false; @@ -214,18 +214,18 @@ namespace Umbraco.Web.Cache { if (payloads.Any(x => x.Type == typeof (IContentType).Name)) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); } if (payloads.Any(x => x.Type == typeof(IMediaType).Name)) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); } if (payloads.Any(x => x.Type == typeof(IMemberType).Name)) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); } @@ -291,7 +291,7 @@ namespace Umbraco.Web.Cache /// /// true if the entity was deleted, false if it is just an update /// - private static void ClearContentTypeCache(bool isDeleted, params int[] ids) + private void ClearContentTypeCache(bool isDeleted, params int[] ids) { ClearContentTypeCache( ids.Select( diff --git a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs index 86114d5f77..11b3ab6294 100644 --- a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs @@ -92,27 +92,22 @@ namespace Umbraco.Web.Cache // db data type to store the value against and anytime a datatype changes, this also might change // we basically need to clear all sorts of runtime caches here because so many things depend upon a data type - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.IdToKeyCacheKey); ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.KeyToIdCacheKey); payloads.ForEach(payload => { - //clear both the Id and Unique Id cache since we cache both in the legacy classes :( - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( - string.Format("{0}{1}", CacheKeys.DataTypeCacheKey, payload.Id)); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( - string.Format("{0}{1}", CacheKeys.DataTypeCacheKey, payload.UniqueId)); - //clears the prevalue cache - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( - string.Format("{0}{1}", CacheKeys.DataTypePreValuesCacheKey, payload.Id)); - + var dataTypeCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + if (dataTypeCache) + dataTypeCache.Result.ClearCacheByKeySearch(string.Format("{0}{1}", CacheKeys.DataTypePreValuesCacheKey, payload.Id)); + PublishedContentType.ClearDataType(payload.Id); }); diff --git a/src/Umbraco.Web/Cache/DictionaryCacheRefresher.cs b/src/Umbraco.Web/Cache/DictionaryCacheRefresher.cs index 8209d247a2..ae36be33df 100644 --- a/src/Umbraco.Web/Cache/DictionaryCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DictionaryCacheRefresher.cs @@ -28,13 +28,13 @@ namespace Umbraco.Web.Cache public override void Refresh(int id) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); base.Refresh(id); } public override void Remove(int id) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); base.Remove(id); } } diff --git a/src/Umbraco.Web/Cache/DistributedCache.cs b/src/Umbraco.Web/Cache/DistributedCache.cs index 27c30d89e8..6848ce2496 100644 --- a/src/Umbraco.Web/Cache/DistributedCache.cs +++ b/src/Umbraco.Web/Cache/DistributedCache.cs @@ -38,6 +38,9 @@ namespace Umbraco.Web.Cache public const string ContentTypeCacheRefresherId = "6902E22C-9C10-483C-91F3-66B7CAE9E2F5"; public const string LanguageCacheRefresherId = "3E0F95D8-0BE5-44B8-8394-2B8750B62654"; public const string DomainCacheRefresherId = "11290A79-4B57-4C99-AD72-7748A3CF38AF"; + + [Obsolete("This is no longer used and will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] public const string StylesheetCacheRefresherId = "E0633648-0DEB-44AE-9A48-75C3A55CB670"; public const string StylesheetPropertyCacheRefresherId = "2BC7A3A4-6EB1-4FBC-BAA3-C9E7B6D36D38"; public const string DataTypeCacheRefresherId = "35B16C25-A17E-45D7-BC8F-EDAB1DCC28D2"; diff --git a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs index 66cb82ff7c..750872d8af 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs @@ -442,7 +442,7 @@ namespace Umbraco.Web.Cache public static void ClearXsltCacheOnCurrentServer(this DistributedCache dc) { if (UmbracoConfig.For.UmbracoSettings().Content.UmbracoLibraryCacheDuration <= 0) return; - ApplicationContext.Current.ApplicationCache.ClearCacheObjectTypes("MS.Internal.Xml.XPath.XPathSelectionIterator"); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes("MS.Internal.Xml.XPath.XPathSelectionIterator"); } #endregion diff --git a/src/Umbraco.Web/Cache/DomainCacheRefresher.cs b/src/Umbraco.Web/Cache/DomainCacheRefresher.cs index 51a2c79b2d..955d87ff59 100644 --- a/src/Umbraco.Web/Cache/DomainCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DomainCacheRefresher.cs @@ -41,8 +41,8 @@ namespace Umbraco.Web.Cache } private void ClearCache() - { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + { + ClearAllIsolatedCacheByEntityType(); // SD: we need to clear the routes cache here! // diff --git a/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs b/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs index 019be66b15..087aa36764 100644 --- a/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/LanguageCacheRefresher.cs @@ -28,13 +28,15 @@ namespace Umbraco.Web.Cache public override void Refresh(int id) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); base.Refresh(id); } public override void Remove(int id) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); + //if a language is removed, then all dictionary cache needs to be removed + ClearAllIsolatedCacheByEntityType(); base.Remove(id); } } diff --git a/src/Umbraco.Web/Cache/MacroCacheRefresher.cs b/src/Umbraco.Web/Cache/MacroCacheRefresher.cs index a06501032f..cb62dbeb39 100644 --- a/src/Umbraco.Web/Cache/MacroCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MacroCacheRefresher.cs @@ -176,7 +176,7 @@ namespace Umbraco.Web.Cache prefix => ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(prefix)); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); base.RefreshAll(); } @@ -191,7 +191,11 @@ namespace Umbraco.Web.Cache alias => ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(alias)); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(payload.Id)); + var macroRepoCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + if (macroRepoCache) + { + macroRepoCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(payload.Id)); + } }); base.Refresh(jsonPayload); diff --git a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs index dce512b1dc..a0e037e110 100644 --- a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Cache public class MediaCacheRefresher : JsonCacheRefresherBase { #region Static helpers - + /// /// Converts the json to a JsonPayload object /// @@ -143,13 +143,13 @@ namespace Umbraco.Web.Cache } public override void Remove(int id) - { + { ClearCache(FromMedia(ApplicationContext.Current.Services.MediaService.GetById(id), //NOTE: we'll just default to trashed for this one. OperationType.Trashed)); base.Remove(id); } - + private static void ClearCache(params JsonPayload[] payloads) { if (payloads == null) return; @@ -159,40 +159,40 @@ namespace Umbraco.Web.Cache ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); payloads.ForEach(payload => + { + var mediaCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + + //if there's no path, then just use id (this will occur on permanent deletion like emptying recycle bin) + if (payload.Path.IsNullOrWhiteSpace()) { - - //if there's no path, then just use id (this will occur on permanent deletion like emptying recycle bin) - if (payload.Path.IsNullOrWhiteSpace()) + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( + string.Format("{0}_{1}", CacheKeys.MediaCacheKey, payload.Id)); + } + else + { + foreach (var idPart in payload.Path.Split(',')) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( - string.Format("{0}_{1}", CacheKeys.MediaCacheKey, payload.Id)); - } - else - { - foreach (var idPart in payload.Path.Split(',')) + int idPartAsInt; + if (int.TryParse(idPart, out idPartAsInt) && mediaCache) { - int idPartAsInt; - if (int.TryParse(idPart, out idPartAsInt)) - { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem( - RepositoryBase.GetCacheIdKey(idPartAsInt)); - } + mediaCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(idPartAsInt)); + } + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( + string.Format("{0}_{1}_True", CacheKeys.MediaCacheKey, idPart)); + + // Also clear calls that only query this specific item! + if (idPart == payload.Id.ToString(CultureInfo.InvariantCulture)) ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( - string.Format("{0}_{1}_True", CacheKeys.MediaCacheKey, idPart)); - - // Also clear calls that only query this specific item! - if (idPart == payload.Id.ToString(CultureInfo.InvariantCulture)) - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( - string.Format("{0}_{1}", CacheKeys.MediaCacheKey, payload.Id)); - } + string.Format("{0}_{1}", CacheKeys.MediaCacheKey, payload.Id)); } + } + + // published cache... + PublishedMediaCache.ClearCache(payload.Id); + }); - // published cache... - PublishedMediaCache.ClearCache(payload.Id); - }); - } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/MemberCacheRefresher.cs b/src/Umbraco.Web/Cache/MemberCacheRefresher.cs index a0167454a5..32e6a69717 100644 --- a/src/Umbraco.Web/Cache/MemberCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MemberCacheRefresher.cs @@ -69,7 +69,9 @@ namespace Umbraco.Web.Cache ApplicationContext.Current.ApplicationCache.RuntimeCache. ClearCacheByKeySearch(string.Format("{0}{1}", CacheKeys.MemberBusinessLogicCacheKey, id)); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); + var memberCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + if (memberCache) + memberCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs b/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs index e410ab560c..dc2ba39b9d 100644 --- a/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs @@ -104,13 +104,13 @@ namespace Umbraco.Web.Cache { if (payloads == null) return; + var memberGroupCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); payloads.ForEach(payload => { - if (payload != null) + if (payload != null && memberGroupCache) { - ApplicationContext.Current.ApplicationCache.RuntimeCache - .ClearCacheByKeySearch(string.Format("{0}.{1}", typeof(IMemberGroup).FullName, payload.Name)); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(payload.Id)); + memberGroupCache.Result.ClearCacheByKeySearch(string.Format("{0}.{1}", typeof(IMemberGroup).FullName, payload.Name)); + memberGroupCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(payload.Id)); } }); diff --git a/src/Umbraco.Web/Cache/PageCacheRefresher.cs b/src/Umbraco.Web/Cache/PageCacheRefresher.cs index 73f525383e..a8e24009fb 100644 --- a/src/Umbraco.Web/Cache/PageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/PageCacheRefresher.cs @@ -76,7 +76,7 @@ namespace Umbraco.Web.Cache content.Instance.ClearDocumentCache(id); DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); base.Remove(id); } @@ -86,7 +86,7 @@ namespace Umbraco.Web.Cache content.Instance.UpdateDocumentCache(new Document(instance)); DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); base.Refresh(instance); } @@ -96,7 +96,7 @@ namespace Umbraco.Web.Cache content.Instance.ClearDocumentCache(new Document(instance)); DistributedCache.Instance.ClearAllMacroCacheOnCurrentServer(); DistributedCache.Instance.ClearXsltCacheOnCurrentServer(); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); base.Remove(instance); } } diff --git a/src/Umbraco.Web/Cache/PublicAccessCacheRefresher.cs b/src/Umbraco.Web/Cache/PublicAccessCacheRefresher.cs index 09b38f1ac3..922e01e2a2 100644 --- a/src/Umbraco.Web/Cache/PublicAccessCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/PublicAccessCacheRefresher.cs @@ -27,25 +27,25 @@ namespace Umbraco.Web.Cache public override void Refresh(Guid id) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); base.Refresh(id); } public override void Refresh(int id) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); base.Refresh(id); } public override void RefreshAll() { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); base.RefreshAll(); } public override void Remove(int id) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); base.Remove(id); } } diff --git a/src/Umbraco.Web/Cache/StylesheetCacheRefresher.cs b/src/Umbraco.Web/Cache/StylesheetCacheRefresher.cs index 5d98671a76..959a937e7b 100644 --- a/src/Umbraco.Web/Cache/StylesheetCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/StylesheetCacheRefresher.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using Umbraco.Core; using Umbraco.Core.Cache; @@ -7,6 +8,8 @@ namespace Umbraco.Web.Cache /// /// A cache refresher to ensure stylesheet cache is refreshed when stylesheets change /// + [Obsolete("This is no longer used and will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] public sealed class StylesheetCacheRefresher : CacheRefresherBase { protected override StylesheetCacheRefresher Instance @@ -24,27 +27,5 @@ namespace Umbraco.Web.Cache get { return "Stylesheet cache refresher"; } } - public override void RefreshAll() - { - ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(CacheKeys.StylesheetCacheKey); - base.RefreshAll(); - } - - public override void Refresh(int id) - { - ApplicationContext.Current.ApplicationCache.ClearCacheItem(GetStylesheetCacheKey(id)); - base.Refresh(id); - } - - public override void Remove(int id) - { - ApplicationContext.Current.ApplicationCache.ClearCacheItem(GetStylesheetCacheKey(id)); - base.Remove(id); - } - - private static string GetStylesheetCacheKey(int id) - { - return CacheKeys.StylesheetCacheKey + id; - } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/StylesheetPropertyCacheRefresher.cs b/src/Umbraco.Web/Cache/StylesheetPropertyCacheRefresher.cs index d7006c8531..5b72e0384b 100644 --- a/src/Umbraco.Web/Cache/StylesheetPropertyCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/StylesheetPropertyCacheRefresher.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using Umbraco.Core; using Umbraco.Core.Cache; @@ -7,6 +8,8 @@ namespace Umbraco.Web.Cache /// /// A cache refresher to ensure stylesheet property cache is refreshed when stylesheet properties change /// + [Obsolete("This is no longer used and will be removed in future versions")] + [EditorBrowsable(EditorBrowsableState.Never)] public sealed class StylesheetPropertyCacheRefresher : CacheRefresherBase { protected override StylesheetPropertyCacheRefresher Instance @@ -23,28 +26,6 @@ namespace Umbraco.Web.Cache { get { return "Stylesheet property cache refresher"; } } - - public override void RefreshAll() - { - ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(CacheKeys.StylesheetPropertyCacheKey); - base.RefreshAll(); - } - - public override void Refresh(int id) - { - ApplicationContext.Current.ApplicationCache.ClearCacheItem(GetStylesheetPropertyCacheKey(id)); - base.Refresh(id); - } - - public override void Remove(int id) - { - ApplicationContext.Current.ApplicationCache.ClearCacheItem(GetStylesheetPropertyCacheKey(id)); - base.Remove(id); - } - - private static string GetStylesheetPropertyCacheKey(int id) - { - return CacheKeys.StylesheetPropertyCacheKey + id; - } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs b/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs index 22bce3d5bf..4d6c0c612b 100644 --- a/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs @@ -52,8 +52,8 @@ namespace Umbraco.Web.Cache // all three of these types are referenced by templates, and the cache needs to be cleared on every server, // otherwise things like looking up content type's after a template is removed is still going to show that // it has an associated template. - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); base.Remove(id); } @@ -66,7 +66,7 @@ namespace Umbraco.Web.Cache string.Format("{0}{1}", CacheKeys.TemplateFrontEndCacheKey, id)); //need to clear the runtime cache for templates - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); } } diff --git a/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs b/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs index e3601188f4..beb5b8db7d 100644 --- a/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs @@ -77,16 +77,16 @@ namespace Umbraco.Web.Cache public override void RefreshAll() { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); base.RefreshAll(); } public override void Refresh(int id) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearRepositoryCacheItemById(id); + ClearAllIsolatedCacheByEntityType(); content.Instance.UpdateSortOrder(id); DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); base.Refresh(id); @@ -94,8 +94,8 @@ namespace Umbraco.Web.Cache public override void Remove(int id) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearRepositoryCacheItemById(id); + ClearAllIsolatedCacheByEntityType(); DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); base.Remove(id); } @@ -103,8 +103,8 @@ namespace Umbraco.Web.Cache public override void Refresh(IContent instance) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(instance.Id)); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearRepositoryCacheItemById(instance.Id); + ClearAllIsolatedCacheByEntityType(); content.Instance.UpdateSortOrder(instance); DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); base.Refresh(instance); @@ -112,8 +112,8 @@ namespace Umbraco.Web.Cache public override void Remove(IContent instance) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(instance.Id)); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearRepositoryCacheItemById(instance.Id); + ClearAllIsolatedCacheByEntityType(); DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); base.Remove(instance); } @@ -124,11 +124,11 @@ namespace Umbraco.Web.Cache /// public void Refresh(string jsonPayload) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); foreach (var payload in DeserializeFromJsonPayload(jsonPayload)) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(payload.Id)); + ClearRepositoryCacheItemById(payload.Id); content.Instance.UpdateSortOrder(payload.Id); } @@ -136,6 +136,15 @@ namespace Umbraco.Web.Cache OnCacheUpdated(Instance, new CacheRefresherEventArgs(jsonPayload, MessageType.RefreshByJson)); } + + private void ClearRepositoryCacheItemById(int id) + { + var contentCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + if (contentCache) + { + contentCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/UserCacheRefresher.cs b/src/Umbraco.Web/Cache/UserCacheRefresher.cs index 95a7fdac49..2f8a370cd4 100644 --- a/src/Umbraco.Web/Cache/UserCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/UserCacheRefresher.cs @@ -30,9 +30,9 @@ namespace Umbraco.Web.Cache public override void RefreshAll() { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.UserPermissionsCacheKey); - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.UserContextCacheKey); + ClearAllIsolatedCacheByEntityType(); + if (UserPermissionsCache) + UserPermissionsCache.Result.ClearCacheByKeySearch(CacheKeys.UserPermissionsCacheKey); base.RefreshAll(); } @@ -44,16 +44,20 @@ namespace Umbraco.Web.Cache public override void Remove(int id) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); - - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(string.Format("{0}{1}", CacheKeys.UserPermissionsCacheKey, id)); - - //we need to clear all UserContextCacheKey since we cannot invalidate based on ID since the cache is done so based - //on the current contextId stored in the database - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.UserContextCacheKey); + var userCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + if (userCache) + userCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); + if (UserPermissionsCache) + UserPermissionsCache.Result.ClearCacheItem(string.Format("{0}{1}", CacheKeys.UserPermissionsCacheKey, id)); + base.Remove(id); } + private Attempt UserPermissionsCache + { + get { return ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); } + } + } } \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/UserPermissionsCacheRefresher.cs b/src/Umbraco.Web/Cache/UserPermissionsCacheRefresher.cs index db1baf1bcd..64744024ed 100644 --- a/src/Umbraco.Web/Cache/UserPermissionsCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/UserPermissionsCacheRefresher.cs @@ -1,6 +1,7 @@ using System; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Models.Membership; namespace Umbraco.Web.Cache { @@ -31,7 +32,8 @@ namespace Umbraco.Web.Cache public override void RefreshAll() { - ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(CacheKeys.UserPermissionsCacheKey); + if (UserPermissionsCache) + UserPermissionsCache.Result.ClearCacheByKeySearch(CacheKeys.UserPermissionsCacheKey); base.RefreshAll(); } @@ -43,8 +45,14 @@ namespace Umbraco.Web.Cache public override void Remove(int id) { - ApplicationContext.Current.ApplicationCache.ClearCacheItem(string.Format("{0}{1}", CacheKeys.UserPermissionsCacheKey, id)); + if (UserPermissionsCache) + UserPermissionsCache.Result.ClearCacheItem(string.Format("{0}{1}", CacheKeys.UserPermissionsCacheKey, id)); base.Remove(id); } + + private Attempt UserPermissionsCache + { + get { return ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); } + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/UserTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/UserTypeCacheRefresher.cs index 2beaa4347d..ca3f93c068 100644 --- a/src/Umbraco.Web/Cache/UserTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/UserTypeCacheRefresher.cs @@ -29,19 +29,23 @@ namespace Umbraco.Web.Cache public override void RefreshAll() { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes(); + ClearAllIsolatedCacheByEntityType(); base.RefreshAll(); } public override void Refresh(int id) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); + var userTypeCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + if (userTypeCache) + userTypeCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); base.Refresh(id); } public override void Remove(int id) { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); + var userTypeCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + if (userTypeCache) + userTypeCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); base.Remove(id); } diff --git a/src/Umbraco.Web/CacheHelperExtensions.cs b/src/Umbraco.Web/CacheHelperExtensions.cs index f23bb6d070..1b0451a999 100644 --- a/src/Umbraco.Web/CacheHelperExtensions.cs +++ b/src/Umbraco.Web/CacheHelperExtensions.cs @@ -58,7 +58,7 @@ namespace Umbraco.Web /// public static void ClearPartialViewCache(this CacheHelper cacheHelper) { - cacheHelper.ClearCacheByKeySearch(PartialViewCacheKey); + cacheHelper.RuntimeCache.ClearCacheByKeySearch(PartialViewCacheKey); } } } diff --git a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs b/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs index 4087d26bea..fdb4145160 100644 --- a/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs +++ b/src/Umbraco.Web/Dictionary/UmbracoCultureDictionary.cs @@ -98,6 +98,11 @@ namespace Umbraco.Web.Dictionary /// /// /// + /// + /// NOTE: The result of this is not cached anywhere - the underlying repository does not cache + /// the child lookups because that is done by a query lookup. This method isn't used in our codebase + /// so I don't think this is a performance issue but if devs are using this it could be optimized here. + /// public IDictionary GetChildren(string key) { var result = new Dictionary(); @@ -131,6 +136,7 @@ namespace Umbraco.Web.Dictionary get { //ensure it's stored/retrieved from request cache + //NOTE: This is no longer necessary since these are cached at the runtime level, but we can leave it here for now. return _requestCacheProvider.GetCacheItem(typeof (DefaultCultureDictionary).Name + "Culture", () => _localizationService.GetLanguageByIsoCode(Culture.Name)); } diff --git a/src/Umbraco.Web/Media/ImageUrl.cs b/src/Umbraco.Web/Media/ImageUrl.cs index 8f56d8bfa4..dff9358a38 100644 --- a/src/Umbraco.Web/Media/ImageUrl.cs +++ b/src/Umbraco.Web/Media/ImageUrl.cs @@ -87,7 +87,7 @@ namespace Umbraco.Web.Media private static object GetContentFromCache(int nodeIdInt, string field) { - var content = ApplicationContext.Current.ApplicationCache.GetCacheItem( + var content = ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem( string.Format("{0}{1}_{2}", CacheKeys.ContentItemCacheKey, nodeIdInt.ToString(CultureInfo.InvariantCulture), field)); return content; } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 89780c9aae..a88c21f9bb 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -286,9 +286,9 @@ {D7636876-0756-43CB-A192-138C6F0D5E42} umbraco.providers - - False - ..\packages\UrlRewritingNet.UrlRewriter.2.0.60829.1\lib\UrlRewritingNet.UrlRewriter.dll + + ..\packages\UrlRewritingNet.UrlRewriter.2.0.7\lib\UrlRewritingNet.UrlRewriter.dll + True diff --git a/src/Umbraco.Web/packages.config b/src/Umbraco.Web/packages.config index d255e152ba..75747af5ee 100644 --- a/src/Umbraco.Web/packages.config +++ b/src/Umbraco.Web/packages.config @@ -26,6 +26,6 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index 23e44896f4..0bee9e631a 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -297,7 +297,7 @@ namespace umbraco ClearContextCache(); var cachedFieldKeyStart = string.Format("{0}{1}_", CacheKeys.ContentItemCacheKey, d.Id); - ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(cachedFieldKeyStart); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(cachedFieldKeyStart); FireAfterUpdateDocumentCache(d, e); } diff --git a/src/Umbraco.Web/umbraco.presentation/library.cs b/src/Umbraco.Web/umbraco.presentation/library.cs index cbfdafc56a..d9b1d520ed 100644 --- a/src/Umbraco.Web/umbraco.presentation/library.cs +++ b/src/Umbraco.Web/umbraco.presentation/library.cs @@ -493,11 +493,11 @@ namespace umbraco { if (UmbracoConfig.For.UmbracoSettings().Content.UmbracoLibraryCacheDuration > 0) { - var xml = ApplicationContext.Current.ApplicationCache.GetCacheItem( + var xml = ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem( string.Format( "{0}_{1}_{2}", CacheKeys.MediaCacheKey, MediaId, Deep), - TimeSpan.FromSeconds(UmbracoConfig.For.UmbracoSettings().Content.UmbracoLibraryCacheDuration), - () => GetMediaDo(MediaId, Deep)); + timeout: TimeSpan.FromSeconds(UmbracoConfig.For.UmbracoSettings().Content.UmbracoLibraryCacheDuration), + getCacheItem: () => GetMediaDo(MediaId, Deep)); if (xml != null) { @@ -552,11 +552,11 @@ namespace umbraco { if (UmbracoConfig.For.UmbracoSettings().Content.UmbracoLibraryCacheDuration > 0) { - var xml = ApplicationContext.Current.ApplicationCache.GetCacheItem( + var xml = ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem( string.Format( "{0}_{1}", CacheKeys.MemberLibraryCacheKey, MemberId), - TimeSpan.FromSeconds(UmbracoConfig.For.UmbracoSettings().Content.UmbracoLibraryCacheDuration), - () => GetMemberDo(MemberId)); + timeout: TimeSpan.FromSeconds(UmbracoConfig.For.UmbracoSettings().Content.UmbracoLibraryCacheDuration), + getCacheItem: () => GetMemberDo(MemberId)); if (xml != null) { diff --git a/src/Umbraco.Web/umbraco.presentation/macro.cs b/src/Umbraco.Web/umbraco.presentation/macro.cs index e14cef4eb1..66ba6fea15 100644 --- a/src/Umbraco.Web/umbraco.presentation/macro.cs +++ b/src/Umbraco.Web/umbraco.presentation/macro.cs @@ -571,11 +571,11 @@ namespace umbraco } //insert the cache string result - ApplicationContext.Current.ApplicationCache.InsertCacheItem( + ApplicationContext.Current.ApplicationCache.RuntimeCache.InsertCacheItem( CacheKeys.MacroHtmlCacheKey + Model.CacheIdentifier, - CacheItemPriority.NotRemovable, - new TimeSpan(0, 0, Model.CacheDuration), - () => outputCacheString); + priority: CacheItemPriority.NotRemovable, + timeout: new TimeSpan(0, 0, Model.CacheDuration), + getCacheItem: () => outputCacheString); dateAddedCacheKey = CacheKeys.MacroHtmlDateAddedCacheKey + Model.CacheIdentifier; @@ -590,11 +590,11 @@ namespace umbraco else { //insert the cache control result - ApplicationContext.Current.ApplicationCache.InsertCacheItem( + ApplicationContext.Current.ApplicationCache.RuntimeCache.InsertCacheItem( CacheKeys.MacroControlCacheKey + Model.CacheIdentifier, - CacheItemPriority.NotRemovable, - new TimeSpan(0, 0, Model.CacheDuration), - () => new MacroCacheContent(macroControl, macroControl.ID)); + priority: CacheItemPriority.NotRemovable, + timeout: new TimeSpan(0, 0, Model.CacheDuration), + getCacheItem: () => new MacroCacheContent(macroControl, macroControl.ID)); dateAddedCacheKey = CacheKeys.MacroControlDateAddedCacheKey + Model.CacheIdentifier; @@ -603,11 +603,11 @@ namespace umbraco } //insert the date inserted (so we can check file modification date) - ApplicationContext.Current.ApplicationCache.InsertCacheItem( + ApplicationContext.Current.ApplicationCache.RuntimeCache.InsertCacheItem( dateAddedCacheKey, - CacheItemPriority.NotRemovable, - new TimeSpan(0, 0, Model.CacheDuration), - () => DateTime.Now); + priority: CacheItemPriority.NotRemovable, + timeout: new TimeSpan(0, 0, Model.CacheDuration), + getCacheItem: () => DateTime.Now); } @@ -640,7 +640,7 @@ namespace umbraco if (CacheMacroAsString(Model)) { - macroHtml = ApplicationContext.Current.ApplicationCache.GetCacheItem( + macroHtml = ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem( CacheKeys.MacroHtmlCacheKey + Model.CacheIdentifier); // FlorisRobbemont: @@ -666,7 +666,7 @@ namespace umbraco } else { - var cacheContent = ApplicationContext.Current.ApplicationCache.GetCacheItem( + var cacheContent = ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem( CacheKeys.MacroControlCacheKey + Model.CacheIdentifier); if (cacheContent != null) @@ -730,7 +730,7 @@ namespace umbraco { if (MacroIsFileBased(model)) { - var cacheResult = ApplicationContext.Current.ApplicationCache.GetCacheItem(dateAddedKey); + var cacheResult = ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem(dateAddedKey); if (cacheResult != null) { @@ -864,7 +864,7 @@ namespace umbraco [Obsolete("This is no longer used in the codebase and will be removed in future versions")] public static void unloadXslt(string XsltFile) { - ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(CacheKeys.MacroXsltCacheKey + XsltFile); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.MacroXsltCacheKey + XsltFile); } #region LoadMacroXslt diff --git a/src/Umbraco.Web/umbraco.presentation/template.cs b/src/Umbraco.Web/umbraco.presentation/template.cs index 6c1e23a13e..dfdad324e2 100644 --- a/src/Umbraco.Web/umbraco.presentation/template.cs +++ b/src/Umbraco.Web/umbraco.presentation/template.cs @@ -494,7 +494,7 @@ namespace umbraco { var tId = templateID; - var t = ApplicationContext.Current.ApplicationCache.GetCacheItem( + var t = ApplicationContext.Current.ApplicationCache.RuntimeCache.GetCacheItem