From 848eaf5e2770cbe20c056cae8e2c0fc1911eb609 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 20 Jan 2017 12:08:55 +0100 Subject: [PATCH] U4-9322 - cont. implementing scoped repository caches --- .../Cache/DefaultRepositoryCachePolicy.cs | 6 + .../Cache/FullDataSetRepositoryCachePolicy.cs | 6 + .../Cache/IRepositoryCachePolicy.cs | 6 +- .../Cache/RepositoryCachePolicyBase.cs | 3 + ...licy.cs => ScopedRepositoryCachePolicy.cs} | 17 ++- .../Repositories/ContentRepository.cs | 2 +- .../Repositories/ContentTypeRepository.cs | 8 +- .../DataTypeDefinitionRepository.cs | 8 +- .../Repositories/DictionaryRepository.cs | 79 +++++------- .../Repositories/DomainRepository.cs | 13 +- .../Repositories/EntityContainerRepository.cs | 9 +- .../Repositories/LanguageRepository.cs | 26 ++-- .../Repositories/MediaRepository.cs | 2 +- .../Repositories/MediaTypeRepository.cs | 13 +- .../Repositories/MemberGroupRepository.cs | 2 +- .../Repositories/MemberRepository.cs | 2 +- .../Repositories/MemberTypeRepository.cs | 13 +- .../Repositories/PublicAccessRepository.cs | 13 +- .../Repositories/RelationTypeRepository.cs | 13 +- .../Repositories/RepositoryBase.cs | 69 ++++++---- .../Repositories/TemplateRepository.cs | 17 +-- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- .../Scoping/ScopedRepositoryTests.cs | 118 ++++++++++++++++-- 23 files changed, 250 insertions(+), 197 deletions(-) rename src/Umbraco.Core/Cache/{ScopedDefaultRepositoryCachePolicy.cs => ScopedRepositoryCachePolicy.cs} (76%) diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs index 5d08e36f6a..fc98473c4c 100644 --- a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Scoping; namespace Umbraco.Core.Cache { @@ -29,6 +30,11 @@ namespace Umbraco.Core.Cache _options = options; } + public override IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope) + { + return new ScopedRepositoryCachePolicy(this, runtimeCache, scope); + } + protected string GetEntityCacheKey(object id) { if (id == null) throw new ArgumentNullException("id"); diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index 95ad7e2bf0..41c0249877 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Umbraco.Core.Collections; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Scoping; namespace Umbraco.Core.Cache { @@ -30,6 +31,11 @@ namespace Umbraco.Core.Cache _expires = expires; } + public override IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope) + { + return new ScopedRepositoryCachePolicy(this, runtimeCache, scope); + } + protected static readonly TId[] EmptyIds = new TId[0]; // const protected string GetEntityTypeCacheKey() diff --git a/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs index 11a3cf6bb9..cd37ac1e60 100644 --- a/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Scoping; namespace Umbraco.Core.Cache { @@ -17,7 +18,10 @@ namespace Umbraco.Core.Cache // it is not *that* complicated but then RepositoryBase needs to have a TRepository generic // type parameter and it all becomes convoluted - keeping it simple for the time being. - /// + // fixme explain + IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope); + + /// /// Gets an entity from the cache, else from the repository. /// /// The identifier. diff --git a/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs b/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs index ce366e3d0b..0b5d2b15c5 100644 --- a/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs +++ b/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Scoping; namespace Umbraco.Core.Cache { @@ -18,6 +19,8 @@ namespace Umbraco.Core.Cache Cache = cache; } + public abstract IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope); + protected IRuntimeCacheProvider Cache { get; private set; } /// diff --git a/src/Umbraco.Core/Cache/ScopedDefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/ScopedRepositoryCachePolicy.cs similarity index 76% rename from src/Umbraco.Core/Cache/ScopedDefaultRepositoryCachePolicy.cs rename to src/Umbraco.Core/Cache/ScopedRepositoryCachePolicy.cs index 3c237f307c..ec3516cdef 100644 --- a/src/Umbraco.Core/Cache/ScopedDefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/ScopedRepositoryCachePolicy.cs @@ -5,27 +5,34 @@ using Umbraco.Core.Scoping; namespace Umbraco.Core.Cache { - internal class ScopedDefaultRepositoryCachePolicy : IRepositoryCachePolicy + internal class ScopedRepositoryCachePolicy : IRepositoryCachePolicy where TEntity : class, IAggregateRoot { - private readonly DefaultRepositoryCachePolicy _cachePolicy; + private readonly IRepositoryCachePolicy _cachePolicy; private readonly IRuntimeCacheProvider _globalIsolatedCache; private readonly IScope _scope; - public ScopedDefaultRepositoryCachePolicy(DefaultRepositoryCachePolicy cachePolicy, IRuntimeCacheProvider globalIsolatedCache, IScope scope) + public ScopedRepositoryCachePolicy(IRepositoryCachePolicy cachePolicy, IRuntimeCacheProvider globalIsolatedCache, IScope scope) { _cachePolicy = cachePolicy; _globalIsolatedCache = globalIsolatedCache; _scope = scope; } + public IRepositoryCachePolicy Scoped(IRuntimeCacheProvider runtimeCache, IScope scope) + { + throw new InvalidOperationException(); // obviously + } + // when the scope completes we need to clear the global isolated cache // for now, we are not doing it selectively at all - just kill everything + // later on we might want to be more clever private void RegisterDirty(TEntity entity = null) { - // "name" would be used to de-duplicate? + // use unique names to de-duplicate // fixme - casting! - ((Scope) _scope).Register("name", completed => _globalIsolatedCache.ClearAllCache()); + ((Scope) _scope).Register("dirty_" + typeof (TEntity).Name, + () => _globalIsolatedCache.ClearAllCache()); } public TEntity Get(TId id, Func performGet, Func> performGetAll) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 7f3e657dc2..59f64b370c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -853,7 +853,7 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder"; // if the cache contains the published version, use it if (withCache) { - var cached = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); + var cached = IsolatedCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); //only use this cached version if the dto returned is also the publish version, they must match if (cached != null && cached.Published && dto.Published) { diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index ca53b2e04e..57f96ea2f0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -18,7 +18,6 @@ namespace Umbraco.Core.Persistence.Repositories internal class ContentTypeRepository : ContentTypeBaseRepository, IContentTypeRepository { private readonly ITemplateRepository _templateRepository; - private IRepositoryCachePolicy _cachePolicy; public ContentTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, ITemplateRepository templateRepository) : base(work, cache, logger, sqlSyntax) @@ -26,12 +25,9 @@ namespace Umbraco.Core.Persistence.Repositories _templateRepository = templateRepository; } - protected override IRepositoryCachePolicy CachePolicy + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get - { - return _cachePolicy ?? (_cachePolicy = new FullDataSetRepositoryCachePolicy(RuntimeCache, GetEntityId, /*expires:*/ true)); - } + return new FullDataSetRepositoryCachePolicy(runtimeCache, GetEntityId, /*expires:*/ true); } protected override IContentType PerformGet(int id) diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index a4d71885f8..2d76b64521 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -237,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) - RuntimeCache.ClearCacheItem(GetPrefixedCacheKey(entity.Id)); + IsolatedCache.ClearCacheItem(GetPrefixedCacheKey(entity.Id)); entity.ResetDirtyProperties(); } @@ -276,7 +276,7 @@ AND umbracoNode.id <> @id", public PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId) { - var cached = RuntimeCache.GetCacheItemsByKeySearch(GetPrefixedCacheKey(dataTypeId)); + var cached = IsolatedCache.GetCacheItemsByKeySearch(GetPrefixedCacheKey(dataTypeId)); if (cached != null && cached.Any()) { //return from the cache, ensure it's a cloned result @@ -295,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 = RuntimeCache.GetCacheItemsByKeyExpression(GetCacheKeyRegex(preValueId)); + var cached = IsolatedCache.GetCacheItemsByKeyExpression(GetCacheKeyRegex(preValueId)); if (cached != null && cached.Any()) { //return from the cache @@ -463,7 +463,7 @@ AND umbracoNode.id <> @id", + string.Join(",", collection.FormatAsDictionary().Select(x => x.Value.Id).ToArray()); //store into cache - RuntimeCache.InsertCacheItem(key, () => collection, + IsolatedCache.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 da6d4d94a8..d2cc92ff0b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs @@ -20,28 +20,19 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class DictionaryRepository : PetaPocoRepositoryBase, IDictionaryRepository { - private IRepositoryCachePolicy _cachePolicy; - public DictionaryRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider syntax) : base(work, cache, logger, syntax) { } - protected override IRepositoryCachePolicy CachePolicy + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get + var options = new RepositoryCachePolicyOptions { - if (_cachePolicy != null) return _cachePolicy; + //allow zero to be cached + GetAllCacheAllowZeroCount = true + }; - var options = new RepositoryCachePolicyOptions - { - //allow zero to be cached - GetAllCacheAllowZeroCount = true - }; - - _cachePolicy = new SingleItemsOnlyRepositoryCachePolicy(RuntimeCache, options); - - return _cachePolicy; - } + return new SingleItemsOnlyRepositoryCachePolicy(runtimeCache, options); } #region Overrides of RepositoryBase @@ -185,8 +176,8 @@ namespace Umbraco.Core.Persistence.Repositories entity.ResetDirtyProperties(); //Clear the cache entries that exist by uniqueid/item key - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.ItemKey)); - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.Key)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.ItemKey)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.Key)); } protected override void PersistDeletedItem(IDictionaryItem entity) @@ -197,8 +188,8 @@ namespace Umbraco.Core.Persistence.Repositories Database.Delete("WHERE id = @Id", new { Id = entity.Key }); //Clear the cache entries that exist by uniqueid/item key - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.ItemKey)); - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.Key)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.ItemKey)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.Key)); } private void RecursiveDelete(Guid parentId) @@ -212,8 +203,8 @@ namespace Umbraco.Core.Persistence.Repositories Database.Delete("WHERE id = @Id", new { Id = dto.UniqueId }); //Clear the cache entries that exist by uniqueid/item key - RuntimeCache.ClearCacheItem(GetCacheIdKey(dto.Key)); - RuntimeCache.ClearCacheItem(GetCacheIdKey(dto.UniqueId)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(dto.Key)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(dto.UniqueId)); } } @@ -240,7 +231,8 @@ namespace Umbraco.Core.Persistence.Repositories public IDictionaryItem Get(Guid uniqueId) { - using (var uniqueIdRepo = new DictionaryByUniqueIdRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax)) + // note: normal to use GlobalCache here since we're passing it to a new repository + using (var uniqueIdRepo = new DictionaryByUniqueIdRepository(this, UnitOfWork, GlobalCache, Logger, SqlSyntax)) { return uniqueIdRepo.Get(uniqueId); } @@ -248,7 +240,8 @@ namespace Umbraco.Core.Persistence.Repositories public IDictionaryItem Get(string key) { - using (var keyRepo = new DictionaryByKeyRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax)) + // note: normal to use GlobalCache here since we're passing it to a new repository + using (var keyRepo = new DictionaryByKeyRepository(this, UnitOfWork, GlobalCache, Logger, SqlSyntax)) { return keyRepo.Get(key); } @@ -294,7 +287,6 @@ namespace Umbraco.Core.Persistence.Repositories private class DictionaryByUniqueIdRepository : SimpleGetRepository { - private IRepositoryCachePolicy _cachePolicy; private readonly DictionaryRepository _dictionaryRepository; public DictionaryByUniqueIdRepository(DictionaryRepository dictionaryRepository, IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) @@ -333,28 +325,20 @@ namespace Umbraco.Core.Persistence.Repositories return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("id") + " in (@ids)"; } - protected override IRepositoryCachePolicy CachePolicy + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get + var options = new RepositoryCachePolicyOptions { - if (_cachePolicy != null) return _cachePolicy; + //allow zero to be cached + GetAllCacheAllowZeroCount = true + }; - var options = new RepositoryCachePolicyOptions - { - //allow zero to be cached - GetAllCacheAllowZeroCount = true - }; - - _cachePolicy = new SingleItemsOnlyRepositoryCachePolicy(RuntimeCache, options); - - return _cachePolicy; - } + return new SingleItemsOnlyRepositoryCachePolicy(runtimeCache, options); } } private class DictionaryByKeyRepository : SimpleGetRepository { - private IRepositoryCachePolicy _cachePolicy; private readonly DictionaryRepository _dictionaryRepository; public DictionaryByKeyRepository(DictionaryRepository dictionaryRepository, IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) @@ -393,22 +377,15 @@ namespace Umbraco.Core.Persistence.Repositories return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("key") + " in (@ids)"; } - protected override IRepositoryCachePolicy CachePolicy + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get + var options = new RepositoryCachePolicyOptions { - if (_cachePolicy != null) return _cachePolicy; + //allow zero to be cached + GetAllCacheAllowZeroCount = true + }; - var options = new RepositoryCachePolicyOptions - { - //allow zero to be cached - GetAllCacheAllowZeroCount = true - }; - - _cachePolicy = new SingleItemsOnlyRepositoryCachePolicy(RuntimeCache, options); - - return _cachePolicy; - } + return new SingleItemsOnlyRepositoryCachePolicy(runtimeCache, options); } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs index 682c75eeb6..75c6ad0d87 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs @@ -18,23 +18,14 @@ namespace Umbraco.Core.Persistence.Repositories internal class DomainRepository : PetaPocoRepositoryBase, IDomainRepository { - private IRepositoryCachePolicy _cachePolicy; - public DomainRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } - protected override IRepositoryCachePolicy CachePolicy + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get - { - if (_cachePolicy != null) return _cachePolicy; - - _cachePolicy = new FullDataSetRepositoryCachePolicy(RuntimeCache, GetEntityId, /*expires:*/ false); - - return _cachePolicy; - } + return new FullDataSetRepositoryCachePolicy(runtimeCache, GetEntityId, /*expires:*/ false); } protected override IDomain PerformGet(int id) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs index cc13275798..41910e507a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs @@ -29,12 +29,11 @@ namespace Umbraco.Core.Persistence.Repositories throw new InvalidOperationException("No container type exists with ID: " + _containerObjectType); } - /// - /// Do not cache anything - /// - protected override IRuntimeCacheProvider RuntimeCache + // never cache + private static readonly IRuntimeCacheProvider NullCache = new NullCacheProvider(); + protected override IRuntimeCacheProvider GetIsolatedCache(IsolatedRuntimeCache provider) { - get { return new NullCacheProvider(); } + return NullCache; } protected override EntityContainer PerformGet(int id) diff --git a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs index 7911df8edb..a32a3c4647 100644 --- a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs @@ -19,23 +19,13 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class LanguageRepository : PetaPocoRepositoryBase, ILanguageRepository { - private IRepositoryCachePolicy _cachePolicy; - public LanguageRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) - { - } + { } - protected override IRepositoryCachePolicy CachePolicy + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get - { - if (_cachePolicy != null) return _cachePolicy; - - _cachePolicy = new FullDataSetRepositoryCachePolicy(RuntimeCache, GetEntityId, /*expires:*/ false); - - return _cachePolicy; - } + return new FullDataSetRepositoryCachePolicy(runtimeCache, GetEntityId, /*expires:*/ false); } #region Overrides of RepositoryBase @@ -134,8 +124,8 @@ namespace Umbraco.Core.Persistence.Repositories entity.ResetDirtyProperties(); //Clear the cache entries that exist by key/iso - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.IsoCode)); - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.CultureName)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.IsoCode)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.CultureName)); } protected override void PersistDeletedItem(ILanguage entity) @@ -143,8 +133,8 @@ namespace Umbraco.Core.Persistence.Repositories base.PersistDeletedItem(entity); //Clear the cache entries that exist by key/iso - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.IsoCode)); - RuntimeCache.ClearCacheItem(GetCacheIdKey(entity.CultureName)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.IsoCode)); + IsolatedCache.ClearCacheItem(GetCacheIdKey(entity.CultureName)); } #endregion @@ -167,7 +157,5 @@ namespace Umbraco.Core.Persistence.Repositories //use the underlying GetAll which will force cache all languages return GetAll().FirstOrDefault(x => x.IsoCode.InvariantEquals(isoCode)); } - - } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 08c2f7e0be..1d1a62f354 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -155,7 +155,7 @@ namespace Umbraco.Core.Persistence.Repositories // if the cache contains the item, use it if (withCache) { - var cached = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); + var cached = IsolatedCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); if (cached != null) { content[i] = cached; diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index 516c08330a..edf7051d4f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -22,23 +22,14 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class MediaTypeRepository : ContentTypeBaseRepository, IMediaTypeRepository { - private IRepositoryCachePolicy _cachePolicy; - public MediaTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } - protected override IRepositoryCachePolicy CachePolicy + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get - { - if (_cachePolicy != null) return _cachePolicy; - - _cachePolicy = new FullDataSetRepositoryCachePolicy(RuntimeCache, GetEntityId, /*expires:*/ true); - - return _cachePolicy; - } + return new FullDataSetRepositoryCachePolicy(runtimeCache, GetEntityId, /*expires:*/ true); } protected override IMediaType PerformGet(int id) diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs index 2c0e910428..b98b20ae3f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -131,7 +131,7 @@ namespace Umbraco.Core.Persistence.Repositories public IMemberGroup GetByName(string name) { - return RuntimeCache.GetCacheItem( + return IsolatedCache.GetCacheItem( string.Format("{0}.{1}", typeof (IMemberGroup).FullName, name), () => { diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index dcab898685..72c367326b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -674,7 +674,7 @@ namespace Umbraco.Core.Persistence.Repositories // if the cache contains the item, use it if (withCache) { - var cached = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); + var cached = IsolatedCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); if (cached != null) { content[i] = cached; diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index fd26afac89..49c9d47319 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -21,23 +21,14 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class MemberTypeRepository : ContentTypeBaseRepository, IMemberTypeRepository { - private IRepositoryCachePolicy _cachePolicy; - public MemberTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } - protected override IRepositoryCachePolicy CachePolicy + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get - { - if (_cachePolicy != null) return _cachePolicy; - - _cachePolicy = new FullDataSetRepositoryCachePolicy(RuntimeCache, GetEntityId, /*expires:*/ true); - - return _cachePolicy; - } + return new FullDataSetRepositoryCachePolicy(runtimeCache, GetEntityId, /*expires:*/ true); } protected override IMemberType PerformGet(int id) diff --git a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs index c34952f4be..a4f8705f81 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs @@ -15,22 +15,13 @@ namespace Umbraco.Core.Persistence.Repositories { internal class PublicAccessRepository : PetaPocoRepositoryBase, IPublicAccessRepository { - private IRepositoryCachePolicy _cachePolicy; - public PublicAccessRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } - protected override IRepositoryCachePolicy CachePolicy + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get - { - if (_cachePolicy != null) return _cachePolicy; - - _cachePolicy = new FullDataSetRepositoryCachePolicy(RuntimeCache, GetEntityId, /*expires:*/ false); - - return _cachePolicy; - } + return new FullDataSetRepositoryCachePolicy(runtimeCache, GetEntityId, /*expires:*/ false); } protected override PublicAccessEntry PerformGet(Guid id) diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs index 148ebba456..198afa818b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs @@ -19,22 +19,13 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class RelationTypeRepository : PetaPocoRepositoryBase, IRelationTypeRepository { - private IRepositoryCachePolicy _cachePolicy; - public RelationTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) { } - protected override IRepositoryCachePolicy CachePolicy + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get - { - if (_cachePolicy != null) return _cachePolicy; - - _cachePolicy = new FullDataSetRepositoryCachePolicy(RuntimeCache, GetEntityId, /*expires:*/ true); - - return _cachePolicy; - } + return new FullDataSetRepositoryCachePolicy(runtimeCache, GetEntityId, /*expires:*/ true); } #region Overrides of RepositoryBase diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index 09b4d7609d..eb07ed226b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -1,9 +1,7 @@ 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; @@ -16,7 +14,7 @@ namespace Umbraco.Core.Persistence.Repositories internal abstract class RepositoryBase : DisposableObject { private readonly IUnitOfWork _work; - private readonly CacheHelper _cache; + private readonly CacheHelper _globalCache; protected RepositoryBase(IUnitOfWork work, CacheHelper cache, ILogger logger) { @@ -25,7 +23,7 @@ namespace Umbraco.Core.Persistence.Repositories if (logger == null) throw new ArgumentNullException("logger"); Logger = logger; _work = work; - _cache = cache; + _globalCache = cache; } /// @@ -44,18 +42,18 @@ namespace Umbraco.Core.Persistence.Repositories get { return (Guid)_work.Key; } } - protected CacheHelper RepositoryCache + /// + /// Gets the global application cache. + /// + protected CacheHelper GlobalCache { - get { return _cache; } + get { return _globalCache; } } /// - /// The runtime cache used for this repo - by standard this is the runtime cache exposed by the CacheHelper but can be overridden + /// Gets the repository isolated cache. /// - protected virtual IRuntimeCacheProvider RuntimeCache - { - get { return _cache.RuntimeCache; } - } + protected abstract IRuntimeCacheProvider IsolatedCache { get; } public static string GetCacheIdKey(object id) { @@ -97,9 +95,34 @@ namespace Umbraco.Core.Persistence.Repositories /// /// The runtime cache used for this repo by default is the isolated cache for this type /// - protected override IRuntimeCacheProvider RuntimeCache + private IRuntimeCacheProvider _isolatedCache; + protected override IRuntimeCacheProvider IsolatedCache { - get { return RepositoryCache.IsolatedRuntimeCache.GetOrCreateCache(); } + get + { + if (_isolatedCache != null) return _isolatedCache; + + var scope = ((PetaPocoUnitOfWork) UnitOfWork).Scope; // fixme cast! + IsolatedRuntimeCache provider; + switch (scope.RepositoryCacheMode) + { + case RepositoryCacheMode.Default: + provider = GlobalCache.IsolatedRuntimeCache; + break; + case RepositoryCacheMode.Scoped: + provider = scope.IsolatedRuntimeCache; + break; + default: + throw new Exception("oops: cache mode."); + } + + return _isolatedCache = GetIsolatedCache(provider); + } + } + + protected virtual IRuntimeCacheProvider GetIsolatedCache(IsolatedRuntimeCache provider) + { + return provider.GetOrCreateCache(); } private static RepositoryCachePolicyOptions _defaultOptions; @@ -126,37 +149,39 @@ namespace Umbraco.Core.Persistence.Repositories // get // { // return _defaultCachePolicy ?? (_defaultCachePolicy - // = new DefaultRepositoryCachePolicy(RuntimeCache, DefaultOptions)); + // = new DefaultRepositoryCachePolicy(IsolatedCache, DefaultOptions)); // } //} private IRepositoryCachePolicy _cachePolicy; - protected virtual IRepositoryCachePolicy CachePolicy + protected IRepositoryCachePolicy CachePolicy { get { if (_cachePolicy != null) return _cachePolicy; - var scope = ((PetaPocoUnitOfWork) UnitOfWork).Scope; + _cachePolicy = CreateCachePolicy(IsolatedCache); + var scope = ((PetaPocoUnitOfWork) UnitOfWork).Scope; // fixme cast! switch (scope.RepositoryCacheMode) { case RepositoryCacheMode.Default: - //_cachePolicy = DefaultCachePolicy; - _cachePolicy = new DefaultRepositoryCachePolicy(RuntimeCache, DefaultOptions); break; case RepositoryCacheMode.Scoped: - var scopedCache = scope.IsolatedRuntimeCache.GetOrCreateCache(); - var scopedPolicy = new DefaultRepositoryCachePolicy(scopedCache, DefaultOptions); - _cachePolicy = new ScopedDefaultRepositoryCachePolicy(scopedPolicy, RuntimeCache, scope); + _cachePolicy = _cachePolicy.Scoped(GetIsolatedCache(GlobalCache.IsolatedRuntimeCache), scope); break; default: - throw new Exception(); + throw new Exception("oops: cache mode."); } return _cachePolicy; } } + protected virtual IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) + { + return new DefaultRepositoryCachePolicy(runtimeCache, DefaultOptions); + } + /// /// Adds or Updates an entity of type TEntity /// diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index a164e7e5ba..45a402d36f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -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; using Umbraco.Core.Logging; @@ -17,9 +15,7 @@ using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Services; using Umbraco.Core.Strings; -using Umbraco.Core.Sync; namespace Umbraco.Core.Persistence.Repositories { @@ -33,7 +29,6 @@ namespace Umbraco.Core.Persistence.Repositories private readonly ITemplatesSection _templateConfig; private readonly ViewHelper _viewHelper; private readonly MasterPageHelper _masterPageHelper; - private IRepositoryCachePolicy _cachePolicy; internal TemplateRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IFileSystem masterpageFileSystem, IFileSystem viewFileSystem, ITemplatesSection templateConfig) : base(work, cache, logger, sqlSyntax) @@ -45,17 +40,9 @@ namespace Umbraco.Core.Persistence.Repositories _masterPageHelper = new MasterPageHelper(_masterpagesFileSystem); } - - protected override IRepositoryCachePolicy CachePolicy + protected override IRepositoryCachePolicy CreateCachePolicy(IRuntimeCacheProvider runtimeCache) { - get - { - if (_cachePolicy != null) return _cachePolicy; - - _cachePolicy = new FullDataSetRepositoryCachePolicy(RuntimeCache, GetEntityId, /*expires:*/ false); - - return _cachePolicy; - } + return new FullDataSetRepositoryCachePolicy(runtimeCache, GetEntityId, /*expires:*/ false); } #region Overrides of RepositoryBase diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2c229c6d73..59479b5472 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -156,7 +156,7 @@ - + diff --git a/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs b/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs index 329af6833f..3a7d7ac465 100644 --- a/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs +++ b/src/Umbraco.Tests/Scoping/ScopedRepositoryTests.cs @@ -1,7 +1,10 @@ using System; +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Scoping; using Umbraco.Tests.TestHelpers; @@ -32,15 +35,15 @@ namespace Umbraco.Tests.Scoping [TestCase(true)] [TestCase(false)] - public void Test(bool complete) + public void DefaultRepositoryCachePolicy(bool complete) { var scopeProvider = DatabaseContext.ScopeProvider; - var userService = ApplicationContext.Services.UserService; + var service = ApplicationContext.Services.UserService; var globalCache = ApplicationContext.ApplicationCache.IsolatedRuntimeCache.GetOrCreateCache(typeof(IUser)); - var userType = userService.GetUserTypeByAlias("admin"); + var userType = service.GetUserTypeByAlias("admin"); var user = (IUser) new User("name", "email", "username", "rawPassword", userType); - userService.Save(user); + service.Save(user); // global cache contains the user entity var globalCached = (IUser) globalCache.GetCacheItem(GetCacheIdKey(user.Id), () => null); @@ -60,7 +63,7 @@ namespace Umbraco.Tests.Scoping Assert.AreNotSame(globalCache, scopedCache); user.Name = "changed"; - ApplicationContext.Services.UserService.Save(user); + service.Save(user); // scoped cache contains the "new" user entity var scopeCached = (IUser) scopedCache.GetCacheItem(GetCacheIdKey(user.Id), () => null); @@ -79,12 +82,20 @@ namespace Umbraco.Tests.Scoping } Assert.IsNull(scopeProvider.AmbientScope); - // global cache has been cleared - globalCached = (IUser) globalCache.GetCacheItem(GetCacheIdKey(user.Id), () => null); - Assert.IsNull(globalCached); + globalCached = (IUser)globalCache.GetCacheItem(GetCacheIdKey(user.Id), () => null); + if (complete) + { + // global cache has been cleared + Assert.IsNull(globalCached); + } + else + { + // global cache has *not* been cleared + Assert.IsNotNull(globalCached); + } // get again, updated if completed - user = userService.GetUserById(user.Id); + user = service.GetUserById(user.Id); Assert.AreEqual(complete ? "changed" : "name", user.Name); // global cache contains the entity again @@ -94,6 +105,95 @@ namespace Umbraco.Tests.Scoping Assert.AreEqual(complete ? "changed" : "name", globalCached.Name); } + [TestCase(true)] + [TestCase(false)] + public void FullDataSetRepositoryCachePolicy(bool complete) + { + var scopeProvider = DatabaseContext.ScopeProvider; + var service = ApplicationContext.Services.LocalizationService; + var globalCache = ApplicationContext.ApplicationCache.IsolatedRuntimeCache.GetOrCreateCache(typeof (ILanguage)); + + var lang = (ILanguage) new Language("fr-FR"); + service.Save(lang); + + // global cache has been flushed, reload + var globalFullCached = (IEnumerable) globalCache.GetCacheItem(GetCacheTypeKey(), () => null); + Assert.IsNull(globalFullCached); + var reload = service.GetLanguageById(lang.Id); + + // global cache contains the user entity + globalFullCached = (IEnumerable) globalCache.GetCacheItem(GetCacheTypeKey(), () => null); + Assert.IsNotNull(globalFullCached); + var globalCached = globalFullCached.First(x => x.Id == lang.Id); + Assert.IsNotNull(globalCached); + Assert.AreEqual(lang.Id, globalCached.Id); + Assert.AreEqual("fr-FR", globalCached.IsoCode); + + Assert.IsNull(scopeProvider.AmbientScope); + using (var scope = scopeProvider.CreateScope(repositoryCacheMode: RepositoryCacheMode.Scoped)) + { + Assert.IsInstanceOf(scope); + Assert.IsNotNull(scopeProvider.AmbientScope); + Assert.AreSame(scope, scopeProvider.AmbientScope); + + // scope has its own isolated cache + var scopedCache = scope.IsolatedRuntimeCache.GetOrCreateCache(typeof (ILanguage)); + Assert.AreNotSame(globalCache, scopedCache); + + lang.IsoCode = "de-DE"; + service.Save(lang); + + // scoped cache has been flushed, reload + var scopeFullCached = (IEnumerable) scopedCache.GetCacheItem(GetCacheTypeKey(), () => null); + Assert.IsNull(scopeFullCached); + reload = service.GetLanguageById(lang.Id); + + // scoped cache contains the "new" user entity + scopeFullCached = (IEnumerable) scopedCache.GetCacheItem(GetCacheTypeKey(), () => null); + Assert.IsNotNull(scopeFullCached); + var scopeCached = scopeFullCached.First(x => x.Id == lang.Id); + Assert.IsNotNull(scopeCached); + Assert.AreEqual(lang.Id, scopeCached.Id); + Assert.AreEqual("de-DE", scopeCached.IsoCode); + + // global cache is unchanged + globalFullCached = (IEnumerable) globalCache.GetCacheItem(GetCacheTypeKey(), () => null); + Assert.IsNotNull(globalFullCached); + globalCached = globalFullCached.First(x => x.Id == lang.Id); + Assert.IsNotNull(globalCached); + Assert.AreEqual(lang.Id, globalCached.Id); + Assert.AreEqual("fr-FR", globalCached.IsoCode); + + if (complete) + scope.Complete(); + } + Assert.IsNull(scopeProvider.AmbientScope); + + globalFullCached = (IEnumerable) globalCache.GetCacheItem(GetCacheTypeKey(), () => null); + if (complete) + { + // global cache has been cleared + Assert.IsNull(globalFullCached); + } + else + { + // global cache has *not* been cleared + Assert.IsNotNull(globalFullCached); + } + + // get again, updated if completed + lang = service.GetLanguageById(lang.Id); + Assert.AreEqual(complete ? "de-DE" : "fr-FR", lang.IsoCode); + + // global cache contains the entity again + globalFullCached = (IEnumerable) globalCache.GetCacheItem(GetCacheTypeKey(), () => null); + Assert.IsNotNull(globalFullCached); + globalCached = globalFullCached.First(x => x.Id == lang.Id); + Assert.IsNotNull(globalCached); + Assert.AreEqual(lang.Id, globalCached.Id); + Assert.AreEqual(complete ? "de-DE" : "fr-FR", lang.IsoCode); + } + public static string GetCacheIdKey(object id) { return string.Format("{0}{1}", GetCacheTypeKey(), id);