diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 1cf7fc9684..ec763d07fa 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -133,29 +133,6 @@ namespace Umbraco.Core.Models Key = Guid.NewGuid(); } - /// - /// Method to call when Entity is being updated - /// - /// Modified Date is set and a new Version guid is set - internal override void UpdatingEntity() - { - base.UpdatingEntity(); - } - - public override object DeepClone() - { - var clone = (ContentType)base.DeepClone(); - var propertyGroups = PropertyGroups.Select(x => (PropertyGroup)x.DeepClone()).ToList(); - clone.PropertyGroups = new PropertyGroupCollection(propertyGroups); - //set the property types that are not part of a group - clone.PropertyTypes = PropertyTypeCollection - .Where(x => x.PropertyGroupId == null) - .Select(x => (PropertyType)x.DeepClone()).ToList(); - - clone.ResetDirtyProperties(false); - - return clone; - } /// /// Creates a deep clone of the current entity with its identity/alias and it's property identities reset diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index da4cdcfd19..5c09deadb8 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -351,7 +351,9 @@ namespace Umbraco.Core.Models /// /// List of PropertyGroups available on this ContentType /// - /// A PropertyGroup corresponds to a Tab in the UI + /// + /// A PropertyGroup corresponds to a Tab in the UI + /// [DataMember] public virtual PropertyGroupCollection PropertyGroups { @@ -367,7 +369,13 @@ namespace Umbraco.Core.Models /// List of PropertyTypes available on this ContentType. /// This list aggregates PropertyTypes across the PropertyGroups. /// + /// + /// Marked as DoNotClone because the result of this property is not the natural result of the data, it is + /// a union of data so when auto-cloning if the setter is used it will be setting the unnatural result of the + /// data. We manually clone this instead. + /// [IgnoreDataMember] + [DoNotClone] public virtual IEnumerable PropertyTypes { get @@ -383,6 +391,14 @@ namespace Umbraco.Core.Models } /// + /// Returns the property type collection containing types that are non-groups - used for tests + /// + internal IEnumerable NonGroupedPropertyTypes + { + get { return _propertyTypes; } + } + + /// /// A boolean flag indicating if a property type has been removed from this instance. /// /// @@ -579,5 +595,19 @@ namespace Umbraco.Core.Models propertyType.ResetDirtyProperties(); } } + + public override object DeepClone() + { + var clone = (ContentTypeBase)base.DeepClone(); + + //need to manually wire up the event handlers for the property type collections - we've ensured + // its ignored from the auto-clone process because its return values are unions, not raw and + // we end up with duplicates, see: http://issues.umbraco.org/issue/U4-4842 + + clone._propertyTypes = (PropertyTypeCollection)_propertyTypes.DeepClone(); + clone._propertyTypes.CollectionChanged += clone.PropertyTypesChanged; + + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs index aaee03f963..d8ef10751b 100644 --- a/src/Umbraco.Core/Models/DeepCloneHelper.cs +++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs @@ -2,9 +2,29 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Reflection; namespace Umbraco.Core.Models { + /// + /// Used to attribute properties that have a setter and are a reference type + /// that should be ignored for cloning when using the DeepCloneHelper + /// + /// + /// + /// This attribute must be used: + /// * when the property is backed by a field but the result of the property is the un-natural data stored in the field + /// + /// This attribute should not be used: + /// * when the property is virtual + /// * when the setter performs additional required logic other than just setting the underlying field + /// + /// + internal class DoNotCloneAttribute : Attribute + { + + } + public static class DeepCloneHelper { /// @@ -25,8 +45,10 @@ namespace Umbraco.Core.Models var refProperties = inputType.GetProperties() .Where(x => + //is not attributed with the ignore clone attribute + x.GetCustomAttribute() == null //reference type but not string - x.PropertyType.IsValueType == false && x.PropertyType != typeof (string) + && x.PropertyType.IsValueType == false && x.PropertyType != typeof (string) //settable && x.CanWrite //non-indexed diff --git a/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs b/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs index c2ca53a240..acacadb0cc 100644 --- a/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs @@ -128,9 +128,9 @@ namespace Umbraco.Core.Persistence.Caching public void Save(Type type, IEntity entity) { //IMPORTANT: we must clone to store, see: http://issues.umbraco.org/issue/U4-4259 - entity = (IEntity)entity.DeepClone(); + var clone = (IEntity)entity.DeepClone(); - var key = GetCompositeId(type, entity.Id); + var key = GetCompositeId(type, clone.Id); _keyTracker.TryAdd(key); @@ -139,11 +139,11 @@ namespace Umbraco.Core.Persistence.Caching if (_memoryCache != null) { - _memoryCache.Set(key, entity, new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(5) }); + _memoryCache.Set(key, clone, new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(5) }); } else { - HttpRuntime.Cache.Insert(key, entity, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(5)); + HttpRuntime.Cache.Insert(key, clone, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(5)); } } diff --git a/src/Umbraco.Tests/Models/ContentTypeTests.cs b/src/Umbraco.Tests/Models/ContentTypeTests.cs index dc2de0d6bb..43b90c424e 100644 --- a/src/Umbraco.Tests/Models/ContentTypeTests.cs +++ b/src/Umbraco.Tests/Models/ContentTypeTests.cs @@ -162,6 +162,7 @@ namespace Umbraco.Tests.Models } Assert.AreNotSame(clone.PropertyTypes, contentType.PropertyTypes); Assert.AreEqual(clone.PropertyTypes.Count(), contentType.PropertyTypes.Count()); + Assert.AreEqual(0, ((ContentTypeBase)clone).NonGroupedPropertyTypes.Count()); for (var index = 0; index < contentType.PropertyTypes.Count(); index++) { Assert.AreNotSame(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index));