diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 2223d45cd8..59cd36b3f8 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -232,6 +232,7 @@ namespace Umbraco.Core.Models /// /// This Property is kept internal until localization is introduced. /// + [DataMember] internal string NodeName { get { return _nodeName; } @@ -248,6 +249,7 @@ namespace Umbraco.Core.Models /// /// Used internally to track if permissions have been changed during the saving process for this entity /// + [IgnoreDataMember] internal bool PermissionsChanged { get { return _permissionsChanged; } @@ -314,6 +316,7 @@ namespace Umbraco.Core.Models PublishedState = state; } + [DataMember] internal PublishedState PublishedState { get; set; } /// @@ -333,25 +336,25 @@ namespace Umbraco.Core.Models } } - /// - /// Creates a clone of the current entity - /// - /// - public IContent Clone() - { - var clone = (Content)this.MemberwiseClone(); - clone.Key = Guid.Empty; - clone.Version = Guid.NewGuid(); - clone.ResetIdentity(); + ///// + ///// Creates a clone of the current entity + ///// + ///// + //public IContent Clone() + //{ + // var clone = (Content)this.MemberwiseClone(); + // clone.Key = Guid.Empty; + // clone.Version = Guid.NewGuid(); + // clone.ResetIdentity(); - foreach (var property in clone.Properties) - { - property.ResetIdentity(); - property.Version = clone.Version; - } + // foreach (var property in clone.Properties) + // { + // property.ResetIdentity(); + // property.Version = clone.Version; + // } - return clone; - } + // return clone; + //} /// /// Indicates whether a specific property on the current entity is dirty. @@ -432,5 +435,43 @@ namespace Umbraco.Core.Models base.UpdatingEntity(); Version = Guid.NewGuid(); } + + /// + /// TODO: Remove this as it's really only a shallow clone and not thread safe + /// Creates a clone of the current entity + /// + /// + public IContent Clone() + { + var clone = (Content)this.MemberwiseClone(); + clone.Key = Guid.Empty; + clone.Version = Guid.NewGuid(); + clone.ResetIdentity(); + + foreach (var property in clone.Properties) + { + property.ResetIdentity(); + property.Version = clone.Version; + } + + return clone; + } + + public override T DeepClone() + { + var clone = base.DeepClone(); + + var asContent = (Content)(object)clone; + if (Template != null) + { + asContent.Template = Template.DeepClone(); + } + + asContent._contentType = ContentType.DeepClone(); + asContent.ResetDirtyProperties(true); + + return clone; + + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index 9531860051..521afeee78 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -15,10 +15,13 @@ namespace Umbraco.Core.Models /// /// Represents an abstract class for base Content properties and methods /// + [Serializable] + [DataContract(IsReference = true)] [DebuggerDisplay("Id: {Id}, Name: {Name}, ContentType: {ContentTypeBase.Alias}")] public abstract class ContentBase : Entity, IContentBase { protected IContentTypeComposition ContentTypeBase; + private Lazy _parentId; private string _name;//NOTE Once localization is introduced this will be the localized Name of the Content/Media. private int _sortOrder; @@ -257,6 +260,7 @@ namespace Umbraco.Core.Models } private readonly IDictionary _additionalData; + /// /// Some entities may expose additional data that other's might not, this custom data will be available in this collection /// @@ -465,5 +469,22 @@ namespace Umbraco.Core.Models prop.ResetDirtyProperties(rememberPreviouslyChangedProperties); } } + + public override T DeepClone() + { + var clone = base.DeepClone(); + + //cast to this object to set the complex properties + var asContentBase = (IContentBase)clone; + asContentBase.Properties = Properties.DeepClone(); + + var tracksChanges = clone as TracksChangesEntityBase; + if (tracksChanges != null) + { + tracksChanges.ResetDirtyProperties(true); + } + + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 46bf9bdc18..2d6f180ee4 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -122,6 +122,28 @@ namespace Umbraco.Core.Models } /// + /// Method to call when Entity is being saved + /// + /// Created date is set and a Unique key is assigned + internal override void AddingEntity() + { + base.AddingEntity(); + + if(Key == Guid.Empty) + 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(); + } + + /// + /// //TODO: REmove this as it's mostly just a shallow clone and not thread safe /// Creates a clone of the current entity /// /// @@ -153,25 +175,15 @@ namespace Umbraco.Core.Models return clone; } - /// - /// Method to call when Entity is being saved - /// - /// Created date is set and a Unique key is assigned - internal override void AddingEntity() + public override T DeepClone() { - base.AddingEntity(); + var clone = base.DeepClone(); - if(Key == Guid.Empty) - Key = Guid.NewGuid(); - } + var contentType = (ContentType)(object)clone; + contentType.AllowedTemplates = AllowedTemplates.Select(t => t.DeepClone()).ToArray(); + contentType.ResetDirtyProperties(true); - /// - /// 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(); + return clone; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index 005158d4f4..b13e1d4115 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -319,7 +319,7 @@ namespace Umbraco.Core.Models } } - private readonly IDictionary _additionalData; + private IDictionary _additionalData; /// /// Some entities may expose additional data that other's might not, this custom data will be available in this collection /// @@ -577,5 +577,21 @@ namespace Umbraco.Core.Models propertyType.ResetDirtyProperties(); } } + + public override T DeepClone() + { + var clone = base.DeepClone(); + + var contentType = (ContentTypeBase)(object)clone; + contentType._additionalData = new Dictionary(); + contentType._additionalData.MergeLeft(_additionalData); + contentType.AllowedContentTypes = AllowedContentTypes.Select(x => x.DeepClone()).ToList(); + contentType.PropertyGroups = PropertyGroups.DeepClone(); + contentType.PropertyTypes = PropertyTypes.Select(x => x.DeepClone()).ToList(); + + contentType.ResetDirtyProperties(true); + + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 8e4583adb8..138f2cff45 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public abstract class ContentTypeCompositionBase : ContentTypeBase, IContentTypeComposition { - private readonly List _contentTypeComposition = new List(); + private List _contentTypeComposition = new List(); internal List RemovedContentTypeKeyTracker = new List(); protected ContentTypeCompositionBase(int parentId) : base(parentId) @@ -217,5 +217,17 @@ namespace Umbraco.Core.Models .Select(x => x.Id) .Union(ContentTypeComposition.SelectMany(x => x.CompositionIds())); } + + public override T DeepClone() + { + var clone = base.DeepClone(); + + var contentType = (ContentTypeCompositionBase)(object)clone; + contentType.RemovedContentTypeKeyTracker = new List(); + contentType._contentTypeComposition = ContentTypeComposition.Select(x => x.DeepClone()).ToList(); + contentType.ResetDirtyProperties(true); + + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ContentTypeSort.cs b/src/Umbraco.Core/Models/ContentTypeSort.cs index 6790f15fb0..431b1e2cbe 100644 --- a/src/Umbraco.Core/Models/ContentTypeSort.cs +++ b/src/Umbraco.Core/Models/ContentTypeSort.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Models /// /// Represents a POCO for setting sort order on a ContentType reference /// - public class ContentTypeSort : IValueObject + public class ContentTypeSort : IValueObject, IDeepCloneable { public ContentTypeSort() { @@ -33,5 +33,14 @@ namespace Umbraco.Core.Models /// Gets or sets the Alias of the ContentType /// public string Alias { get; set; } + + + public T DeepClone() where T: IDeepCloneable + { + var clone = (ContentTypeSort)MemberwiseClone(); + var id = Id.Value; + clone.Id = new Lazy(() => id); + return (T)(IDeepCloneable)clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityBase/Entity.cs b/src/Umbraco.Core/Models/EntityBase/Entity.cs index 8a9521c23c..3d8d7b40b3 100644 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs @@ -1,7 +1,10 @@ using System; using System.Diagnostics; +using System.IO; using System.Reflection; using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; namespace Umbraco.Core.Models.EntityBase { @@ -226,5 +229,60 @@ namespace Umbraco.Core.Models.EntityBase _hash = !HasIdentity ? new int?(base.GetHashCode()) : new int?(Id.GetHashCode() * 397 ^ GetType().GetHashCode()); return _hash.Value; } + + public virtual T DeepClone() where T : IDeepCloneable + { + //Memberwise clone on Entity will work since it doesn't have any deep elements + // for any sub class this will work for standard properties as well that aren't complex object's themselves. + var clone = MemberwiseClone(); + ((TracksChangesEntityBase)clone).ResetDirtyProperties(true); + return (T)clone; + + //Using data contract serializer - has issues + + //var s = Serialize(this); + //var d = Deserialize(s, this.GetType()); + //return d; + + //Using binary serializer - has issues + + //using (var memoryStream = new MemoryStream(10)) + //{ + //IFormatter formatter = new BinaryFormatter(); + //formatter.Serialize(memoryStream, this); + //memoryStream.Seek(0, SeekOrigin.Begin); + //return formatter.Deserialize(memoryStream); + //} + } + + // serialize/deserialize with data contracts: + + //public static string Serialize(object obj) + //{ + // using (var memoryStream = new MemoryStream()) + // using (var reader = new StreamReader(memoryStream)) + // { + // var serializer = new DataContractSerializer(obj.GetType()); + // serializer.WriteObject(memoryStream, obj); + // memoryStream.Position = 0; + // return reader.ReadToEnd(); + // } + //} + + //public static object Deserialize(string xml, Type toType) + //{ + // using (Stream stream = new MemoryStream()) + // { + // using (var writer = new StreamWriter(stream, Encoding.UTF8)) + // { + // writer.Write(xml); + // //byte[] data = Encoding.UTF8.GetBytes(xml); + // //stream.Write(data, 0, data.Length); + // stream.Position = 0; + // var deserializer = new DataContractSerializer(toType); + // return deserializer.ReadObject(stream); + // } + // } + //} } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityBase/IDeepCloneable.cs b/src/Umbraco.Core/Models/EntityBase/IDeepCloneable.cs new file mode 100644 index 0000000000..9d64e22858 --- /dev/null +++ b/src/Umbraco.Core/Models/EntityBase/IDeepCloneable.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Core.Models.EntityBase +{ + public interface IDeepCloneable + { + T DeepClone() where T : IDeepCloneable; + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/EntityBase/IEntity.cs b/src/Umbraco.Core/Models/EntityBase/IEntity.cs index 9557d6fb0e..81f5f632ef 100644 --- a/src/Umbraco.Core/Models/EntityBase/IEntity.cs +++ b/src/Umbraco.Core/Models/EntityBase/IEntity.cs @@ -9,7 +9,7 @@ namespace Umbraco.Core.Models.EntityBase /// /// The current database schema doesn't provide a modified date /// for all entities, so this will have to be changed at a later stage. - public interface IEntity + public interface IEntity : IDeepCloneable { /// /// The Id of the entity diff --git a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs b/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs index dc65c583bc..1d23fe8385 100644 --- a/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs +++ b/src/Umbraco.Core/Models/EntityBase/TracksChangesEntityBase.cs @@ -3,12 +3,15 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; +using System.Runtime.Serialization; namespace Umbraco.Core.Models.EntityBase { /// /// A base class for use to implement IRememberBeingDirty/ICanBeDirty /// + [Serializable] + [DataContract(IsReference = true)] public abstract class TracksChangesEntityBase : IRememberBeingDirty { /// diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index f520b63210..ff5dac1a96 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -6,16 +6,6 @@ using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { - internal sealed class Folder : Entity - { - public Folder(string folderPath) - { - Path = folderPath; - } - - public string Path { get; set; } - } - /// /// Represents an abstract file which provides basic functionality for a File with an Alias and Name /// @@ -114,5 +104,22 @@ namespace Umbraco.Core.Models /// /// True if file is valid, otherwise false public abstract bool IsValid(); + + public override T DeepClone() + { + var clone = base.DeepClone(); + + var asFile = (File)(object)clone; + asFile._alias = Alias; + asFile._name = Name; + + var tracksChanges = clone as TracksChangesEntityBase; + if (tracksChanges != null) + { + tracksChanges.ResetDirtyProperties(true); + } + + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Folder.cs b/src/Umbraco.Core/Models/Folder.cs new file mode 100644 index 0000000000..be8e9b6dc4 --- /dev/null +++ b/src/Umbraco.Core/Models/Folder.cs @@ -0,0 +1,14 @@ +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Models +{ + internal sealed class Folder : Entity + { + public Folder(string folderPath) + { + Path = folderPath; + } + + public string Path { get; set; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index af70f4d7c4..558b3156de 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -7,7 +7,7 @@ using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Configuration; using Umbraco.Core.Models.EntityBase; -using Umbraco.Core.Persistence.Mappers; + namespace Umbraco.Core.Models.Membership { @@ -20,7 +20,7 @@ namespace Umbraco.Core.Models.Membership /// [Serializable] [DataContract(IsReference = true)] - public class User : TracksChangesEntityBase, IUser + public class User : Entity, IUser { public User(IUserType userType) { @@ -54,8 +54,6 @@ namespace Umbraco.Core.Models.Membership } private IUserType _userType; - private bool _hasIdentity; - private int _id; private string _name; private Type _userTypeKey; private readonly List _addedSections; @@ -78,7 +76,6 @@ namespace Umbraco.Core.Models.Membership private static readonly PropertyInfo StartContentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartContentId); private static readonly PropertyInfo StartMediaIdSelector = ExpressionHelper.GetPropertyInfo(x => x.StartMediaId); private static readonly PropertyInfo AllowedSectionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.AllowedSections); - private static readonly PropertyInfo IdSelector = ExpressionHelper.GetPropertyInfo(x => x.Id); private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo(x => x.Name); private static readonly PropertyInfo UserTypeKeySelector = ExpressionHelper.GetPropertyInfo(x => x.ProviderUserKeyType); @@ -90,52 +87,8 @@ namespace Umbraco.Core.Models.Membership private static readonly PropertyInfo LanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.Language); private static readonly PropertyInfo DefaultPermissionsSelector = ExpressionHelper.GetPropertyInfo>(x => x.DefaultPermissions); private static readonly PropertyInfo DefaultToLiveEditingSelector = ExpressionHelper.GetPropertyInfo(x => x.DefaultToLiveEditing); - private static readonly PropertyInfo HasIdentitySelector = ExpressionHelper.GetPropertyInfo(x => x.HasIdentity); private static readonly PropertyInfo UserTypeSelector = ExpressionHelper.GetPropertyInfo(x => x.UserType); - - #region Implementation of IEntity - - [IgnoreDataMember] - public bool HasIdentity - { - get - { - return _hasIdentity; - } - protected set - { - SetPropertyValueAndDetectChanges(o => - { - _hasIdentity = value; - return _hasIdentity; - }, _hasIdentity, HasIdentitySelector); - } - } - - [DataMember] - public int Id - { - get - { - return _id; - } - set - { - SetPropertyValueAndDetectChanges(o => - { - _id = value; - HasIdentity = true; //set the has Identity - return _id; - }, _id, IdSelector); - } - } - - //this doesn't get used - [IgnoreDataMember] - public Guid Key { get; set; } - - #endregion - + #region Implementation of IMembershipUser [IgnoreDataMember] @@ -253,11 +206,7 @@ namespace Umbraco.Core.Models.Membership [IgnoreDataMember] public string PasswordAnswer { get; set; } [IgnoreDataMember] - public string Comments { get; set; } - [IgnoreDataMember] - public DateTime CreateDate { get; set; } - [IgnoreDataMember] - public DateTime UpdateDate { get; set; } + public string Comments { get; set; } [IgnoreDataMember] public DateTime LastLoginDate { get; set; } [IgnoreDataMember] diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index d6298d9da2..45c2a8e617 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -13,7 +13,7 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Property : Entity { - private readonly PropertyType _propertyType; + private PropertyType _propertyType; private Guid _version; private object _value; @@ -142,5 +142,22 @@ namespace Umbraco.Core.Models { return _propertyType.IsPropertyValueValid(value); } + + public override T DeepClone() + { + var clone = base.DeepClone(); + + var asProperty = (Property)(object)clone; + asProperty._propertyType = PropertyType.DeepClone(); + asProperty.ResetDirtyProperties(true); + + var tracksChanges = clone as TracksChangesEntityBase; + if (tracksChanges != null) + { + tracksChanges.ResetDirtyProperties(true); + } + + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyCollection.cs b/src/Umbraco.Core/Models/PropertyCollection.cs index db90d5b42b..79ae50bc05 100644 --- a/src/Umbraco.Core/Models/PropertyCollection.cs +++ b/src/Umbraco.Core/Models/PropertyCollection.cs @@ -5,6 +5,7 @@ using System.Collections.Specialized; using System.Linq; using System.Runtime.Serialization; using System.Threading; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { @@ -13,7 +14,7 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract(IsReference = true)] - public class PropertyCollection : KeyedCollection, INotifyCollectionChanged + public class PropertyCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable { private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); internal Action OnAdd; @@ -195,5 +196,19 @@ namespace Umbraco.Core.Models } } } + + /// + /// Create a deep clone of this property collection + /// + /// + public T DeepClone() where T: IDeepCloneable + { + var newList = new PropertyCollection(); + foreach (var p in this) + { + newList.Add(p.DeepClone()); + } + return (T)(object)newList; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index a7834c8b1e..73125f576c 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -107,23 +107,6 @@ namespace Umbraco.Core.Models } } - internal PropertyGroup Clone() - { - var clone = (PropertyGroup) this.MemberwiseClone(); - var collection = new PropertyTypeCollection(); - foreach (var propertyType in this.PropertyTypes) - { - var property = propertyType.Clone(); - property.ResetIdentity(); - property.ResetDirtyProperties(false); - collection.Add(property); - } - clone.PropertyTypes = collection; - clone.ResetIdentity(); - clone.ResetDirtyProperties(false); - return clone; - } - /// /// Sets the ParentId from the lazy integer id /// @@ -157,5 +140,34 @@ namespace Umbraco.Core.Models //Calculate the hash code for the product. return hashName ^ hashId; } + + //TODO: Remove this, its mostly a shallow clone and is not thread safe + internal PropertyGroup Clone() + { + var clone = (PropertyGroup)this.MemberwiseClone(); + var collection = new PropertyTypeCollection(); + foreach (var propertyType in this.PropertyTypes) + { + var property = propertyType.Clone(); + property.ResetIdentity(); + property.ResetDirtyProperties(false); + collection.Add(property); + } + clone.PropertyTypes = collection; + clone.ResetIdentity(); + clone.ResetDirtyProperties(false); + return clone; + } + + public override T DeepClone() + { + var clone = base.DeepClone(); + + var propertyGroup = (PropertyGroup)(object)clone; + propertyGroup.PropertyTypes = PropertyTypes.DeepClone(); + propertyGroup.ResetDirtyProperties(true); + + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyGroupCollection.cs b/src/Umbraco.Core/Models/PropertyGroupCollection.cs index facb5b9e5c..57fad535f1 100644 --- a/src/Umbraco.Core/Models/PropertyGroupCollection.cs +++ b/src/Umbraco.Core/Models/PropertyGroupCollection.cs @@ -5,6 +5,7 @@ using System.Collections.Specialized; using System.Linq; using System.Runtime.Serialization; using System.Threading; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { @@ -13,9 +14,10 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract] - public class PropertyGroupCollection : KeyedCollection, INotifyCollectionChanged + public class PropertyGroupCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable { private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); + internal Action OnAdd; internal PropertyGroupCollection() @@ -131,5 +133,15 @@ namespace Umbraco.Core.Models CollectionChanged(this, args); } } + + public T DeepClone() where T : IDeepCloneable + { + var newGroup = new PropertyGroupCollection(); + foreach (var p in this) + { + newGroup.Add(p.DeepClone()); + } + return (T)(object)newGroup; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index cfeb9e832d..0c6b169c2b 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -386,14 +386,6 @@ namespace Umbraco.Core.Models return true; } - internal PropertyType Clone() - { - var clone = (PropertyType) this.MemberwiseClone(); - clone.ResetIdentity(); - clone.ResetDirtyProperties(false); - return clone; - } - public bool Equals(PropertyType other) { //Check whether the compared object is null. @@ -417,5 +409,35 @@ namespace Umbraco.Core.Models //Calculate the hash code for the product. return hashName ^ hashAlias; } + + //TODO: Remove this + internal PropertyType Clone() + { + var clone = (PropertyType)this.MemberwiseClone(); + clone.ResetIdentity(); + clone.ResetDirtyProperties(false); + return clone; + } + + public override T DeepClone() + { + var clone = base.DeepClone(); + + var asPropertyType = (PropertyType)(object)clone; + + if (PropertyGroupId != null) + { + var propGroupId = PropertyGroupId.Value; + asPropertyType._propertyGroupId = new Lazy(() => propGroupId); + } + + var tracksChanges = clone as TracksChangesEntityBase; + if (tracksChanges != null) + { + tracksChanges.ResetDirtyProperties(true); + } + + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs index 27e3926f09..34588f2e80 100644 --- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs +++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs @@ -5,6 +5,7 @@ using System.Collections.Specialized; using System.Linq; using System.Runtime.Serialization; using System.Threading; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { @@ -13,9 +14,12 @@ namespace Umbraco.Core.Models /// [Serializable] [DataContract] - public class PropertyTypeCollection : KeyedCollection, INotifyCollectionChanged + public class PropertyTypeCollection : KeyedCollection, INotifyCollectionChanged, IDeepCloneable { + [IgnoreDataMember] private readonly ReaderWriterLockSlim _addLocker = new ReaderWriterLockSlim(); + + [IgnoreDataMember] internal Action OnAdd; internal PropertyTypeCollection() @@ -131,5 +135,15 @@ namespace Umbraco.Core.Models CollectionChanged(this, args); } } + + public T DeepClone() where T : IDeepCloneable + { + var newGroup = new PropertyTypeCollection(); + foreach (var p in this) + { + newGroup.Add(p.DeepClone()); + } + return (T)(object)newGroup; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 21b0344659..5a152bd92b 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Configuration; using Umbraco.Core.IO; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Models { @@ -14,20 +15,18 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Template : File, ITemplate { - private readonly string _alias; - private readonly string _name; + private string _alias; + private string _name; private int _creatorId; private int _level; private int _sortOrder; private int _parentId; - private int _masterTemplateId; private string _masterTemplateAlias; private static readonly PropertyInfo CreatorIdSelector = ExpressionHelper.GetPropertyInfo(x => x.CreatorId); private static readonly PropertyInfo LevelSelector = ExpressionHelper.GetPropertyInfo(x => x.Level); private static readonly PropertyInfo SortOrderSelector = ExpressionHelper.GetPropertyInfo(x => x.SortOrder); private static readonly PropertyInfo ParentIdSelector = ExpressionHelper.GetPropertyInfo(x => x.ParentId); - //private static readonly PropertyInfo MasterTemplateIdSelector = ExpressionHelper.GetPropertyInfo(x => x.MasterTemplateId); private static readonly PropertyInfo MasterTemplateAliasSelector = ExpressionHelper.GetPropertyInfo(x => x.MasterTemplateAlias); @@ -197,5 +196,22 @@ namespace Umbraco.Core.Models { MasterTemplateId = new Lazy(() => masterTemplate.Id); } + + public override T DeepClone() + { + var clone = base.DeepClone(); + + var asTemplate = (Template)(object)clone; + asTemplate._alias = Alias; + asTemplate._name = Name; + + var tracksChanges = clone as TracksChangesEntityBase; + if (tracksChanges != null) + { + tracksChanges.ResetDirtyProperties(true); + } + + return clone; + } } } diff --git a/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs b/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs index 273d605bef..ff6db47860 100644 --- a/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; @@ -29,6 +29,9 @@ namespace Umbraco.Core.Persistence.Caching /// when there are async requests being made even in the context of a web request, the HttpContext.Current will be null but the HttpRuntime.Cache will /// always be available. /// + /// TODO: Each item that get's added to this cache will be a clone of the original with it's dirty properties reset, and every item that is resolved from the cache + /// is a clone of the item that is in there, otherwise we end up with thread safety issues since multiple thread would be working on the exact same entity at the same time. + /// /// internal sealed class RuntimeCacheProvider : IRepositoryCacheProvider { diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 98a2aab8b9..0f71c0b783 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -194,7 +194,9 @@ + + diff --git a/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs b/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs index 0699370259..018ef43ff6 100644 --- a/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs +++ b/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs @@ -419,5 +419,10 @@ namespace Umbraco.Tests.Models.Collections _hash = !HasIdentity ? new int?(base.GetHashCode()) : new int?(Id.GetHashCode() * 397 ^ GetType().GetHashCode()); return _hash.Value; }*/ + + public T DeepClone() where T : IDeepCloneable + { + return (T)this.MemberwiseClone(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index beae30fd77..f35818eb7e 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -120,13 +120,15 @@ namespace Umbraco.Tests.Models content.Key = new Guid("29181B97-CB8F-403F-86DE-5FEB497F4800"); // Act - var clone = content.Clone(); + var clone = content.DeepClone(); // Assert Assert.AreNotSame(clone, content); Assert.AreNotSame(clone.Id, content.Id); Assert.AreNotSame(clone.Version, content.Version); Assert.That(clone.HasIdentity, Is.False); + + Assert.AreNotSame(content.Properties, clone.Properties); } /*[Test]