From b01f8799aea93360cd3f7e3d9bcb36eafa0602b9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 18 May 2015 16:03:25 +1000 Subject: [PATCH] oops! long time ago with the original 7.3 refactor of runtime caches, before we fixed deep cloning, the changes made just used normal http runtime cache, but with the deep cloning fixes these were not carried over. This fixes that by forcing the base RepositoryBase to use a DeepCloneRuntimeCacheProvider which is a wrapper for the normal runtime cache but ensures deep cloning in/out and resets dirty properties. --- .../DeepCloneRuntimeCacheProvider.cs | 126 ++++++++++++++++++ .../Repositories/RepositoryBase.cs | 6 +- 2 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs diff --git a/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs b/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs new file mode 100644 index 0000000000..fa998f06a2 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs @@ -0,0 +1,126 @@ +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 +{ + /// + /// Ensures that all inserts and returns are a deep cloned copy of the item when + /// the item is IDeepCloneable + /// + internal class DeepCloneRuntimeCacheProvider : IRuntimeCacheProvider + { + private readonly IRuntimeCacheProvider _innerProvider; + + public DeepCloneRuntimeCacheProvider(IRuntimeCacheProvider innerProvider) + { + _innerProvider = innerProvider; + } + + #region Clear - doesn't require any changes + public void ClearAllCache() + { + _innerProvider.ClearAllCache(); + } + + public void ClearCacheItem(string key) + { + _innerProvider.ClearCacheItem(key); + } + + public void ClearCacheObjectTypes(string typeName) + { + _innerProvider.ClearCacheObjectTypes(typeName); + } + + public void ClearCacheObjectTypes() + { + _innerProvider.ClearCacheObjectTypes(); + } + + public void ClearCacheObjectTypes(Func predicate) + { + _innerProvider.ClearCacheObjectTypes(predicate); + } + + public void ClearCacheByKeySearch(string keyStartsWith) + { + _innerProvider.ClearCacheByKeySearch(keyStartsWith); + } + + public void ClearCacheByKeyExpression(string regexString) + { + _innerProvider.ClearCacheByKeyExpression(regexString); + } + #endregion + + public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + { + return _innerProvider.GetCacheItemsByKeySearch(keyStartsWith) + .Select(CheckCloneableAndTracksChanges); + } + + public IEnumerable GetCacheItemsByKeyExpression(string regexString) + { + return _innerProvider.GetCacheItemsByKeyExpression(regexString) + .Select(CheckCloneableAndTracksChanges); + } + + public object GetCacheItem(string cacheKey) + { + var item = _innerProvider.GetCacheItem(cacheKey); + return CheckCloneableAndTracksChanges(item); + } + + public object GetCacheItem(string cacheKey, Func getCacheItem) + { + return _innerProvider.GetCacheItem(cacheKey, () => + { + //Resolve the item but returned the cloned/reset item + var item = getCacheItem(); + return CheckCloneableAndTracksChanges(item); + }); + } + + public object GetCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + return _innerProvider.GetCacheItem(cacheKey, () => + { + //Resolve the item but returned the cloned/reset item + var item = getCacheItem(); + return CheckCloneableAndTracksChanges(item); + }, timeout, isSliding, priority, removedCallback, dependentFiles); + } + + public void InsertCacheItem(string cacheKey, Func getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null) + { + _innerProvider.InsertCacheItem(cacheKey, () => + { + //Resolve the item but returned the cloned/reset item + var item = getCacheItem(); + return CheckCloneableAndTracksChanges(item); + }, timeout, isSliding, priority, removedCallback, dependentFiles); + } + + private static object CheckCloneableAndTracksChanges(object input) + { + var entity = input as IDeepCloneable; + if (entity == null) return input; + + var cloned = entity.DeepClone(); + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + var tracksChanges = cloned as TracksChangesEntityBase; + if (tracksChanges != null) + { + tracksChanges.ResetDirtyProperties(false); + return tracksChanges; + } + return cloned; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index 0d3c7db7a7..4c5087674f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -22,7 +22,11 @@ namespace Umbraco.Core.Persistence.Repositories if (logger == null) throw new ArgumentNullException("logger"); Logger = logger; _work = work; - _cache = cache; + + //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); } ///