From f199b0a651dd430adf4916c16be255bdd8b54d4b Mon Sep 17 00:00:00 2001 From: Lars-Erik Aabech Date: Tue, 18 Oct 2016 22:35:46 +0200 Subject: [PATCH 1/4] Added default cachehelper to the relation repositories --- src/Umbraco.Core/Persistence/RepositoryFactory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 85eb00ea87..a960d46eeb 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -197,7 +197,7 @@ namespace Umbraco.Core.Persistence { return new RelationRepository( uow, - _noCache, //never cache + _cacheHelper, _logger, _sqlSyntax, CreateRelationTypeRepository(uow)); } @@ -206,7 +206,7 @@ namespace Umbraco.Core.Persistence { return new RelationTypeRepository( uow, - _noCache, //never cache + _cacheHelper, _logger, _sqlSyntax); } From bf50c25854ea1cc66c92c629b707d0d4d572960e Mon Sep 17 00:00:00 2001 From: Lars-Erik Aabech Date: Thu, 3 Nov 2016 10:26:31 +0100 Subject: [PATCH 2/4] Only caching relation types First hackathon task done. :) --- src/Umbraco.Core/Persistence/RepositoryFactory.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index a960d46eeb..ff0f9e9028 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -197,7 +197,7 @@ namespace Umbraco.Core.Persistence { return new RelationRepository( uow, - _cacheHelper, + _noCache, _logger, _sqlSyntax, CreateRelationTypeRepository(uow)); } @@ -343,4 +343,4 @@ namespace Umbraco.Core.Persistence _sqlSyntax); } } -} \ No newline at end of file +} From b441c73604ec6e2266cbe6e72748e127bc297f74 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 9 Nov 2016 13:05:23 +0100 Subject: [PATCH 3/4] U4-9077 - relation type cache refresher + policy --- .../Repositories/RelationTypeRepository.cs | 69 ++++++++++--------- .../Cache/CacheRefresherEventHandler.cs | 27 +++++++- src/Umbraco.Web/Cache/DistributedCache.cs | 2 + .../Cache/DistributedCacheExtensions.cs | 14 ++++ .../Cache/RelationTypeCacheRefresher.cs | 52 ++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 6 files changed, 128 insertions(+), 37 deletions(-) create mode 100644 src/Umbraco.Web/Cache/RelationTypeCacheRefresher.cs diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs index df0ae0b224..c0a110feca 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.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; @@ -18,50 +19,42 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class RelationTypeRepository : PetaPocoRepositoryBase, IRelationTypeRepository { - public RelationTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) + { } + + // assuming we don't have tons of relation types, use a FullDataSet policy, ie + // cache the entire GetAll result once in a single collection - which can expire + private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory { + get + { + return _cachePolicyFactory + ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( + RuntimeCache, GetEntityId, () => PerformGetAll(), expires: true)); + } } #region Overrides of RepositoryBase protected override IRelationType PerformGet(int id) { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - - var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); - if (dto == null) - return null; - - var factory = new RelationTypeFactory(); - var entity = factory.BuildEntity(dto); - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((TracksChangesEntityBase)entity).ResetDirtyProperties(false); - - return entity; + // use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Id == id); } protected override IEnumerable PerformGetAll(params int[] ids) { + var sql = GetBaseQuery(false); + + // should not happen due to the cache policy if (ids.Any()) - { - foreach (var id in ids) - { - yield return Get(id); - } - } - else - { - var dtos = Database.Fetch("WHERE id > 0"); - foreach (var dto in dtos) - { - yield return Get(dto.Id); - } - } + throw new NotImplementedException(); + + var dtos = Database.Fetch(sql); + var factory = new RelationTypeFactory(); + return dtos.Select(x => DtoToEntity(x, factory)); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -71,11 +64,19 @@ namespace Umbraco.Core.Persistence.Repositories var sql = translator.Translate(); var dtos = Database.Fetch(sql); + var factory = new RelationTypeFactory(); + return dtos.Select(x => DtoToEntity(x, factory)); + } - foreach (var dto in dtos) - { - yield return Get(dto.Id); - } + private static IRelationType DtoToEntity(RelationTypeDto dto, RelationTypeFactory factory) + { + var entity = factory.BuildEntity(dto); + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((TracksChangesEntityBase) entity).ResetDirtyProperties(false); + + return entity; } #endregion diff --git a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs index 15e01fd430..791318d8ab 100644 --- a/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs +++ b/src/Umbraco.Web/Cache/CacheRefresherEventHandler.cs @@ -125,9 +125,12 @@ namespace Umbraco.Web.Cache //public access events PublicAccessService.Saved += PublicAccessService_Saved; - PublicAccessService.Deleted += PublicAccessService_Deleted; ; + PublicAccessService.Deleted += PublicAccessService_Deleted; + + RelationService.SavedRelationType += RelationType_Saved; + RelationService.DeletedRelationType += RelationType_Deleted; } - + #region Publishing void PublishingStrategy_UnPublished(IPublishingStrategy sender, PublishEventArgs e) @@ -661,7 +664,25 @@ namespace Umbraco.Web.Cache { DistributedCache.Instance.RemoveMemberGroupCache(m.Id); } - } + } + #endregion + + #region Relation type event handlers + + private static void RelationType_Saved(IRelationService sender, SaveEventArgs args) + { + var dc = DistributedCache.Instance; + foreach (var e in args.SavedEntities) + dc.RefreshRelationTypeCache(e.Id); + } + + private static void RelationType_Deleted(IRelationService sender, DeleteEventArgs args) + { + var dc = DistributedCache.Instance; + foreach (var e in args.DeletedEntities) + dc.RemoveRelationTypeCache(e.Id); + } + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/DistributedCache.cs b/src/Umbraco.Web/Cache/DistributedCache.cs index 6848ce2496..01eaf4cdd3 100644 --- a/src/Umbraco.Web/Cache/DistributedCache.cs +++ b/src/Umbraco.Web/Cache/DistributedCache.cs @@ -38,6 +38,7 @@ 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"; + public const string RelationTypeCacheRefresherId = "D8375ABA-4FB3-4F86-B505-92FBA1B6F7C9"; [Obsolete("This is no longer used and will be removed in future versions")] [EditorBrowsable(EditorBrowsableState.Never)] @@ -67,6 +68,7 @@ namespace Umbraco.Web.Cache public static readonly Guid DataTypeCacheRefresherGuid = new Guid(DataTypeCacheRefresherId); public static readonly Guid DictionaryCacheRefresherGuid = new Guid(DictionaryCacheRefresherId); public static readonly Guid PublicAccessCacheRefresherGuid = new Guid(PublicAccessCacheRefresherId); + public static readonly Guid RelationTypeCacheRefresherGuid = new Guid(RelationTypeCacheRefresherId); #endregion diff --git a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs index 750872d8af..50fd53ce09 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs @@ -446,5 +446,19 @@ namespace Umbraco.Web.Cache } #endregion + + #region Relation type cache + + public static void RefreshRelationTypeCache(this DistributedCache dc, int id) + { + dc.Refresh(DistributedCache.RelationTypeCacheRefresherGuid, id); + } + + public static void RemoveRelationTypeCache(this DistributedCache dc, int id) + { + dc.Remove(DistributedCache.RelationTypeCacheRefresherGuid, id); + } + + #endregion } } \ No newline at end of file diff --git a/src/Umbraco.Web/Cache/RelationTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/RelationTypeCacheRefresher.cs new file mode 100644 index 0000000000..cef308c52b --- /dev/null +++ b/src/Umbraco.Web/Cache/RelationTypeCacheRefresher.cs @@ -0,0 +1,52 @@ +using System; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; + +namespace Umbraco.Web.Cache +{ + public sealed class RelationTypeCacheRefresher : CacheRefresherBase + { + protected override RelationTypeCacheRefresher Instance + { + get { return this; } + } + + public override Guid UniqueIdentifier + { + get { return DistributedCache.RelationTypeCacheRefresherGuid; } + } + + public override string Name + { + get { return "Relation Type Cache Refresher"; } + } + + public override void RefreshAll() + { + ClearAllIsolatedCacheByEntityType(); + base.RefreshAll(); + } + + public override void Refresh(int id) + { + var cache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + if (cache) cache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); + base.Refresh(id); + } + + public override void Refresh(Guid id) + { + throw new NotSupportedException(); + //base.Refresh(id); + } + + public override void Remove(int id) + { + var cache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + if (cache) cache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); + base.Remove(id); + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 7fb022e848..a620a6716c 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -310,6 +310,7 @@ + From 5c3d605d34b0e44e71a60f43c95abfd39adb5143 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 9 Nov 2016 15:33:28 +0100 Subject: [PATCH 4/4] U4-9077 - in addition, deal with N+1 for relations --- .../Repositories/RelationRepository.cs | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs index be0808bb19..4511ebe35d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs @@ -42,34 +42,17 @@ namespace Umbraco.Core.Persistence.Repositories throw new Exception(string.Format("RelationType with Id: {0} doesn't exist", dto.RelationType)); var factory = new RelationFactory(relationType); - var entity = factory.BuildEntity(dto); - - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((TracksChangesEntityBase)entity).ResetDirtyProperties(false); - - return entity; + return DtoToEntity(dto, factory); } - //TODO: Fix N+1 ! - protected override IEnumerable PerformGetAll(params int[] ids) { - if (ids.Any()) - { - foreach (var id in ids) - { - yield return Get(id); - } - } - else - { - var dtos = Database.Fetch("WHERE id > 0"); - foreach (var dto in dtos) - { - yield return Get(dto.Id); - } - } + var sql = GetBaseQuery(false); + if (ids.Length > 0) + sql.WhereIn(x => x.Id, ids); + sql.OrderBy(x => x.RelationType); + var dtos = Database.Fetch(sql); + return DtosToEntities(dtos); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -77,13 +60,36 @@ namespace Umbraco.Core.Persistence.Repositories var sqlClause = GetBaseQuery(false); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); - + sql.OrderBy(x => x.RelationType); var dtos = Database.Fetch(sql); + return DtosToEntities(dtos); + } - foreach (var dto in dtos) + private IEnumerable DtosToEntities(IEnumerable dtos) + { + // in most cases, the relation type will be the same for all of them, + // plus we've ordered the relations by type, so try to allocate as few + // factories as possible - bearing in mind that relation types are cached + RelationFactory factory = null; + var relationTypeId = -1; + + return dtos.Select(x => { - yield return Get(dto.Id); - } + if (relationTypeId != x.RelationType) + factory = new RelationFactory(_relationTypeRepository.Get(relationTypeId = x.RelationType)); + return DtoToEntity(x, factory); + }); + } + + private static IRelation DtoToEntity(RelationDto dto, RelationFactory factory) + { + var entity = factory.BuildEntity(dto); + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((TracksChangesEntityBase)entity).ResetDirtyProperties(false); + + return entity; } #endregion