diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs index 45e79a1b67..1f51fc3ccc 100644 --- a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs @@ -15,35 +15,31 @@ namespace Umbraco.Core.Cache /// This cache policy uses sliding expiration and caches instances for 5 minutes. However if allow zero count is true, then we use the /// default policy with no expiry. /// - internal class DefaultRepositoryCachePolicy : DisposableObject, IRepositoryCachePolicy + internal class DefaultRepositoryCachePolicy : RepositoryCachePolicyBase where TEntity : class, IAggregateRoot { private readonly RepositoryCachePolicyOptions _options; - protected IRuntimeCacheProvider Cache { get; private set; } - private Action _action; public DefaultRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options) - { - if (cache == null) throw new ArgumentNullException("cache"); + : base(cache) + { if (options == null) throw new ArgumentNullException("options"); - - _options = options; - Cache = cache; + _options = options; } - public string GetCacheIdKey(object id) + protected string GetCacheIdKey(object id) { if (id == null) throw new ArgumentNullException("id"); return string.Format("{0}{1}", GetCacheTypeKey(), id); } - public string GetCacheTypeKey() + protected string GetCacheTypeKey() { return string.Format("uRepo_{0}_", typeof(TEntity).Name); } - public void CreateOrUpdate(TEntity entity, Action persistMethod) + public override void CreateOrUpdate(TEntity entity, Action persistMethod) { if (entity == null) throw new ArgumentNullException("entity"); if (persistMethod == null) throw new ArgumentNullException("persistMethod"); @@ -85,24 +81,29 @@ namespace Umbraco.Core.Cache } } - public void Remove(TEntity entity, Action persistMethod) + public override void Remove(TEntity entity, Action persistMethod) { if (entity == null) throw new ArgumentNullException("entity"); if (persistMethod == null) throw new ArgumentNullException("persistMethod"); - persistMethod(entity); - - //set the disposal action - var cacheKey = GetCacheIdKey(entity.Id); - SetCacheAction(() => + try { - Cache.ClearCacheItem(cacheKey); - //If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared - Cache.ClearCacheItem(GetCacheTypeKey()); - }); + persistMethod(entity); + } + finally + { + //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) + public override TEntity Get(TId id, Func getFromRepo) { if (getFromRepo == null) throw new ArgumentNullException("getFromRepo"); @@ -119,13 +120,13 @@ namespace Umbraco.Core.Cache return entity; } - public TEntity Get(TId id) + public override TEntity Get(TId id) { var cacheKey = GetCacheIdKey(id); return Cache.GetCacheItem(cacheKey); } - public bool Exists(TId id, Func getFromRepo) + public override bool Exists(TId id, Func getFromRepo) { if (getFromRepo == null) throw new ArgumentNullException("getFromRepo"); @@ -134,7 +135,7 @@ namespace Umbraco.Core.Cache return fromCache != null || getFromRepo(id); } - public virtual TEntity[] GetAll(TId[] ids, Func> getFromRepo) + public override TEntity[] GetAll(TId[] ids, Func> getFromRepo) { if (getFromRepo == null) throw new ArgumentNullException("getFromRepo"); @@ -188,7 +189,7 @@ namespace Umbraco.Core.Cache /// Looks up the zero count cache, must return null if it doesn't exist /// /// - protected virtual bool HasZeroCountCache() + protected bool HasZeroCountCache() { var zeroCount = Cache.GetCacheItem(GetCacheTypeKey()); return (zeroCount != null && zeroCount.Any() == false); @@ -198,24 +199,13 @@ namespace Umbraco.Core.Cache /// Performs the lookup for all entities of this type from the cache /// /// - protected virtual TEntity[] GetAllFromCache() + protected 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 @@ -273,14 +263,6 @@ namespace Umbraco.Core.Cache } }); } - - /// - /// 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/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index c098af8992..eeb651dc09 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -11,33 +11,114 @@ namespace Umbraco.Core.Cache /// /// /// - /// - /// This caching policy has no sliding expiration but uses the default ObjectCache.InfiniteAbsoluteExpiration as it's timeout, so it - /// should not leave the cache unless the cache memory is exceeded and it gets thrown out. - /// - internal class FullDataSetRepositoryCachePolicy : DefaultRepositoryCachePolicy + internal class FullDataSetRepositoryCachePolicy : RepositoryCachePolicyBase where TEntity : class, IAggregateRoot { private readonly Func _getEntityId; + private readonly Func> _getAllFromRepo; + private readonly bool _expires; - public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, Func getEntityId) : base(cache, - new RepositoryCachePolicyOptions - { - //Definitely allow zero'd cache entires since this is a full set, in many cases there will be none, - // and we must cache this! - GetAllCacheAllowZeroCount = true - }) + public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, Func getEntityId, Func> getAllFromRepo, bool expires) + : base(cache) { _getEntityId = getEntityId; + _getAllFromRepo = getAllFromRepo; + _expires = expires; } private bool? _hasZeroCountCache; + protected string GetCacheTypeKey() + { + return string.Format("uRepo_{0}_", typeof(TEntity).Name); + } + + public override void CreateOrUpdate(TEntity entity, Action persistMethod) + { + if (entity == null) throw new ArgumentNullException("entity"); + if (persistMethod == null) throw new ArgumentNullException("persistMethod"); + + try + { + persistMethod(entity); + + //set the disposal action + SetCacheAction(() => + { + //Clear all + Cache.ClearCacheItem(GetCacheTypeKey()); + }); + } + catch + { + //set the disposal action + SetCacheAction(() => + { + //Clear all + Cache.ClearCacheItem(GetCacheTypeKey()); + }); + throw; + } + } + + public override void Remove(TEntity entity, Action persistMethod) + { + if (entity == null) throw new ArgumentNullException("entity"); + if (persistMethod == null) throw new ArgumentNullException("persistMethod"); + + try + { + persistMethod(entity); + } + finally + { + //set the disposal action + SetCacheAction(() => + { + //Clear all + Cache.ClearCacheItem(GetCacheTypeKey()); + }); + } + } + + public override TEntity Get(TId id, Func getFromRepo) + { + //Force get all with cache + var found = GetAll(new TId[] { }, ids => _getAllFromRepo()); + + //we don't have anything in cache (this should never happen), just return from the repo + return found == null + ? getFromRepo(id) + : found.FirstOrDefault(x => _getEntityId(x).Equals(id)); + } + + public override TEntity Get(TId id) + { + //Force get all with cache + var found = GetAll(new TId[] { }, ids => _getAllFromRepo()); + + //we don't have anything in cache (this should never happen), just return null + return found == null + ? null + : found.FirstOrDefault(x => _getEntityId(x).Equals(id)); + } + + public override bool Exists(TId id, Func getFromRepo) + { + //Force get all with cache + var found = GetAll(new TId[] {}, ids => _getAllFromRepo()); + + //we don't have anything in cache (this should never happen), just return from the repo + return found == null + ? getFromRepo(id) + : found.Any(x => _getEntityId(x).Equals(id)); + } + public override TEntity[] GetAll(TId[] ids, Func> getFromRepo) { - //process the base logic without any Ids - we want to cache them all! - var result = base.GetAll(new TId[] { }, getFromRepo); + //process getting all including setting the cache callback + var result = PerformGetAll(getFromRepo); //now that the base result has been calculated, they will all be cached. // Now we can just filter by ids if they have been supplied @@ -47,31 +128,64 @@ namespace Umbraco.Core.Cache : result; } + protected TEntity[] PerformGetAll(Func> getFromRepo) + { + var allEntities = GetAllFromCache(); + if (allEntities.Any()) + { + return allEntities; + } + + //check the zero count cache + if (HasZeroCountCache()) + { + //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(new TId[] {}) + //ensure we don't include any null refs in the returned collection! + .WhereNotNull() + .ToArray(); + + //set the disposal action + SetCacheAction(entityCollection); + + return entityCollection; + } + /// /// For this type of caching policy, we don't cache individual items /// /// /// - protected override void SetCacheAction(string cacheKey, TEntity entity) + protected void SetCacheAction(string cacheKey, TEntity entity) { - //do nothing + //No-op } /// /// Sets the action to execute on disposal for an entity collection /// - /// /// - protected override void SetCacheAction(TId[] ids, TEntity[] entityCollection) + protected void SetCacheAction(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)); + + if (_expires) + { + Cache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList(entityCollection), + timeout: TimeSpan.FromMinutes(5), + isSliding: true); + } + else + { + Cache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList(entityCollection)); + } }); } @@ -79,7 +193,7 @@ namespace Umbraco.Core.Cache /// Looks up the zero count cache, must return null if it doesn't exist /// /// - protected override bool HasZeroCountCache() + protected bool HasZeroCountCache() { if (_hasZeroCountCache.HasValue) return _hasZeroCountCache.Value; @@ -92,7 +206,7 @@ namespace Umbraco.Core.Cache /// This policy will cache the full data set as a single collection /// /// - protected override TEntity[] GetAllFromCache() + protected TEntity[] GetAllFromCache() { var found = Cache.GetCacheItem>(GetCacheTypeKey()); @@ -101,5 +215,6 @@ namespace Umbraco.Core.Cache 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 index 6a79c2b8c2..e4addcf355 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Cache @@ -13,16 +14,20 @@ namespace Umbraco.Core.Cache { private readonly IRuntimeCacheProvider _runtimeCache; private readonly Func _getEntityId; + private readonly Func> _getAllFromRepo; + private readonly bool _expires; - public FullDataSetRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, Func getEntityId) + public FullDataSetRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, Func getEntityId, Func> getAllFromRepo, bool expires) { _runtimeCache = runtimeCache; _getEntityId = getEntityId; + _getAllFromRepo = getAllFromRepo; + _expires = expires; } public virtual IRepositoryCachePolicy CreatePolicy() { - return new FullDataSetRepositoryCachePolicy(_runtimeCache, _getEntityId); + return new FullDataSetRepositoryCachePolicy(_runtimeCache, _getEntityId, _getAllFromRepo, _expires); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs index 97844933b7..215487c3be 100644 --- a/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs @@ -10,9 +10,7 @@ namespace Umbraco.Core.Cache 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); diff --git a/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs b/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs new file mode 100644 index 0000000000..b939cd14e6 --- /dev/null +++ b/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Cache +{ + internal abstract class RepositoryCachePolicyBase : DisposableObject, IRepositoryCachePolicy + where TEntity : class, IAggregateRoot + { + private Action _action; + + protected RepositoryCachePolicyBase(IRuntimeCacheProvider cache) + { + if (cache == null) throw new ArgumentNullException("cache"); + + Cache = cache; + } + + protected IRuntimeCacheProvider Cache { get; private set; } + + /// + /// The disposal performs the caching + /// + protected override void DisposeResources() + { + if (_action != null) + { + _action(); + } + } + + /// + /// Sets the action to execute on disposal + /// + /// + protected void SetCacheAction(Action action) + { + _action = action; + } + + public abstract TEntity Get(TId id, Func getFromRepo); + public abstract TEntity Get(TId id); + public abstract bool Exists(TId id, Func getFromRepo); + public abstract void CreateOrUpdate(TEntity entity, Action persistMethod); + public abstract void Remove(TEntity entity, Action persistMethod); + public abstract TEntity[] GetAll(TId[] ids, Func> getFromRepo); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs index 9566cd6e7f..28ac4ee2d1 100644 --- a/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs @@ -18,7 +18,7 @@ namespace Umbraco.Core.Cache protected override void SetCacheAction(TId[] ids, TEntity[] entityCollection) { - //do nothing + //no-op } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index 1a09a2206d..1441db0907 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -34,7 +34,10 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( + RuntimeCache, GetEntityId, () => PerformGetAll(), + //allow this cache to expire + expires:true)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs index 563243f12c..7b6cc162a8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs @@ -29,7 +29,8 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( + RuntimeCache, GetEntityId, () => PerformGetAll(), false)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs index 3884eac888..f9a8e59cfa 100644 --- a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs @@ -30,7 +30,8 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( + RuntimeCache, GetEntityId, () => PerformGetAll(), false)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index 4ee5e1a327..2651cf98d4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -31,7 +31,10 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( + RuntimeCache, GetEntityId, () => PerformGetAll(), + //allow this cache to expire + expires: true)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 4bfdbf3c8a..ddbf08e71b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -33,7 +33,10 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( + RuntimeCache, GetEntityId, () => PerformGetAll(), + //allow this cache to expire + expires: true)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs index 1d8e56190b..22fad9d99b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs @@ -26,7 +26,8 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( + RuntimeCache, GetEntityId, () => PerformGetAll(), false)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index a523a06293..fa780e1bd0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -51,7 +51,8 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( + RuntimeCache, GetEntityId, () => PerformGetAll(), false)); } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2ef0501f8e..e0991e9ba7 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -175,6 +175,7 @@ + diff --git a/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs b/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs index 32381b593b..9b0aaac78b 100644 --- a/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs +++ b/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs @@ -120,5 +120,37 @@ namespace Umbraco.Tests.Cache Assert.IsTrue(cacheCleared); } } + + [Test] + public void If_Removes_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.Remove(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 index 9187fe5b27..d3df319ac7 100644 --- a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs +++ b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs @@ -14,9 +14,57 @@ namespace Umbraco.Tests.Cache [TestFixture] public class FullDataSetCachePolicyTests { + [Test] + public void Caches_Single() + { + var getAll = new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }; + + 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 FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id, () => getAll, false); + 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 getAll = new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }; + + var cache = new Mock(); + cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem(1, "blah", AuditType.Copy, 123)); + + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id, () => getAll, false); + using (defaultPolicy) + { + var found = defaultPolicy.Get(1, o => (AuditItem)null); + Assert.IsNotNull(found); + } + } + [Test] public void Get_All_Caches_Empty_List() { + var getAll = new AuditItem[] {}; + var cached = new List(); IList list = null; @@ -36,20 +84,20 @@ namespace Umbraco.Tests.Cache return cached.Any() ? new DeepCloneableList() : null; }); - var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id); + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id, () => getAll, false); using (defaultPolicy) { - var found = defaultPolicy.GetAll(new object[] {}, o => new AuditItem[] {}); + var found = defaultPolicy.GetAll(new object[] {}, o => getAll); } Assert.AreEqual(1, cached.Count); Assert.IsNotNull(list); //Do it again, ensure that its coming from the cache! - defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id); + defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id, () => getAll, false); using (defaultPolicy) { - var found = defaultPolicy.GetAll(new object[] { }, o => new AuditItem[] { }); + var found = defaultPolicy.GetAll(new object[] { }, o => getAll); } Assert.AreEqual(1, cached.Count); @@ -59,6 +107,12 @@ namespace Umbraco.Tests.Cache [Test] public void Get_All_Caches_As_Single_List() { + var getAll = new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }; + var cached = new List(); IList list = null; @@ -73,14 +127,10 @@ namespace Umbraco.Tests.Cache }); cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem[] { }); - var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id); + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id, () => getAll, false); using (defaultPolicy) { - var found = defaultPolicy.GetAll(new object[] { }, o => new[] - { - new AuditItem(1, "blah", AuditType.Copy, 123), - new AuditItem(2, "blah2", AuditType.Copy, 123) - }); + var found = defaultPolicy.GetAll(new object[] { }, o => getAll); } Assert.AreEqual(1, cached.Count); @@ -89,7 +139,9 @@ namespace Umbraco.Tests.Cache [Test] public void Get_All_Without_Ids_From_Cache() - { + { + var getAll = new[] { (AuditItem)null }; + var cache = new Mock(); cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(() => new DeepCloneableList @@ -98,12 +150,88 @@ namespace Umbraco.Tests.Cache new AuditItem(2, "blah2", AuditType.Copy, 123) }); - var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id); + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id, () => getAll, false); using (defaultPolicy) { - var found = defaultPolicy.GetAll(new object[] { }, o => new[] { (AuditItem)null }); + var found = defaultPolicy.GetAll(new object[] { }, o => getAll); Assert.AreEqual(2, found.Length); } } + + [Test] + public void If_CreateOrUpdate_Throws_Cache_Is_Removed() + { + var getAll = new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }; + + var cacheCleared = false; + var cache = new Mock(); + cache.Setup(x => x.ClearCacheItem(It.IsAny())) + .Callback(() => + { + cacheCleared = true; + }); + + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id, () => getAll, false); + 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); + } + } + + [Test] + public void If_Removes_Throws_Cache_Is_Removed() + { + var getAll = new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }; + + var cacheCleared = false; + var cache = new Mock(); + cache.Setup(x => x.ClearCacheItem(It.IsAny())) + .Callback(() => + { + cacheCleared = true; + }); + + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id, () => getAll, false); + try + { + using (defaultPolicy) + { + defaultPolicy.Remove(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