From 3052c7311fcdbd14e429809beeeb81bb604796ac Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Tue, 19 Mar 2013 02:02:11 +0600 Subject: [PATCH] Converts dictionary cache in ContentType to use concurrent dictionary. Changes ContentTypeControlNew to not call the propertytype.delete() method if it is a DocumentType or MediaType (should only be called for legacy code like members). Updates ContentTypeCacheRefresher to ensure that all of the content type cache is cleared properly in the ICacheRefresher. --- .../Cache/ContentTypeCacheRefresher.cs | 83 +++++++++++++--- .../controls/ContentTypeControlNew.ascx.cs | 10 +- src/umbraco.cms/businesslogic/ContentType.cs | 96 ++++++++----------- .../businesslogic/Packager/Installer.cs | 2 +- 4 files changed, 120 insertions(+), 71 deletions(-) diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index dfac28c87a..7145c72ea7 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -5,10 +5,18 @@ using System.Text; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Models; +using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence.Caching; namespace Umbraco.Web.Cache { + + /// + /// A cache refresher to ensure content type cache is updated when members change + /// + /// + /// This is not intended to be used directly in your code + /// public sealed class ContentTypeCacheRefresher : ICacheRefresher, ICacheRefresher { public Guid UniqueIdentifier @@ -25,7 +33,11 @@ namespace Umbraco.Web.Cache //all property type cache ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(CacheKeys.PropertyTypeCacheKey); //all content type property cache - ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(CacheKeys.ContentTypePropertiesCacheKey); + ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(CacheKeys.ContentTypePropertiesCacheKey); + //all content type cache + ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(CacheKeys.ContentTypeCacheKey); + //clear static object cache + global::umbraco.cms.businesslogic.ContentType.RemoveAllDataTypeCache(); } public void Refresh(int id) @@ -62,27 +74,70 @@ namespace Umbraco.Web.Cache ClearContentTypeCache(instance); } + /// + /// This clears out all cache associated with a content type + /// + /// + /// + /// The cache that is required to be cleared when a content type is updated is as follows: + /// - ApplicationCache (keys to clear): + /// -- CacheKeys.PropertyTypeCacheKey + propertyType.Id (each property type assigned) + /// -- CacheKeys.ContentTypePropertiesCacheKey + contentType.Id + /// - ContentType.RemoveFromDataTypeCache (clears static object/dictionary cache) + /// - InMemoryCacheProvider.Current.Clear(); + /// - RuntimeCacheProvider.Current.Clear(); + /// + /// TODO: Needs to update any content items that this effects for the xml cache... currently it would seem that this is not handled! + /// it is only handled in the ContentTypeControlNew.ascx, not by business logic/events. - The xml cache needs to be updated when the doc type alias changes or when a property type is removed, the ContentService.RePublishAll should be executed anytime either of these happens. + /// private static void ClearContentTypeCache(params IContentTypeBase[] contentTypes) { - contentTypes.ForEach(contentType => - { - //clears the cache for each property type associated with the content type - foreach (var p in contentType.PropertyTypes) - { - ApplicationContext.Current.ApplicationCache.ClearCacheItem(CacheKeys.PropertyTypeCacheKey + p.Id); - } - //clears the cache associated with the content type properties collection - ApplicationContext.Current.ApplicationCache.ClearCacheItem(CacheKeys.ContentTypePropertiesCacheKey + contentType.Id); - //clears the dictionary object cache of the legacy ContentType - global::umbraco.cms.businesslogic.ContentType.RemoveFromDataTypeCache(contentType.Alias); - }); + contentTypes.ForEach(ClearContentTypeCache); + + //clear the cache providers if there were any content types to clear if (contentTypes.Any()) { InMemoryCacheProvider.Current.Clear(); RuntimeCacheProvider.Current.Clear(); - } + } } + /// + /// Clears cache for an individual IContentTypeBase object + /// + /// + /// + /// See notes for the other overloaded ClearContentTypeCache for + /// full details on clearing cache. + /// + private static void ClearContentTypeCache(IContentTypeBase contentType) + { + //clears the cache for each property type associated with the content type + foreach (var p in contentType.PropertyTypes) + { + ApplicationContext.Current.ApplicationCache.ClearCacheItem(CacheKeys.PropertyTypeCacheKey + p.Id); + } + //clears the cache associated with the Content type itself + ApplicationContext.Current.ApplicationCache.ClearCacheItem(string.Format("{0}{1}", CacheKeys.ContentTypeCacheKey, contentType.Id)); + //clears the cache associated with the content type properties collection + ApplicationContext.Current.ApplicationCache.ClearCacheItem(CacheKeys.ContentTypePropertiesCacheKey + contentType.Id); + + //clears the dictionary object cache of the legacy ContentType + global::umbraco.cms.businesslogic.ContentType.RemoveFromDataTypeCache(contentType.Alias); + + //need to recursively clear the cache for each child content type + // performance related to http://issues.umbraco.org/issue/U4-1714 + var dtos = ApplicationContext.Current.DatabaseContext.Database.Fetch("WHERE parentContentTypeId = @Id", new { Id = contentType.Id }); + foreach (var dto in dtos) + { + ClearContentTypeCache(dto.ChildId); + } + } + + /// + /// Clears the cache for any content type with the specified Ids + /// + /// private static void ClearContentTypeCache(params int[] ids) { ClearContentTypeCache( diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs index 39283f1a7a..1bf050cf64 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/controls/ContentTypeControlNew.ascx.cs @@ -866,8 +866,14 @@ jQuery(document).ready(function() {{ refreshDropDowns(); }}); _contentType.ContentTypeItem.RemovePropertyType(gpw.PropertyType.Alias); _contentType.Save(); } - - gpw.GenricPropertyControl.PropertyType.delete(); + else + { + //if it is not a document type or a media type, then continue to call the legacy delete() method. + //the new API for document type and media type's will ensure that the data is removed correctly and that + //the cache is flushed correctly (using events). If it is not one of these types, we'll rever to the + //legacy operation (... like for members i suppose ?) + gpw.GenricPropertyControl.PropertyType.delete(); + } LoadContentType(_contentType.Id); BindDataGenericProperties(true); diff --git a/src/umbraco.cms/businesslogic/ContentType.cs b/src/umbraco.cms/businesslogic/ContentType.cs index 1d327976b2..17fc8c33ab 100644 --- a/src/umbraco.cms/businesslogic/ContentType.cs +++ b/src/umbraco.cms/businesslogic/ContentType.cs @@ -99,8 +99,7 @@ namespace umbraco.cms.businesslogic /// Used for cache so we don't have to lookup column names all the time, this is actually only used for the ChildrenAsTable methods /// private static readonly ConcurrentDictionary> AliasToNames = new ConcurrentDictionary>(); - - private static Dictionary, Guid> _propertyTypeCache = new Dictionary, Guid>(); + private static readonly ConcurrentDictionary, Guid> PropertyTypeCache = new ConcurrentDictionary, Guid>(); /// /// Returns a content type's columns alias -> name mapping @@ -112,68 +111,58 @@ namespace umbraco.cms.businesslogic /// internal static IDictionary GetAliasesAndNames(string contentTypeAlias) { - IDictionary cached; - if (AliasToNames.TryGetValue(contentTypeAlias, out cached)) - { - return cached; - } - - var ct = ContentType.GetByAlias(contentTypeAlias); - var userFields = ct.PropertyTypes.ToDictionary(x => x.Alias, x => x.Name); - AliasToNames.TryAdd(contentTypeAlias, userFields); - return userFields; + return AliasToNames.GetOrAdd(contentTypeAlias, s => + { + var ct = ContentType.GetByAlias(contentTypeAlias); + var userFields = ct.PropertyTypes.ToDictionary(x => x.Alias, x => x.Name); + return userFields; + }); } + /// + /// Removes the static object cache + /// + /// public static void RemoveFromDataTypeCache(string contentTypeAlias) { - lock (_propertyTypeCache) + var toDelete = PropertyTypeCache.Keys.Where(key => string.Equals(key.first, contentTypeAlias)).ToList(); + foreach (var key in toDelete) { - List> toDelete = new List>(); - foreach (Tuple key in _propertyTypeCache.Keys) - { - if (string.Equals(key.first, contentTypeAlias)) - { - toDelete.Add(key); - } - } - foreach (Tuple key in toDelete) - { - _propertyTypeCache.Remove(key); - } + Guid id; + PropertyTypeCache.TryRemove(key, out id); } - //don't put lock around this as it is ConcurrentDictionary. AliasToNames.Clear(); } + + /// + /// Removes the static object cache + /// + internal static void RemoveAllDataTypeCache() + { + AliasToNames.Clear(); + PropertyTypeCache.Clear(); + } + public static Guid GetDataType(string contentTypeAlias, string propertyTypeAlias) { - Tuple key = new Tuple() + var key = new Tuple() { first = contentTypeAlias, second = propertyTypeAlias }; - //The property type is not on IProperty (it's not stored in NodeFactory) - //first check the cache - if (_propertyTypeCache != null && _propertyTypeCache.ContainsKey(key)) - { - return _propertyTypeCache[key]; - } - - // With 4.10 we can't do this via direct SQL as we have content type mixins - Guid controlId = Guid.Empty; - ContentType ct = GetByAlias(contentTypeAlias); - PropertyType pt = ct.getPropertyType(propertyTypeAlias); - if (pt != null) - { - controlId = pt.DataTypeDefinition.DataType.Id; - } - - //add to cache (even if empty!) - if (!_propertyTypeCache.ContainsKey(key)) - { - _propertyTypeCache.Add(key, controlId); - } - return controlId; + return PropertyTypeCache.GetOrAdd(key, tuple => + { + // With 4.10 we can't do this via direct SQL as we have content type mixins + var controlId = Guid.Empty; + var ct = GetByAlias(contentTypeAlias); + var pt = ct.getPropertyType(propertyTypeAlias); + if (pt != null) + { + controlId = pt.DataTypeDefinition.DataType.Id; + } + return controlId; + }); } @@ -196,7 +185,7 @@ namespace umbraco.cms.businesslogic /// Flushes the tab cache. /// /// The tab id. - [Obsolete("Tab cache is flushed automatically by Umbraco when a content type changes")] + [Obsolete("There is no cache to flush for tabs")] public static void FlushTabCache(int TabId, int ContentTypeId) { Tab.FlushCache(TabId, ContentTypeId); @@ -731,7 +720,7 @@ namespace umbraco.cms.businesslogic [Obsolete("Use PropertyTypeGroup methods instead", false)] public void ClearVirtualTabs() { - // zb-00040 #29889 : clear the right cache! t.contentType is the ctype which _defines_ the tab, not the current one. + //NOTE: SD: There is no cache to clear so this doesn't actually do anything foreach (TabI t in getVirtualTabs) Tab.FlushCache(t.Id, Id); @@ -1132,14 +1121,13 @@ namespace umbraco.cms.businesslogic } } - [Obsolete("The content type cache is automatically cleared by Umbraco when a content type is saved")] + [Obsolete("The content type cache is automatically cleared by Umbraco when a content type is saved, this method is no longer used")] protected internal void FlushAllFromCache() { ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(CacheKeys.ContentTypeCacheKey); ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch(CacheKeys.ContentTypePropertiesCacheKey); - //clear the property datatype cache used by razor - _propertyTypeCache = new Dictionary, Guid>(); + RemoveAllDataTypeCache(); ClearVirtualTabs(); } diff --git a/src/umbraco.cms/businesslogic/Packager/Installer.cs b/src/umbraco.cms/businesslogic/Packager/Installer.cs index 36991f26bd..08e85a4919 100644 --- a/src/umbraco.cms/businesslogic/Packager/Installer.cs +++ b/src/umbraco.cms/businesslogic/Packager/Installer.cs @@ -867,7 +867,7 @@ namespace umbraco.cms.businesslogic.packager } } - // clear caching + // clear caching (NOTE: SD: there is no tab caching so this really doesn't do anything) foreach (DocumentType.TabI t in dt.getVirtualTabs.ToList()) DocumentType.FlushTabCache(t.Id, dt.Id);