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); } ///