From b7f7775b89baba8387d853ca7e716c0c4239616b Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 20 Feb 2014 22:34:54 +1100 Subject: [PATCH 01/25] adds initial commit of entity cloning --- src/Umbraco.Core/Models/Content.cs | 75 ++++++++++++++----- src/Umbraco.Core/Models/ContentBase.cs | 21 ++++++ src/Umbraco.Core/Models/ContentType.cs | 44 +++++++---- src/Umbraco.Core/Models/ContentTypeBase.cs | 18 ++++- .../Models/ContentTypeCompositionBase.cs | 14 +++- src/Umbraco.Core/Models/ContentTypeSort.cs | 11 ++- src/Umbraco.Core/Models/EntityBase/Entity.cs | 58 ++++++++++++++ .../Models/EntityBase/IDeepCloneable.cs | 7 ++ src/Umbraco.Core/Models/EntityBase/IEntity.cs | 2 +- .../EntityBase/TracksChangesEntityBase.cs | 3 + src/Umbraco.Core/Models/File.cs | 27 ++++--- src/Umbraco.Core/Models/Folder.cs | 14 ++++ src/Umbraco.Core/Models/Membership/User.cs | 59 +-------------- src/Umbraco.Core/Models/Property.cs | 19 ++++- src/Umbraco.Core/Models/PropertyCollection.cs | 17 ++++- src/Umbraco.Core/Models/PropertyGroup.cs | 46 +++++++----- .../Models/PropertyGroupCollection.cs | 14 +++- src/Umbraco.Core/Models/PropertyType.cs | 38 ++++++++-- .../Models/PropertyTypeCollection.cs | 16 +++- src/Umbraco.Core/Models/Template.cs | 24 +++++- .../Caching/RuntimeCacheProvider.cs | 5 +- src/Umbraco.Core/Umbraco.Core.csproj | 2 + .../Collections/PropertyCollectionTests.cs | 5 ++ src/Umbraco.Tests/Models/ContentTests.cs | 4 +- 24 files changed, 406 insertions(+), 137 deletions(-) create mode 100644 src/Umbraco.Core/Models/EntityBase/IDeepCloneable.cs create mode 100644 src/Umbraco.Core/Models/Folder.cs 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] From 58758e7a41abc8a0c2660894479963c7e6d31cf4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 15 Apr 2014 13:52:49 +1000 Subject: [PATCH 02/25] More work on deep cloning, have test passing for IContent --- src/Umbraco.Core/Models/Content.cs | 35 ++----- src/Umbraco.Core/Models/ContentBase.cs | 7 +- src/Umbraco.Core/Models/ContentType.cs | 8 +- src/Umbraco.Core/Models/ContentTypeBase.cs | 12 +-- .../Models/ContentTypeCompositionBase.cs | 8 +- src/Umbraco.Core/Models/ContentTypeSort.cs | 4 +- src/Umbraco.Core/Models/EntityBase/Entity.cs | 7 +- .../Models/EntityBase/IDeepCloneable.cs | 2 +- src/Umbraco.Core/Models/File.cs | 6 +- src/Umbraco.Core/Models/Property.cs | 8 +- src/Umbraco.Core/Models/PropertyCollection.cs | 6 +- src/Umbraco.Core/Models/PropertyGroup.cs | 8 +- .../Models/PropertyGroupCollection.cs | 6 +- src/Umbraco.Core/Models/PropertyType.cs | 6 +- .../Models/PropertyTypeCollection.cs | 6 +- src/Umbraco.Core/Models/Template.cs | 6 +- .../Collections/PropertyCollectionTests.cs | 4 +- src/Umbraco.Tests/Models/ContentTests.cs | 98 ++++++++++++++++++- 18 files changed, 156 insertions(+), 81 deletions(-) diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 59cd36b3f8..0f843f9565 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -336,26 +336,6 @@ 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(); - - // foreach (var property in clone.Properties) - // { - // property.ResetIdentity(); - // property.Version = clone.Version; - // } - - // return clone; - //} - /// /// Indicates whether a specific property on the current entity is dirty. /// @@ -437,13 +417,12 @@ namespace Umbraco.Core.Models } /// - /// TODO: Remove this as it's really only a shallow clone and not thread safe - /// Creates a clone of the current entity + /// Creates a deep clone of the current entity with its identity and it's property identities reset /// /// public IContent Clone() { - var clone = (Content)this.MemberwiseClone(); + var clone = (Content)DeepClone(); clone.Key = Guid.Empty; clone.Version = Guid.NewGuid(); clone.ResetIdentity(); @@ -457,17 +436,17 @@ namespace Umbraco.Core.Models return clone; } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var asContent = (Content)(object)clone; + var asContent = (Content)clone; if (Template != null) { - asContent.Template = Template.DeepClone(); + asContent.Template = (ITemplate)Template.DeepClone(); } - asContent._contentType = ContentType.DeepClone(); + asContent._contentType = (IContentType)ContentType.DeepClone(); asContent.ResetDirtyProperties(true); return clone; diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index 521afeee78..75af29263f 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -448,6 +448,7 @@ namespace Umbraco.Core.Models /// /// Returns a collection of the result of the last validation process, this collection contains all invalid properties. /// + [IgnoreDataMember] internal IEnumerable LastInvalidProperties { get { return _lastInvalidProperties; } @@ -470,13 +471,13 @@ namespace Umbraco.Core.Models } } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); //cast to this object to set the complex properties var asContentBase = (IContentBase)clone; - asContentBase.Properties = Properties.DeepClone(); + asContentBase.Properties = (PropertyCollection)Properties.DeepClone(); var tracksChanges = clone as TracksChangesEntityBase; if (tracksChanges != null) diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 2d6f180ee4..12c528278b 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -175,12 +175,12 @@ namespace Umbraco.Core.Models return clone; } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var contentType = (ContentType)(object)clone; - contentType.AllowedTemplates = AllowedTemplates.Select(t => t.DeepClone()).ToArray(); + var contentType = (ContentType)clone; + contentType.AllowedTemplates = AllowedTemplates.Select(t => (ITemplate)t.DeepClone()).ToArray(); contentType.ResetDirtyProperties(true); return clone; diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index b13e1d4115..dfa34fc226 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -578,16 +578,16 @@ namespace Umbraco.Core.Models } } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var contentType = (ContentTypeBase)(object)clone; + var contentType = (ContentTypeBase)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.AllowedContentTypes = AllowedContentTypes.Select(x => (ContentTypeSort)x.DeepClone()).ToList(); + contentType.PropertyGroups = (PropertyGroupCollection)PropertyGroups.DeepClone(); + contentType.PropertyTypes = PropertyTypes.Select(x => (PropertyType)x.DeepClone()).ToList(); contentType.ResetDirtyProperties(true); diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 138f2cff45..b8b48e7eec 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -218,13 +218,13 @@ namespace Umbraco.Core.Models .Union(ContentTypeComposition.SelectMany(x => x.CompositionIds())); } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var contentType = (ContentTypeCompositionBase)(object)clone; + var contentType = (ContentTypeCompositionBase)clone; contentType.RemovedContentTypeKeyTracker = new List(); - contentType._contentTypeComposition = ContentTypeComposition.Select(x => x.DeepClone()).ToList(); + contentType._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList(); contentType.ResetDirtyProperties(true); return clone; diff --git a/src/Umbraco.Core/Models/ContentTypeSort.cs b/src/Umbraco.Core/Models/ContentTypeSort.cs index 431b1e2cbe..a52b5ae0f1 100644 --- a/src/Umbraco.Core/Models/ContentTypeSort.cs +++ b/src/Umbraco.Core/Models/ContentTypeSort.cs @@ -35,12 +35,12 @@ namespace Umbraco.Core.Models public string Alias { get; set; } - public T DeepClone() where T: IDeepCloneable + public object DeepClone() { var clone = (ContentTypeSort)MemberwiseClone(); var id = Id.Value; clone.Id = new Lazy(() => id); - return (T)(IDeepCloneable)clone; + return 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 3d8d7b40b3..27dad979fc 100644 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs @@ -96,7 +96,7 @@ namespace Umbraco.Core.Models.EntityBase } /// - /// /// Gets or sets the WasCancelled flag, which is used to track + /// Gets or sets the WasCancelled flag, which is used to track /// whether some action against an entity was cancelled through some event. /// This only exists so we have a way to check if an event was cancelled through /// the new api, which also needs to take effect in the legacy api. @@ -158,6 +158,7 @@ namespace Umbraco.Core.Models.EntityBase /// /// Indicates whether the current entity has an identity, eg. Id. /// + [DataMember] public virtual bool HasIdentity { get @@ -230,13 +231,13 @@ namespace Umbraco.Core.Models.EntityBase return _hash.Value; } - public virtual T DeepClone() where T : IDeepCloneable + public virtual object DeepClone() { //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; + return clone; //Using data contract serializer - has issues diff --git a/src/Umbraco.Core/Models/EntityBase/IDeepCloneable.cs b/src/Umbraco.Core/Models/EntityBase/IDeepCloneable.cs index 9d64e22858..62d8ba7f77 100644 --- a/src/Umbraco.Core/Models/EntityBase/IDeepCloneable.cs +++ b/src/Umbraco.Core/Models/EntityBase/IDeepCloneable.cs @@ -2,6 +2,6 @@ namespace Umbraco.Core.Models.EntityBase { public interface IDeepCloneable { - T DeepClone() where T : IDeepCloneable; + object DeepClone(); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index ff5dac1a96..49f2557d40 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -105,11 +105,11 @@ namespace Umbraco.Core.Models /// True if file is valid, otherwise false public abstract bool IsValid(); - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var asFile = (File)(object)clone; + var asFile = (File)clone; asFile._alias = Alias; asFile._name = Name; diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index 45c2a8e617..04aa545612 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -143,12 +143,12 @@ namespace Umbraco.Core.Models return _propertyType.IsPropertyValueValid(value); } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var asProperty = (Property)(object)clone; - asProperty._propertyType = PropertyType.DeepClone(); + var asProperty = (Property)clone; + asProperty._propertyType = (PropertyType)PropertyType.DeepClone(); asProperty.ResetDirtyProperties(true); var tracksChanges = clone as TracksChangesEntityBase; diff --git a/src/Umbraco.Core/Models/PropertyCollection.cs b/src/Umbraco.Core/Models/PropertyCollection.cs index 79ae50bc05..0ff2e13484 100644 --- a/src/Umbraco.Core/Models/PropertyCollection.cs +++ b/src/Umbraco.Core/Models/PropertyCollection.cs @@ -201,14 +201,14 @@ namespace Umbraco.Core.Models /// Create a deep clone of this property collection /// /// - public T DeepClone() where T: IDeepCloneable + public object DeepClone() { var newList = new PropertyCollection(); foreach (var p in this) { - newList.Add(p.DeepClone()); + newList.Add((Property)p.DeepClone()); } - return (T)(object)newList; + return newList; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index 73125f576c..000ae3959a 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -159,12 +159,12 @@ namespace Umbraco.Core.Models return clone; } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var propertyGroup = (PropertyGroup)(object)clone; - propertyGroup.PropertyTypes = PropertyTypes.DeepClone(); + var propertyGroup = (PropertyGroup)clone; + propertyGroup.PropertyTypes = (PropertyTypeCollection)PropertyTypes.DeepClone(); propertyGroup.ResetDirtyProperties(true); return clone; diff --git a/src/Umbraco.Core/Models/PropertyGroupCollection.cs b/src/Umbraco.Core/Models/PropertyGroupCollection.cs index 57fad535f1..6e1847856b 100644 --- a/src/Umbraco.Core/Models/PropertyGroupCollection.cs +++ b/src/Umbraco.Core/Models/PropertyGroupCollection.cs @@ -134,14 +134,14 @@ namespace Umbraco.Core.Models } } - public T DeepClone() where T : IDeepCloneable + public object DeepClone() { var newGroup = new PropertyGroupCollection(); foreach (var p in this) { - newGroup.Add(p.DeepClone()); + newGroup.Add((PropertyGroup)p.DeepClone()); } - return (T)(object)newGroup; + return newGroup; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 0c6b169c2b..28014ed2d6 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -419,11 +419,11 @@ namespace Umbraco.Core.Models return clone; } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var asPropertyType = (PropertyType)(object)clone; + var asPropertyType = (PropertyType)clone; if (PropertyGroupId != null) { diff --git a/src/Umbraco.Core/Models/PropertyTypeCollection.cs b/src/Umbraco.Core/Models/PropertyTypeCollection.cs index 34588f2e80..ea153ccae4 100644 --- a/src/Umbraco.Core/Models/PropertyTypeCollection.cs +++ b/src/Umbraco.Core/Models/PropertyTypeCollection.cs @@ -136,14 +136,14 @@ namespace Umbraco.Core.Models } } - public T DeepClone() where T : IDeepCloneable + public object DeepClone() { var newGroup = new PropertyTypeCollection(); foreach (var p in this) { - newGroup.Add(p.DeepClone()); + newGroup.Add((PropertyType)p.DeepClone()); } - return (T)(object)newGroup; + return newGroup; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 5a152bd92b..808e4627b7 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -197,11 +197,11 @@ namespace Umbraco.Core.Models MasterTemplateId = new Lazy(() => masterTemplate.Id); } - public override T DeepClone() + public override object DeepClone() { - var clone = base.DeepClone(); + var clone = base.DeepClone(); - var asTemplate = (Template)(object)clone; + var asTemplate = (Template)clone; asTemplate._alias = Alias; asTemplate._name = Name; diff --git a/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs b/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs index 018ef43ff6..928511e407 100644 --- a/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs +++ b/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs @@ -420,9 +420,9 @@ namespace Umbraco.Tests.Models.Collections return _hash.Value; }*/ - public T DeepClone() where T : IDeepCloneable + public object DeepClone() { - return (T)this.MemberwiseClone(); + return 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 f35818eb7e..f676569c5f 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -6,6 +6,7 @@ using System.Web; using Moq; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -111,7 +112,7 @@ namespace Umbraco.Tests.Models [Test] - public void Can_Clone_Content() + public void Can_Clone_Content_With_Reset_Identity() { // Arrange var contentType = MockedContentTypes.CreateTextpageContentType(); @@ -120,7 +121,7 @@ namespace Umbraco.Tests.Models content.Key = new Guid("29181B97-CB8F-403F-86DE-5FEB497F4800"); // Act - var clone = content.DeepClone(); + var clone = content.Clone(); // Assert Assert.AreNotSame(clone, content); @@ -131,6 +132,99 @@ namespace Umbraco.Tests.Models Assert.AreNotSame(content.Properties, clone.Properties); } + [Test] + public void Can_Deep_Clone() + { + // Arrange + var contentType = MockedContentTypes.CreateTextpageContentType(); + contentType.Id = 99; + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); + var i = 200; + foreach (var property in content.Properties) + { + property.Id = ++i; + } + content.Id = 10; + content.CreateDate = DateTime.Now; + content.CreatorId = 22; + content.ExpireDate = DateTime.Now; + content.Key = Guid.NewGuid(); + content.Language = "en"; + content.Level = 3; + content.Path = "-1,4,10"; + content.ReleaseDate = DateTime.Now; + content.ChangePublishedState(PublishedState.Published); + content.SortOrder = 5; + content.Template = new Template("-1,2,3,4", "Test Template", "testTemplate") + { + Id = 88 + }; + content.Trashed = false; + content.UpdateDate = DateTime.Now; + content.Version = Guid.NewGuid(); + content.WriterId = 23; + + ((IUmbracoEntity)content).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)content).AdditionalData.Add("test2", "hello"); + + // Act + var clone = (Content)content.DeepClone(); + + // Assert + Assert.AreNotSame(clone, content); + Assert.AreEqual(clone, content); + Assert.AreEqual(clone.Id, content.Id); + Assert.AreEqual(clone.Version, content.Version); + Assert.AreEqual(((IUmbracoEntity)clone).AdditionalData, ((IUmbracoEntity)content).AdditionalData); + Assert.AreNotSame(clone.ContentType, content.ContentType); + Assert.AreEqual(clone.ContentType, content.ContentType); + Assert.AreEqual(clone.ContentType.PropertyGroups.Count, content.ContentType.PropertyGroups.Count); + for (var index = 0; index < content.ContentType.PropertyGroups.Count; index++) + { + Assert.AreNotSame(clone.ContentType.PropertyGroups[index], content.ContentType.PropertyGroups[index]); + Assert.AreEqual(clone.ContentType.PropertyGroups[index], content.ContentType.PropertyGroups[index]); + } + Assert.AreEqual(clone.ContentType.PropertyTypes.Count(), content.ContentType.PropertyTypes.Count()); + for (var index = 0; index < content.ContentType.PropertyTypes.Count(); index++) + { + Assert.AreNotSame(clone.ContentType.PropertyTypes.ElementAt(index), content.ContentType.PropertyTypes.ElementAt(index)); + Assert.AreEqual(clone.ContentType.PropertyTypes.ElementAt(index), content.ContentType.PropertyTypes.ElementAt(index)); + } + Assert.AreEqual(clone.ContentTypeId, content.ContentTypeId); + Assert.AreEqual(clone.CreateDate, content.CreateDate); + Assert.AreEqual(clone.CreatorId, content.CreatorId); + Assert.AreEqual(clone.ExpireDate, content.ExpireDate); + Assert.AreEqual(clone.Key, content.Key); + Assert.AreEqual(clone.Language, content.Language); + Assert.AreEqual(clone.Level, content.Level); + Assert.AreEqual(clone.Path, content.Path); + Assert.AreEqual(clone.ReleaseDate, content.ReleaseDate); + Assert.AreEqual(clone.Published, content.Published); + Assert.AreEqual(clone.PublishedState, content.PublishedState); + Assert.AreEqual(clone.SortOrder, content.SortOrder); + Assert.AreEqual(clone.PublishedState, content.PublishedState); + Assert.AreNotSame(clone.Template, content.Template); + Assert.AreEqual(clone.Template, content.Template); + Assert.AreEqual(clone.Trashed, content.Trashed); + Assert.AreEqual(clone.UpdateDate, content.UpdateDate); + Assert.AreEqual(clone.Version, content.Version); + Assert.AreEqual(clone.WriterId, content.WriterId); + Assert.AreNotSame(clone.Properties, content.Properties); + Assert.AreEqual(clone.Properties.Count(), content.Properties.Count()); + for (var index = 0; index < content.Properties.Count; index++) + { + Assert.AreNotSame(clone.Properties[index], content.Properties[index]); + Assert.AreEqual(clone.Properties[index], content.Properties[index]); + } + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(content, null)); + } + } + /*[Test] public void Cannot_Change_Property_With_Invalid_Value() { From 911870166911aa4edf68b0df260a470bfe12d4c6 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 15 Apr 2014 14:53:19 +1000 Subject: [PATCH 03/25] More test written for deep cloning. --- src/Umbraco.Core/Models/ContentTypeBase.cs | 5 +- src/Umbraco.Tests/Models/ContentTypeTests.cs | 229 ++++++++++++++++++ .../Models/PropertyGroupTests.cs | 90 +++++++ src/Umbraco.Tests/Models/PropertyTypeTests.cs | 59 +++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 5 +- 5 files changed, 384 insertions(+), 4 deletions(-) create mode 100644 src/Umbraco.Tests/Models/ContentTypeTests.cs create mode 100644 src/Umbraco.Tests/Models/PropertyGroupTests.cs create mode 100644 src/Umbraco.Tests/Models/PropertyTypeTests.cs diff --git a/src/Umbraco.Core/Models/ContentTypeBase.cs b/src/Umbraco.Core/Models/ContentTypeBase.cs index dfa34fc226..ade9e8b9d9 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; @@ -583,8 +583,7 @@ namespace Umbraco.Core.Models var clone = base.DeepClone(); var contentType = (ContentTypeBase)clone; - contentType._additionalData = new Dictionary(); - contentType._additionalData.MergeLeft(_additionalData); + contentType.AllowedContentTypes = AllowedContentTypes.Select(x => (ContentTypeSort)x.DeepClone()).ToList(); contentType.PropertyGroups = (PropertyGroupCollection)PropertyGroups.DeepClone(); contentType.PropertyTypes = PropertyTypes.Select(x => (PropertyType)x.DeepClone()).ToList(); diff --git a/src/Umbraco.Tests/Models/ContentTypeTests.cs b/src/Umbraco.Tests/Models/ContentTypeTests.cs new file mode 100644 index 0000000000..71f03614e1 --- /dev/null +++ b/src/Umbraco.Tests/Models/ContentTypeTests.cs @@ -0,0 +1,229 @@ +using System; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class ContentTypeTests + { + [Test] + public void Can_Deep_Clone_Content_Type() + { + // Arrange + var contentType = MockedContentTypes.CreateTextpageContentType(); + contentType.Id = 99; + + var i = 200; + foreach (var propertyType in contentType.PropertyTypes) + { + propertyType.Id = ++i; + } + contentType.Id = 10; + contentType.CreateDate = DateTime.Now; + contentType.CreatorId = 22; + contentType.SetDefaultTemplate(new Template("-1,2,3,4", "Test Template", "testTemplate") + { + Id = 88 + }); + contentType.Description = "test"; + contentType.Icon = "icon"; + contentType.IsContainer = true; + contentType.Thumbnail = "thumb"; + contentType.Key = Guid.NewGuid(); + contentType.Level = 3; + contentType.Path = "-1,4,10"; + contentType.SortOrder = 5; + contentType.Trashed = false; + contentType.UpdateDate = DateTime.Now; + + ((IUmbracoEntity)contentType).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)contentType).AdditionalData.Add("test2", "hello"); + + // Act + var clone = (ContentType)contentType.DeepClone(); + + // Assert + Assert.AreNotSame(clone, contentType); + Assert.AreEqual(clone, contentType); + Assert.AreEqual(clone.Id, contentType.Id); + Assert.AreEqual(((IUmbracoEntity)clone).AdditionalData, ((IUmbracoEntity)contentType).AdditionalData); + Assert.AreEqual(clone.PropertyGroups.Count, contentType.PropertyGroups.Count); + for (var index = 0; index < contentType.PropertyGroups.Count; index++) + { + Assert.AreNotSame(clone.PropertyGroups[index], contentType.PropertyGroups[index]); + Assert.AreEqual(clone.PropertyGroups[index], contentType.PropertyGroups[index]); + } + Assert.AreEqual(clone.PropertyTypes.Count(), contentType.PropertyTypes.Count()); + for (var index = 0; index < contentType.PropertyTypes.Count(); index++) + { + Assert.AreNotSame(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); + Assert.AreEqual(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); + } + Assert.AreEqual(clone.CreateDate, contentType.CreateDate); + Assert.AreEqual(clone.CreatorId, contentType.CreatorId); + Assert.AreEqual(clone.Key, contentType.Key); + Assert.AreEqual(clone.Level, contentType.Level); + Assert.AreEqual(clone.Path, contentType.Path); + Assert.AreEqual(clone.SortOrder, contentType.SortOrder); + Assert.AreNotSame(clone.DefaultTemplate, contentType.DefaultTemplate); + Assert.AreEqual(clone.DefaultTemplate, contentType.DefaultTemplate); + Assert.AreEqual(clone.DefaultTemplateId, contentType.DefaultTemplateId); + Assert.AreEqual(clone.Trashed, contentType.Trashed); + Assert.AreEqual(clone.UpdateDate, contentType.UpdateDate); + Assert.AreEqual(clone.Thumbnail, contentType.Thumbnail); + Assert.AreEqual(clone.Icon, contentType.Icon); + Assert.AreEqual(clone.IsContainer, contentType.IsContainer); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(contentType, null)); + } + } + + [Test] + public void Can_Deep_Clone_Media_Type() + { + // Arrange + var contentType = MockedContentTypes.CreateImageMediaType(); + contentType.Id = 99; + + var i = 200; + foreach (var propertyType in contentType.PropertyTypes) + { + propertyType.Id = ++i; + } + contentType.Id = 10; + contentType.CreateDate = DateTime.Now; + contentType.CreatorId = 22; + contentType.Description = "test"; + contentType.Icon = "icon"; + contentType.IsContainer = true; + contentType.Thumbnail = "thumb"; + contentType.Key = Guid.NewGuid(); + contentType.Level = 3; + contentType.Path = "-1,4,10"; + contentType.SortOrder = 5; + contentType.Trashed = false; + contentType.UpdateDate = DateTime.Now; + + ((IUmbracoEntity)contentType).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)contentType).AdditionalData.Add("test2", "hello"); + + // Act + var clone = (MediaType)contentType.DeepClone(); + + // Assert + Assert.AreNotSame(clone, contentType); + Assert.AreEqual(clone, contentType); + Assert.AreEqual(clone.Id, contentType.Id); + Assert.AreEqual(((IUmbracoEntity)clone).AdditionalData, ((IUmbracoEntity)contentType).AdditionalData); + Assert.AreEqual(clone.PropertyGroups.Count, contentType.PropertyGroups.Count); + for (var index = 0; index < contentType.PropertyGroups.Count; index++) + { + Assert.AreNotSame(clone.PropertyGroups[index], contentType.PropertyGroups[index]); + Assert.AreEqual(clone.PropertyGroups[index], contentType.PropertyGroups[index]); + } + Assert.AreEqual(clone.PropertyTypes.Count(), contentType.PropertyTypes.Count()); + for (var index = 0; index < contentType.PropertyTypes.Count(); index++) + { + Assert.AreNotSame(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); + Assert.AreEqual(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); + } + Assert.AreEqual(clone.CreateDate, contentType.CreateDate); + Assert.AreEqual(clone.CreatorId, contentType.CreatorId); + Assert.AreEqual(clone.Key, contentType.Key); + Assert.AreEqual(clone.Level, contentType.Level); + Assert.AreEqual(clone.Path, contentType.Path); + Assert.AreEqual(clone.SortOrder, contentType.SortOrder); + Assert.AreEqual(clone.Trashed, contentType.Trashed); + Assert.AreEqual(clone.UpdateDate, contentType.UpdateDate); + Assert.AreEqual(clone.Thumbnail, contentType.Thumbnail); + Assert.AreEqual(clone.Icon, contentType.Icon); + Assert.AreEqual(clone.IsContainer, contentType.IsContainer); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(contentType, null)); + } + } + + [Test] + public void Can_Deep_Clone_Member_Type() + { + // Arrange + var contentType = MockedContentTypes.CreateSimpleMemberType(); + contentType.Id = 99; + + var i = 200; + foreach (var propertyType in contentType.PropertyTypes) + { + propertyType.Id = ++i; + } + contentType.Id = 10; + contentType.CreateDate = DateTime.Now; + contentType.CreatorId = 22; + contentType.Description = "test"; + contentType.Icon = "icon"; + contentType.IsContainer = true; + contentType.Thumbnail = "thumb"; + contentType.Key = Guid.NewGuid(); + contentType.Level = 3; + contentType.Path = "-1,4,10"; + contentType.SortOrder = 5; + contentType.Trashed = false; + contentType.UpdateDate = DateTime.Now; + contentType.SetMemberCanEditProperty("title", true); + contentType.SetMemberCanViewProperty("bodyText", true); + ((IUmbracoEntity)contentType).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)contentType).AdditionalData.Add("test2", "hello"); + + // Act + var clone = (MemberType)contentType.DeepClone(); + + // Assert + Assert.AreNotSame(clone, contentType); + Assert.AreEqual(clone, contentType); + Assert.AreEqual(clone.Id, contentType.Id); + Assert.AreEqual(((IUmbracoEntity)clone).AdditionalData, ((IUmbracoEntity)contentType).AdditionalData); + Assert.AreEqual(clone.PropertyGroups.Count, contentType.PropertyGroups.Count); + for (var index = 0; index < contentType.PropertyGroups.Count; index++) + { + Assert.AreNotSame(clone.PropertyGroups[index], contentType.PropertyGroups[index]); + Assert.AreEqual(clone.PropertyGroups[index], contentType.PropertyGroups[index]); + } + Assert.AreEqual(clone.PropertyTypes.Count(), contentType.PropertyTypes.Count()); + for (var index = 0; index < contentType.PropertyTypes.Count(); index++) + { + Assert.AreNotSame(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); + Assert.AreEqual(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); + } + Assert.AreEqual(clone.CreateDate, contentType.CreateDate); + Assert.AreEqual(clone.CreatorId, contentType.CreatorId); + Assert.AreEqual(clone.Key, contentType.Key); + Assert.AreEqual(clone.Level, contentType.Level); + Assert.AreEqual(clone.Path, contentType.Path); + Assert.AreEqual(clone.SortOrder, contentType.SortOrder); + Assert.AreEqual(clone.Trashed, contentType.Trashed); + Assert.AreEqual(clone.UpdateDate, contentType.UpdateDate); + Assert.AreEqual(clone.Thumbnail, contentType.Thumbnail); + Assert.AreEqual(clone.Icon, contentType.Icon); + Assert.AreEqual(clone.IsContainer, contentType.IsContainer); + Assert.AreEqual(clone.MemberTypePropertyTypes, contentType.MemberTypePropertyTypes); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(contentType, null)); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/PropertyGroupTests.cs b/src/Umbraco.Tests/Models/PropertyGroupTests.cs new file mode 100644 index 0000000000..3b3af26ab7 --- /dev/null +++ b/src/Umbraco.Tests/Models/PropertyGroupTests.cs @@ -0,0 +1,90 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class PropertyGroupTests + { + [Test] + public void Can_Deep_Clone() + { + var pg = new PropertyGroup( + new PropertyTypeCollection(new[] + { + new PropertyType(Guid.NewGuid(), DataTypeDatabaseType.Nvarchar) + { + Id = 3, + Alias = "test", + CreateDate = DateTime.Now, + DataTypeDefinitionId = 5, + DataTypeId = Guid.NewGuid(), + Description = "testing", + Key = Guid.NewGuid(), + Mandatory = true, + Name = "Test", + PropertyGroupId = new Lazy(() => 11), + SortOrder = 9, + UpdateDate = DateTime.Now, + ValidationRegExp = "xxxx", + DataTypeDatabaseType = DataTypeDatabaseType.Nvarchar + }, + new PropertyType(Guid.NewGuid(), DataTypeDatabaseType.Nvarchar) + { + Id = 4, + Alias = "test2", + CreateDate = DateTime.Now, + DataTypeDefinitionId = 6, + DataTypeId = Guid.NewGuid(), + Description = "testing2", + Key = Guid.NewGuid(), + Mandatory = true, + Name = "Test2", + PropertyGroupId = new Lazy(() => 12), + SortOrder = 10, + UpdateDate = DateTime.Now, + ValidationRegExp = "yyyy", + DataTypeDatabaseType = DataTypeDatabaseType.Nvarchar + } + })) + { + Id = 77, + CreateDate = DateTime.Now, + Key = Guid.NewGuid(), + Name = "Group1", + SortOrder = 555, + UpdateDate = DateTime.Now, + ParentId = 9 + }; + + var clone = (PropertyGroup)pg.DeepClone(); + + Assert.AreNotSame(clone, pg); + Assert.AreEqual(clone, pg); + Assert.AreEqual(clone.Id, pg.Id); + Assert.AreEqual(clone.CreateDate, pg.CreateDate); + Assert.AreEqual(clone.Key, pg.Key); + Assert.AreEqual(clone.Name, pg.Name); + Assert.AreEqual(clone.SortOrder, pg.SortOrder); + Assert.AreEqual(clone.UpdateDate, pg.UpdateDate); + Assert.AreEqual(clone.ParentId, pg.ParentId); + Assert.AreNotSame(clone.PropertyTypes, pg.PropertyTypes); + Assert.AreEqual(clone.PropertyTypes, pg.PropertyTypes); + Assert.AreEqual(clone.PropertyTypes.Count, pg.PropertyTypes.Count); + for (var i = 0; i < pg.PropertyTypes.Count; i++) + { + Assert.AreNotSame(clone.PropertyTypes[i], pg.PropertyTypes[i]); + Assert.AreEqual(clone.PropertyTypes[i], pg.PropertyTypes[i]); + } + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(pg, null)); + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/PropertyTypeTests.cs b/src/Umbraco.Tests/Models/PropertyTypeTests.cs new file mode 100644 index 0000000000..68ce1c5cfc --- /dev/null +++ b/src/Umbraco.Tests/Models/PropertyTypeTests.cs @@ -0,0 +1,59 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class PropertyTypeTests + { + [Test] + public void Can_Deep_Clone() + { + var pt = new PropertyType(Guid.NewGuid(), DataTypeDatabaseType.Nvarchar) + { + Id = 3, + Alias = "test", + CreateDate = DateTime.Now, + DataTypeDefinitionId = 5, + DataTypeId = Guid.NewGuid(), + Description = "testing", + Key = Guid.NewGuid(), + Mandatory = true, + Name = "Test", + PropertyGroupId = new Lazy(() => 11), + SortOrder = 9, + UpdateDate = DateTime.Now, + ValidationRegExp = "xxxx", + DataTypeDatabaseType = DataTypeDatabaseType.Nvarchar + }; + + var clone = (PropertyType)pt.DeepClone(); + + Assert.AreNotSame(clone, pt); + Assert.AreEqual(clone, pt); + Assert.AreEqual(clone.Id, pt.Id); + Assert.AreEqual(clone.Alias, pt.Alias); + Assert.AreEqual(clone.CreateDate, pt.CreateDate); + Assert.AreEqual(clone.DataTypeDefinitionId, pt.DataTypeDefinitionId); + Assert.AreEqual(clone.DataTypeId, pt.DataTypeId); + Assert.AreEqual(clone.Description, pt.Description); + Assert.AreEqual(clone.Key, pt.Key); + Assert.AreEqual(clone.Mandatory, pt.Mandatory); + Assert.AreEqual(clone.Name, pt.Name); + Assert.AreEqual(clone.PropertyGroupId.Value, pt.PropertyGroupId.Value); + Assert.AreEqual(clone.SortOrder, pt.SortOrder); + Assert.AreEqual(clone.UpdateDate, pt.UpdateDate); + Assert.AreEqual(clone.ValidationRegExp, pt.ValidationRegExp); + Assert.AreEqual(clone.DataTypeDatabaseType, pt.DataTypeDatabaseType); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(pt, null)); + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index a7e19aee06..14dc64f41a 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -1,4 +1,4 @@ - + Debug @@ -157,6 +157,9 @@ + + + From 0e4e12d0ba101f9635c02e66e571db37edb66b53 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 15 Apr 2014 19:12:42 +1000 Subject: [PATCH 04/25] More code and tests written for deep cloning. --- src/Umbraco.Core/Models/ContentTypeSort.cs | 22 ++ src/Umbraco.Core/Models/DictionaryItem.cs | 8 + .../Models/DictionaryTranslation.cs | 7 + src/Umbraco.Core/Models/Relation.cs | 7 + src/Umbraco.Core/Models/RelationType.cs | 1 + src/Umbraco.Core/Models/ServerRegistration.cs | 5 +- src/Umbraco.Core/Models/Task.cs | 7 + src/Umbraco.Core/Models/UmbracoEntity.cs | 41 +- src/Umbraco.Tests/Models/Collections/Item.cs | 253 ++++++++++++ .../Models/Collections/OrderItem.cs | 42 ++ .../Collections/PropertyCollectionTests.cs | 363 +----------------- .../Models/Collections/SimpleOrder.cs | 82 ++++ src/Umbraco.Tests/Models/ContentTypeTests.cs | 19 + .../Models/DataTypeDefinitionTests.cs | 54 +++ .../Models/DictionaryItemTests.cs | 82 ++++ .../Models/DictionaryTranslationTests.cs | 49 +++ src/Umbraco.Tests/Models/LanguageTests.cs | 41 ++ src/Umbraco.Tests/Models/RelationTests.cs | 48 +++ src/Umbraco.Tests/Models/RelationTypeTests.cs | 44 +++ src/Umbraco.Tests/Models/TaskTests.cs | 52 +++ src/Umbraco.Tests/Models/TaskTypeTests.cs | 42 ++ src/Umbraco.Tests/Models/TemplateTests.cs | 54 +++ .../Models/UmbracoEntityTests.cs | 79 +++- src/Umbraco.Tests/Umbraco.Tests.csproj | 12 + 24 files changed, 1048 insertions(+), 366 deletions(-) create mode 100644 src/Umbraco.Tests/Models/Collections/Item.cs create mode 100644 src/Umbraco.Tests/Models/Collections/OrderItem.cs create mode 100644 src/Umbraco.Tests/Models/Collections/SimpleOrder.cs create mode 100644 src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs create mode 100644 src/Umbraco.Tests/Models/DictionaryItemTests.cs create mode 100644 src/Umbraco.Tests/Models/DictionaryTranslationTests.cs create mode 100644 src/Umbraco.Tests/Models/LanguageTests.cs create mode 100644 src/Umbraco.Tests/Models/RelationTests.cs create mode 100644 src/Umbraco.Tests/Models/RelationTypeTests.cs create mode 100644 src/Umbraco.Tests/Models/TaskTests.cs create mode 100644 src/Umbraco.Tests/Models/TaskTypeTests.cs create mode 100644 src/Umbraco.Tests/Models/TemplateTests.cs diff --git a/src/Umbraco.Core/Models/ContentTypeSort.cs b/src/Umbraco.Core/Models/ContentTypeSort.cs index a52b5ae0f1..5aa81d9db0 100644 --- a/src/Umbraco.Core/Models/ContentTypeSort.cs +++ b/src/Umbraco.Core/Models/ContentTypeSort.cs @@ -42,5 +42,27 @@ namespace Umbraco.Core.Models clone.Id = new Lazy(() => id); return clone; } + + protected bool Equals(ContentTypeSort other) + { + return Id.Value.Equals(other.Id.Value) && string.Equals(Alias, other.Alias); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((ContentTypeSort) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (Id.GetHashCode()*397) ^ Alias.GetHashCode(); + } + } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index c670a75108..b29e618f83 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Runtime.Serialization; using Umbraco.Core.Models.EntityBase; @@ -96,5 +97,12 @@ namespace Umbraco.Core.Models if(ParentId == Guid.Empty) _parentId = new Guid("41c7638d-f529-4bff-853e-59a0c2fb1bde"); } + + public override object DeepClone() + { + var clone = (DictionaryItem)base.DeepClone(); + clone.Translations = Translations.Select(x => (IDictionaryTranslation) x.DeepClone()).ToList(); + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/DictionaryTranslation.cs b/src/Umbraco.Core/Models/DictionaryTranslation.cs index 5f8f5ebc25..b815ab8c5b 100644 --- a/src/Umbraco.Core/Models/DictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/DictionaryTranslation.cs @@ -65,5 +65,12 @@ namespace Umbraco.Core.Models }, _value, ValueSelector); } } + + public override object DeepClone() + { + var clone = (DictionaryTranslation)base.DeepClone(); + clone.Language = (ILanguage)Language.DeepClone(); + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Relation.cs b/src/Umbraco.Core/Models/Relation.cs index ec7c3741a6..2eef36cf73 100644 --- a/src/Umbraco.Core/Models/Relation.cs +++ b/src/Umbraco.Core/Models/Relation.cs @@ -107,5 +107,12 @@ namespace Umbraco.Core.Models { get { return _relationType.Id; } } + + public override object DeepClone() + { + var clone = (Relation)base.DeepClone(); + clone.RelationType = (RelationType)RelationType.DeepClone(); + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/RelationType.cs b/src/Umbraco.Core/Models/RelationType.cs index e1f5b4a388..dfe84659de 100644 --- a/src/Umbraco.Core/Models/RelationType.cs +++ b/src/Umbraco.Core/Models/RelationType.cs @@ -118,5 +118,6 @@ namespace Umbraco.Core.Models }, _childObjectType, ChildObjectTypeSelector); } } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/ServerRegistration.cs b/src/Umbraco.Core/Models/ServerRegistration.cs index dd352c2c86..7f43f5dfd2 100644 --- a/src/Umbraco.Core/Models/ServerRegistration.cs +++ b/src/Umbraco.Core/Models/ServerRegistration.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Reflection; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.Mappers; @@ -34,7 +35,7 @@ namespace Umbraco.Core.Models { UpdateDate = updateDate; CreateDate = createDate; - Key = Id.ToString().EncodeAsGuid(); + Key = Id.ToString(CultureInfo.InvariantCulture).EncodeAsGuid(); Id = id; ServerAddress = serverAddress; ComputerName = computerName; @@ -51,7 +52,7 @@ namespace Umbraco.Core.Models { CreateDate = createDate; UpdateDate = createDate; - Key = 0.ToString().EncodeAsGuid(); + Key = 0.ToString(CultureInfo.InvariantCulture).EncodeAsGuid(); ServerAddress = serverAddress; ComputerName = computerName; } diff --git a/src/Umbraco.Core/Models/Task.cs b/src/Umbraco.Core/Models/Task.cs index 86370c00bb..9a4b9eaa71 100644 --- a/src/Umbraco.Core/Models/Task.cs +++ b/src/Umbraco.Core/Models/Task.cs @@ -132,5 +132,12 @@ namespace Umbraco.Core.Models }, _comment, CommentSelector); } } + + public override object DeepClone() + { + var clone = (Task) base.DeepClone(); + clone.TaskType = (TaskType)TaskType.DeepClone(); + return clone; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs index 6822582576..347f6be5c7 100644 --- a/src/Umbraco.Core/Models/UmbracoEntity.cs +++ b/src/Umbraco.Core/Models/UmbracoEntity.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Reflection; using Umbraco.Core.Models.EntityBase; @@ -46,17 +47,20 @@ namespace Umbraco.Core.Models public UmbracoEntity() { AdditionalData = new Dictionary(); + UmbracoProperties = new List(); } public UmbracoEntity(bool trashed) { AdditionalData = new Dictionary(); + UmbracoProperties = new List(); Trashed = trashed; } public UmbracoEntity(int trashed) { AdditionalData = new Dictionary(); + UmbracoProperties = new List(); Trashed = trashed == 1; } @@ -287,10 +291,45 @@ namespace Umbraco.Core.Models /// public IList UmbracoProperties { get; set; } - internal class UmbracoProperty + public override object DeepClone() + { + var clone = (UmbracoEntity)base.DeepClone(); + clone.UmbracoProperties = UmbracoProperties.Select(x => (UmbracoProperty) x.DeepClone()).ToList(); + return clone; + } + + internal class UmbracoProperty : IDeepCloneable { public Guid DataTypeControlId { get; set; } public string Value { get; set; } + public object DeepClone() + { + //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(); + return clone; + } + + protected bool Equals(UmbracoProperty other) + { + return DataTypeControlId.Equals(other.DataTypeControlId) && string.Equals(Value, other.Value); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((UmbracoProperty) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (DataTypeControlId.GetHashCode()*397) ^ (Value != null ? Value.GetHashCode() : 0); + } + } } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/Collections/Item.cs b/src/Umbraco.Tests/Models/Collections/Item.cs new file mode 100644 index 0000000000..ff1789a850 --- /dev/null +++ b/src/Umbraco.Tests/Models/Collections/Item.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using Umbraco.Core; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Tests.Models.Collections +{ + public abstract class Item : IEntity, ICanBeDirty + { + private bool _hasIdentity; + private int? _hash; + private int _id; + private Guid _key; + + protected Item() + { + _propertyChangedInfo = new Dictionary(); + } + + /// + /// Integer Id + /// + [DataMember] + public int Id + { + get + { + return _id; + } + set + { + _id = value; + HasIdentity = true; + } + } + + /// + /// Guid based Id + /// + /// The key is currectly used to store the Unique Id from the + /// umbracoNode table, which many of the entities are based on. + [DataMember] + public Guid Key + { + get + { + if (_key == Guid.Empty) + return _id.ToGuid(); + + return _key; + } + set { _key = value; } + } + + /// + /// Gets or sets the Created Date + /// + [DataMember] + public DateTime CreateDate { get; set; } + + /// + /// Gets or sets the Modified Date + /// + [DataMember] + public DateTime UpdateDate { get; set; } + + /// + /// Gets or sets the WasCancelled flag, which is used to track + /// whether some action against an entity was cancelled through some event. + /// This only exists so we have a way to check if an event was cancelled through + /// the new api, which also needs to take effect in the legacy api. + /// + [IgnoreDataMember] + internal bool WasCancelled { get; set; } + + /// + /// Property changed event + /// + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Method to call on a property setter. + /// + /// The property info. + protected virtual void OnPropertyChanged(PropertyInfo propertyInfo) + { + _propertyChangedInfo[propertyInfo.Name] = true; + + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyInfo.Name)); + } + } + + internal virtual void ResetIdentity() + { + _hasIdentity = false; + _id = default(int); + } + + /// + /// Method to call on entity saved when first added + /// + internal virtual void AddingEntity() + { + CreateDate = DateTime.Now; + UpdateDate = DateTime.Now; + } + + /// + /// Method to call on entity saved/updated + /// + internal virtual void UpdatingEntity() + { + UpdateDate = DateTime.Now; + } + + /// + /// Tracks the properties that have changed + /// + //private readonly IDictionary _propertyChangedInfo = new Dictionary(); + private IDictionary _propertyChangedInfo; + + /// + /// Indicates whether a specific property on the current entity is dirty. + /// + /// Name of the property to check + /// True if Property is dirty, otherwise False + public virtual bool IsPropertyDirty(string propertyName) + { + return _propertyChangedInfo.Any(x => x.Key == propertyName); + } + + /// + /// Indicates whether the current entity is dirty. + /// + /// True if entity is dirty, otherwise False + public virtual bool IsDirty() + { + return _propertyChangedInfo.Any(); + } + + /// + /// Resets dirty properties by clearing the dictionary used to track changes. + /// + /// + /// Please note that resetting the dirty properties could potentially + /// obstruct the saving of a new or updated entity. + /// + public virtual void ResetDirtyProperties() + { + _propertyChangedInfo.Clear(); + } + + /// + /// Indicates whether the current entity has an identity, eg. Id. + /// + public virtual bool HasIdentity + { + get + { + return _hasIdentity; + } + protected set + { + _hasIdentity = value; + } + } + + public static bool operator ==(Item left, Item right) + { + /*if (ReferenceEquals(null, left)) + return false; + + if(ReferenceEquals(null, right)) + return false;*/ + + return ReferenceEquals(left, right); + + return left.Equals(right); + } + + public static bool operator !=(Item left, Item right) + { + return !(left == right); + } + + /*public virtual bool SameIdentityAs(IEntity other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return SameIdentityAs(other as Entity); + } + + public virtual bool Equals(Entity other) + { + if (ReferenceEquals(null, other)) + return false; + if (ReferenceEquals(this, other)) + return true; + + return SameIdentityAs(other); + } + + public virtual Type GetRealType() + { + return GetType(); + } + + public virtual bool SameIdentityAs(Entity other) + { + if (ReferenceEquals(null, other)) + return false; + + if (ReferenceEquals(this, other)) + return true; + + if (GetType() == other.GetRealType() && HasIdentity && other.HasIdentity) + return other.Id.Equals(Id); + + return false; + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + return false; + if (ReferenceEquals(this, obj)) + return true; + + return SameIdentityAs(obj as IEntity); + } + + public override int GetHashCode() + { + if (!_hash.HasValue) + _hash = !HasIdentity ? new int?(base.GetHashCode()) : new int?(Id.GetHashCode() * 397 ^ GetType().GetHashCode()); + return _hash.Value; + }*/ + + public object DeepClone() + { + return this.MemberwiseClone(); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/Collections/OrderItem.cs b/src/Umbraco.Tests/Models/Collections/OrderItem.cs new file mode 100644 index 0000000000..81c770db92 --- /dev/null +++ b/src/Umbraco.Tests/Models/Collections/OrderItem.cs @@ -0,0 +1,42 @@ +using System; + +namespace Umbraco.Tests.Models.Collections +{ + public class OrderItem : Item + { + public readonly int PartNumber; + public readonly string Description; + public readonly double UnitPrice; + + private int _quantity = 0; + + public OrderItem(int partNumber, string description, + int quantity, double unitPrice) + { + this.PartNumber = partNumber; + this.Description = description; + this.Quantity = quantity; + this.UnitPrice = unitPrice; + } + + public int Quantity + { + get { return _quantity; } + set + { + if (value < 0) + throw new ArgumentException("Quantity cannot be negative."); + + _quantity = value; + } + } + + public override string ToString() + { + return String.Format( + "{0,9} {1,6} {2,-12} at {3,8:#,###.00} = {4,10:###,###.00}", + PartNumber, _quantity, Description, UnitPrice, + UnitPrice * _quantity); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs b/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs index 928511e407..c0ab5b7f7d 100644 --- a/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs +++ b/src/Umbraco.Tests/Models/Collections/PropertyCollectionTests.cs @@ -1,15 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; -using System.ComponentModel; +using System.Collections.Generic; using System.Linq; -using System.Reflection; -using System.Runtime.Serialization; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.Models.EntityBase; using Umbraco.Tests.TestHelpers.Entities; namespace Umbraco.Tests.Models.Collections @@ -71,358 +64,4 @@ namespace Umbraco.Tests.Models.Collections Assert.That(contentType.PropertyGroups.Any(x => x.Name.InvariantEquals("Test")), Is.False); } } - - public class SimpleOrder : KeyedCollection, INotifyCollectionChanged - { - // The parameterless constructor of the base class creates a - // KeyedCollection with an internal dictionary. For this code - // example, no other constructors are exposed. - // - public SimpleOrder() : base() { } - - public SimpleOrder(IEnumerable properties) - { - Reset(properties); - } - - // This is the only method that absolutely must be overridden, - // because without it the KeyedCollection cannot extract the - // keys from the items. The input parameter type is the - // second generic type argument, in this case OrderItem, and - // the return value type is the first generic type argument, - // in this case int. - // - protected override int GetKeyForItem(OrderItem item) - { - // In this example, the key is the part number. - return item.PartNumber; - } - - internal void Reset(IEnumerable properties) - { - Clear(); - properties.ForEach(Add); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - - protected override void SetItem(int index, OrderItem item) - { - base.SetItem(index, item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); - } - - protected override void RemoveItem(int index) - { - var removed = this[index]; - base.RemoveItem(index); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); - } - - protected override void InsertItem(int index, OrderItem item) - { - base.InsertItem(index, item); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); - } - - protected override void ClearItems() - { - base.ClearItems(); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - } - - public new bool Contains(int partNumber) - { - return this.Any(x => x.PartNumber == partNumber); - } - - public event NotifyCollectionChangedEventHandler CollectionChanged; - - protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) - { - if (CollectionChanged != null) - { - CollectionChanged(this, args); - } - } - } - - public class OrderItem : Item - { - public readonly int PartNumber; - public readonly string Description; - public readonly double UnitPrice; - - private int _quantity = 0; - - public OrderItem(int partNumber, string description, - int quantity, double unitPrice) - { - this.PartNumber = partNumber; - this.Description = description; - this.Quantity = quantity; - this.UnitPrice = unitPrice; - } - - public int Quantity - { - get { return _quantity; } - set - { - if (value < 0) - throw new ArgumentException("Quantity cannot be negative."); - - _quantity = value; - } - } - - public override string ToString() - { - return String.Format( - "{0,9} {1,6} {2,-12} at {3,8:#,###.00} = {4,10:###,###.00}", - PartNumber, _quantity, Description, UnitPrice, - UnitPrice * _quantity); - } - } - - public abstract class Item : IEntity, ICanBeDirty - { - private bool _hasIdentity; - private int? _hash; - private int _id; - private Guid _key; - - protected Item() - { - _propertyChangedInfo = new Dictionary(); - } - - /// - /// Integer Id - /// - [DataMember] - public int Id - { - get - { - return _id; - } - set - { - _id = value; - HasIdentity = true; - } - } - - /// - /// Guid based Id - /// - /// The key is currectly used to store the Unique Id from the - /// umbracoNode table, which many of the entities are based on. - [DataMember] - public Guid Key - { - get - { - if (_key == Guid.Empty) - return _id.ToGuid(); - - return _key; - } - set { _key = value; } - } - - /// - /// Gets or sets the Created Date - /// - [DataMember] - public DateTime CreateDate { get; set; } - - /// - /// Gets or sets the Modified Date - /// - [DataMember] - public DateTime UpdateDate { get; set; } - - /// - /// Gets or sets the WasCancelled flag, which is used to track - /// whether some action against an entity was cancelled through some event. - /// This only exists so we have a way to check if an event was cancelled through - /// the new api, which also needs to take effect in the legacy api. - /// - [IgnoreDataMember] - internal bool WasCancelled { get; set; } - - /// - /// Property changed event - /// - public event PropertyChangedEventHandler PropertyChanged; - - /// - /// Method to call on a property setter. - /// - /// The property info. - protected virtual void OnPropertyChanged(PropertyInfo propertyInfo) - { - _propertyChangedInfo[propertyInfo.Name] = true; - - if (PropertyChanged != null) - { - PropertyChanged(this, new PropertyChangedEventArgs(propertyInfo.Name)); - } - } - - internal virtual void ResetIdentity() - { - _hasIdentity = false; - _id = default(int); - } - - /// - /// Method to call on entity saved when first added - /// - internal virtual void AddingEntity() - { - CreateDate = DateTime.Now; - UpdateDate = DateTime.Now; - } - - /// - /// Method to call on entity saved/updated - /// - internal virtual void UpdatingEntity() - { - UpdateDate = DateTime.Now; - } - - /// - /// Tracks the properties that have changed - /// - //private readonly IDictionary _propertyChangedInfo = new Dictionary(); - private IDictionary _propertyChangedInfo; - - /// - /// Indicates whether a specific property on the current entity is dirty. - /// - /// Name of the property to check - /// True if Property is dirty, otherwise False - public virtual bool IsPropertyDirty(string propertyName) - { - return _propertyChangedInfo.Any(x => x.Key == propertyName); - } - - /// - /// Indicates whether the current entity is dirty. - /// - /// True if entity is dirty, otherwise False - public virtual bool IsDirty() - { - return _propertyChangedInfo.Any(); - } - - /// - /// Resets dirty properties by clearing the dictionary used to track changes. - /// - /// - /// Please note that resetting the dirty properties could potentially - /// obstruct the saving of a new or updated entity. - /// - public virtual void ResetDirtyProperties() - { - _propertyChangedInfo.Clear(); - } - - /// - /// Indicates whether the current entity has an identity, eg. Id. - /// - public virtual bool HasIdentity - { - get - { - return _hasIdentity; - } - protected set - { - _hasIdentity = value; - } - } - - public static bool operator ==(Item left, Item right) - { - /*if (ReferenceEquals(null, left)) - return false; - - if(ReferenceEquals(null, right)) - return false;*/ - - return ReferenceEquals(left, right); - - return left.Equals(right); - } - - public static bool operator !=(Item left, Item right) - { - return !(left == right); - } - - /*public virtual bool SameIdentityAs(IEntity other) - { - if (ReferenceEquals(null, other)) - return false; - if (ReferenceEquals(this, other)) - return true; - - return SameIdentityAs(other as Entity); - } - - public virtual bool Equals(Entity other) - { - if (ReferenceEquals(null, other)) - return false; - if (ReferenceEquals(this, other)) - return true; - - return SameIdentityAs(other); - } - - public virtual Type GetRealType() - { - return GetType(); - } - - public virtual bool SameIdentityAs(Entity other) - { - if (ReferenceEquals(null, other)) - return false; - - if (ReferenceEquals(this, other)) - return true; - - if (GetType() == other.GetRealType() && HasIdentity && other.HasIdentity) - return other.Id.Equals(Id); - - return false; - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - return false; - if (ReferenceEquals(this, obj)) - return true; - - return SameIdentityAs(obj as IEntity); - } - - public override int GetHashCode() - { - if (!_hash.HasValue) - _hash = !HasIdentity ? new int?(base.GetHashCode()) : new int?(Id.GetHashCode() * 397 ^ GetType().GetHashCode()); - return _hash.Value; - }*/ - - public object DeepClone() - { - return this.MemberwiseClone(); - } - } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/Collections/SimpleOrder.cs b/src/Umbraco.Tests/Models/Collections/SimpleOrder.cs new file mode 100644 index 0000000000..82df104b64 --- /dev/null +++ b/src/Umbraco.Tests/Models/Collections/SimpleOrder.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Linq; +using Umbraco.Core; + +namespace Umbraco.Tests.Models.Collections +{ + public class SimpleOrder : KeyedCollection, INotifyCollectionChanged + { + // The parameterless constructor of the base class creates a + // KeyedCollection with an internal dictionary. For this code + // example, no other constructors are exposed. + // + public SimpleOrder() : base() { } + + public SimpleOrder(IEnumerable properties) + { + Reset(properties); + } + + // This is the only method that absolutely must be overridden, + // because without it the KeyedCollection cannot extract the + // keys from the items. The input parameter type is the + // second generic type argument, in this case OrderItem, and + // the return value type is the first generic type argument, + // in this case int. + // + protected override int GetKeyForItem(OrderItem item) + { + // In this example, the key is the part number. + return item.PartNumber; + } + + internal void Reset(IEnumerable properties) + { + Clear(); + properties.ForEach(Add); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + protected override void SetItem(int index, OrderItem item) + { + base.SetItem(index, item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index)); + } + + protected override void RemoveItem(int index) + { + var removed = this[index]; + base.RemoveItem(index); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); + } + + protected override void InsertItem(int index, OrderItem item) + { + base.InsertItem(index, item); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item)); + } + + protected override void ClearItems() + { + base.ClearItems(); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); + } + + public new bool Contains(int partNumber) + { + return this.Any(x => x.PartNumber == partNumber); + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs args) + { + if (CollectionChanged != null) + { + CollectionChanged(this, args); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/ContentTypeTests.cs b/src/Umbraco.Tests/Models/ContentTypeTests.cs index 71f03614e1..9f2e97bbc9 100644 --- a/src/Umbraco.Tests/Models/ContentTypeTests.cs +++ b/src/Umbraco.Tests/Models/ContentTypeTests.cs @@ -10,6 +10,25 @@ namespace Umbraco.Tests.Models [TestFixture] public class ContentTypeTests { + [Test] + public void Can_Deep_Clone_Content_Type_Sort() + { + var contentType = new ContentTypeSort(new Lazy(() => 3), 4, "test"); + var clone = (ContentTypeSort) contentType.DeepClone(); + Assert.AreNotSame(clone, contentType); + Assert.AreEqual(clone, contentType); + Assert.AreEqual(clone.Id.Value, contentType.Id.Value); + Assert.AreEqual(clone.SortOrder, contentType.SortOrder); + Assert.AreEqual(clone.Alias, contentType.Alias); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(contentType, null)); + } + } + [Test] public void Can_Deep_Clone_Content_Type() { diff --git a/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs b/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs new file mode 100644 index 0000000000..e731b3610d --- /dev/null +++ b/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class DataTypeDefinitionTests + { + [Test] + public void Can_Deep_Clone() + { + var dtd = new DataTypeDefinition(9, Guid.NewGuid()) + { + CreateDate = DateTime.Now, + CreatorId = 5, + DatabaseType = DataTypeDatabaseType.Nvarchar, + Id = 4, + Key = Guid.NewGuid(), + Level = 7, + Name = "Test", + ParentId = 9, + Path = "-1,2", + SortOrder = 8, + Trashed = true, + UpdateDate = DateTime.Now + }; + var clone = (DataTypeDefinition) dtd.DeepClone(); + + Assert.AreNotSame(clone, dtd); + Assert.AreEqual(clone, dtd); + Assert.AreEqual(clone.CreateDate, dtd.CreateDate); + Assert.AreEqual(clone.CreatorId, dtd.CreatorId); + Assert.AreEqual(clone.DatabaseType, dtd.DatabaseType); + Assert.AreEqual(clone.Id, dtd.Id); + Assert.AreEqual(clone.Key, dtd.Key); + Assert.AreEqual(clone.Level, dtd.Level); + Assert.AreEqual(clone.Name, dtd.Name); + Assert.AreEqual(clone.ParentId, dtd.ParentId); + Assert.AreEqual(clone.Path, dtd.Path); + Assert.AreEqual(clone.SortOrder, dtd.SortOrder); + Assert.AreEqual(clone.Trashed, dtd.Trashed); + Assert.AreEqual(clone.UpdateDate, dtd.UpdateDate); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(dtd, null)); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/DictionaryItemTests.cs b/src/Umbraco.Tests/Models/DictionaryItemTests.cs new file mode 100644 index 0000000000..32fa2f127e --- /dev/null +++ b/src/Umbraco.Tests/Models/DictionaryItemTests.cs @@ -0,0 +1,82 @@ +using System; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class DictionaryItemTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new DictionaryItem("blah") + { + CreateDate = DateTime.Now, + Id = 8, + ItemKey = "blah", + Key = Guid.NewGuid(), + ParentId = Guid.NewGuid(), + UpdateDate = DateTime.Now, + Translations = new[] + { + new DictionaryTranslation(new Language("en-AU") + { + CreateDate = DateTime.Now, + CultureName = "en", + Id = 11, + IsoCode = "AU", + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, "colour") + { + CreateDate = DateTime.Now, + Id = 88, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, + new DictionaryTranslation(new Language("en-US") + { + CreateDate = DateTime.Now, + CultureName = "en", + Id = 12, + IsoCode = "US", + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, "color") + { + CreateDate = DateTime.Now, + Id = 89, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, + } + }; + + var clone = (DictionaryItem)item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.ItemKey, item.ItemKey); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.ParentId, item.ParentId); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + Assert.AreEqual(clone.Translations.Count(), item.Translations.Count()); + for (var i = 0; i < item.Translations.Count(); i++) + { + Assert.AreNotSame(clone.Translations.ElementAt(i), item.Translations.ElementAt(i)); + Assert.AreEqual(clone.Translations.ElementAt(i), item.Translations.ElementAt(i)); + } + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs b/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs new file mode 100644 index 0000000000..2c585989c4 --- /dev/null +++ b/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs @@ -0,0 +1,49 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class DictionaryTranslationTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new DictionaryTranslation(new Language("en-AU") + { + CreateDate = DateTime.Now, + CultureName = "en", + Id = 11, + IsoCode = "AU", + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, "colour") + { + CreateDate = DateTime.Now, + Id = 88, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }; + + var clone = (DictionaryTranslation) item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + Assert.AreNotSame(clone.Language, item.Language); + Assert.AreEqual(clone.Language, item.Language); + Assert.AreEqual(clone.Value, item.Value); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/LanguageTests.cs b/src/Umbraco.Tests/Models/LanguageTests.cs new file mode 100644 index 0000000000..0637aa1b99 --- /dev/null +++ b/src/Umbraco.Tests/Models/LanguageTests.cs @@ -0,0 +1,41 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class LanguageTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new Language("en-AU") + { + CreateDate = DateTime.Now, + CultureName = "en", + Id = 11, + IsoCode = "AU", + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }; + + var clone = (Language) item.DeepClone(); + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.CultureName, item.CultureName); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.IsoCode, item.IsoCode); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/RelationTests.cs b/src/Umbraco.Tests/Models/RelationTests.cs new file mode 100644 index 0000000000..3293bd76cf --- /dev/null +++ b/src/Umbraco.Tests/Models/RelationTests.cs @@ -0,0 +1,48 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class RelationTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new Relation(9, 8, new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test") + { + Id = 66 + }) + { + Comment = "test comment", + CreateDate = DateTime.Now, + Id = 4, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }; + + var clone = (Relation) item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.ChildId, item.ChildId); + Assert.AreEqual(clone.Comment, item.Comment); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.ParentId, item.ParentId); + Assert.AreNotSame(clone.RelationType, item.RelationType); + Assert.AreEqual(clone.RelationType, item.RelationType); + Assert.AreEqual(clone.RelationTypeId, item.RelationTypeId); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/RelationTypeTests.cs b/src/Umbraco.Tests/Models/RelationTypeTests.cs new file mode 100644 index 0000000000..69850bcb51 --- /dev/null +++ b/src/Umbraco.Tests/Models/RelationTypeTests.cs @@ -0,0 +1,44 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class RelationTypeTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test") + { + Id = 66, + CreateDate = DateTime.Now, + IsBidirectional = true, + Key = Guid.NewGuid(), + Name = "Test", + UpdateDate = DateTime.Now + }; + + var clone = (RelationType)item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.Alias, item.Alias); + Assert.AreEqual(clone.ChildObjectType, item.ChildObjectType); + Assert.AreEqual(clone.IsBidirectional, item.IsBidirectional); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.Name, item.Name); + Assert.AreNotSame(clone.ParentObjectType, item.ParentObjectType); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/TaskTests.cs b/src/Umbraco.Tests/Models/TaskTests.cs new file mode 100644 index 0000000000..8518697f24 --- /dev/null +++ b/src/Umbraco.Tests/Models/TaskTests.cs @@ -0,0 +1,52 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class TaskTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new Task(new TaskType("test") {Id = 3}) + { + AssigneeUserId = 4, + Closed = true, + Comment = "blah", + CreateDate = DateTime.Now, + EntityId = 99, + Id = 2, + Key = Guid.NewGuid(), + OwnerUserId = 89, + TaskType = new TaskType("asdf") {Id = 99}, + UpdateDate = DateTime.Now + }; + + var clone = (Task) item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + Assert.AreEqual(clone.AssigneeUserId, item.AssigneeUserId); + Assert.AreEqual(clone.Closed, item.Closed); + Assert.AreEqual(clone.Comment, item.Comment); + Assert.AreEqual(clone.EntityId, item.EntityId); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.OwnerUserId, item.OwnerUserId); + Assert.AreNotSame(clone.TaskType, item.TaskType); + Assert.AreEqual(clone.TaskType, item.TaskType); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/TaskTypeTests.cs b/src/Umbraco.Tests/Models/TaskTypeTests.cs new file mode 100644 index 0000000000..65f6a8ef34 --- /dev/null +++ b/src/Umbraco.Tests/Models/TaskTypeTests.cs @@ -0,0 +1,42 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class TaskTypeTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new TaskType("test") + { + Id = 3, + Alias = "test", + CreateDate = DateTime.Now, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }; + + var clone = (TaskType)item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.Alias, item.Alias); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/TemplateTests.cs b/src/Umbraco.Tests/Models/TemplateTests.cs new file mode 100644 index 0000000000..12752a8efa --- /dev/null +++ b/src/Umbraco.Tests/Models/TemplateTests.cs @@ -0,0 +1,54 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class TemplateTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new Template("-1,2,3", "Test", "test") + { + Id = 3, + CreateDate = DateTime.Now, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + Content = "blah", + CreatorId = 66, + Level = 55, + ParentId = 2, + SortOrder = 99, + MasterTemplateAlias = "master", + MasterTemplateId = new Lazy(() => 88) + }; + + var clone = (Template)item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.Alias, item.Alias); + Assert.AreEqual(clone.CreatorId, item.CreatorId); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.Level, item.Level); + Assert.AreEqual(clone.MasterTemplateAlias, item.MasterTemplateAlias); + Assert.AreEqual(clone.MasterTemplateId.Value, item.MasterTemplateId.Value); + Assert.AreEqual(clone.Name, item.Name); + Assert.AreEqual(clone.ParentId, item.ParentId); + Assert.AreEqual(clone.SortOrder, item.SortOrder); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs index 2be59c7b07..789dee709c 100644 --- a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs +++ b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using System; +using NUnit.Framework; using Umbraco.Core.Models; namespace Umbraco.Tests.Models @@ -18,5 +19,81 @@ namespace Umbraco.Tests.Models Assert.IsTrue(trashedWithBool.Trashed); Assert.IsTrue(trashedWithInt.Trashed); } + + [Test] + public void Can_Deep_Clone() + { + var item = new UmbracoEntity() + { + Id = 3, + ContentTypeAlias = "test1", + CreatorId = 4, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + CreateDate = DateTime.Now, + Name = "Test", + ParentId = 5, + SortOrder = 6, + Path = "-1,23", + Level = 7, + ContentTypeIcon = "icon", + ContentTypeThumbnail = "thumb", + HasChildren = true, + HasPendingChanges = true, + IsDraft = true, + IsPublished = true, + NodeObjectTypeId = Guid.NewGuid() + }; + item.AdditionalData.Add("test1", 3); + item.AdditionalData.Add("test2", "valuie"); + item.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty() + { + Value = "test", + DataTypeControlId = Guid.NewGuid() + }); + item.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty() + { + Value = "test2", + DataTypeControlId = Guid.NewGuid() + }); + + var clone = (UmbracoEntity)item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.ContentTypeAlias, item.ContentTypeAlias); + Assert.AreEqual(clone.CreatorId, item.CreatorId); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.Level, item.Level); + Assert.AreEqual(clone.Name, item.Name); + Assert.AreEqual(clone.ParentId, item.ParentId); + Assert.AreEqual(clone.SortOrder, item.SortOrder); + Assert.AreEqual(clone.Path, item.Path); + Assert.AreEqual(clone.ContentTypeIcon, item.ContentTypeIcon); + Assert.AreEqual(clone.ContentTypeThumbnail, item.ContentTypeThumbnail); + Assert.AreEqual(clone.HasChildren, item.HasChildren); + Assert.AreEqual(clone.HasPendingChanges, item.HasPendingChanges); + Assert.AreEqual(clone.IsDraft, item.IsDraft); + Assert.AreEqual(clone.IsPublished, item.IsPublished); + Assert.AreEqual(clone.NodeObjectTypeId, item.NodeObjectTypeId); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + Assert.AreEqual(clone.AdditionalData.Count, item.AdditionalData.Count); + Assert.AreEqual(clone.AdditionalData, item.AdditionalData); + Assert.AreEqual(clone.UmbracoProperties.Count, item.UmbracoProperties.Count); + for (var i = 0; i < clone.UmbracoProperties.Count; i++) + { + Assert.AreNotSame(clone.UmbracoProperties[i], item.UmbracoProperties[i]); + Assert.AreEqual(clone.UmbracoProperties[i], item.UmbracoProperties[i]); + } + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 14dc64f41a..38e41ffe68 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -157,9 +157,21 @@ + + + + + + + + + + + + From dcac5d47099f51939f0c4bfdae252c6cd2d4ae7a Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 15 Apr 2014 20:31:32 +1000 Subject: [PATCH 05/25] More code and tests written for deep cloning. --- src/Umbraco.Core/Models/Membership/User.cs | 25 ++++++ .../Models/UmbracoEntityTests.cs | 83 +++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 558b3156de..073423ff4e 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -438,6 +438,13 @@ namespace Umbraco.Core.Models.Membership } } + public override object DeepClone() + { + var clone = (User)base.DeepClone(); + clone.UserType = (UserType)UserType.DeepClone(); + return clone; + } + /// /// Internal class used to wrap the user in a profile /// @@ -461,6 +468,24 @@ namespace Umbraco.Core.Models.Membership get { return _user.Name; } set { _user.Name = value; } } + + protected bool Equals(UserProfile other) + { + return _user.Equals(other._user); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((UserProfile) obj); + } + + public override int GetHashCode() + { + return _user.GetHashCode(); + } } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs index 789dee709c..13a590ad3a 100644 --- a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs +++ b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs @@ -1,9 +1,92 @@ using System; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Models.Membership; namespace Umbraco.Tests.Models { + [TestFixture] + public class UserTypeTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new UserType() + { + Id = 3, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + CreateDate = DateTime.Now, + Name = "Test", + Alias = "test", + Permissions = new[] {"a", "b", "c"} + }; + + var clone = (User)item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + + //Verify normal properties with reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + } + + [TestFixture] + public class UserTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new User(new UserType(){Id = 3}) + { + Id = 3, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + CreateDate = DateTime.Now, + Name = "Test", + Comments = "comments", + DefaultPermissions = new[]{"a","b","c"}, + DefaultToLiveEditing = false, + Email = "test@test.com", + Language = "en", + FailedPasswordAttempts = 3, + IsApproved = true, + IsLockedOut = true, + LastLockoutDate = DateTime.Now, + LastLoginDate = DateTime.Now, + LastPasswordChangeDate = DateTime.Now, + Password = "test pass", + PasswordAnswer = "answer", + PasswordQuestion = "question", + //ProviderUserKey = "user key", + SessionTimeout = 5, + StartContentId = 3, + StartMediaId = 8, + Username = "username" + }; + + var clone = (User)item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + + Assert.AreNotSame(clone.UserType, item.UserType); + Assert.AreEqual(clone.UserType, item.UserType); + + //Verify normal properties with reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + } + [TestFixture] public class UmbracoEntityTests { From bb22f5a28395828bf3e5bce9cfd1ac51ea76bae0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Apr 2014 11:23:00 +1000 Subject: [PATCH 06/25] More code and tests written for deep cloning. --- .../Models/{EntityBase => }/IDeepCloneable.cs | 2 +- src/Umbraco.Core/Umbraco.Core.csproj | 2 +- .../Models/UmbracoEntityTests.cs | 83 ------------------- src/Umbraco.Tests/Models/UserTests.cs | 57 +++++++++++++ src/Umbraco.Tests/Models/UserTypeTests.cs | 37 +++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 2 + 6 files changed, 98 insertions(+), 85 deletions(-) rename src/Umbraco.Core/Models/{EntityBase => }/IDeepCloneable.cs (65%) create mode 100644 src/Umbraco.Tests/Models/UserTests.cs create mode 100644 src/Umbraco.Tests/Models/UserTypeTests.cs diff --git a/src/Umbraco.Core/Models/EntityBase/IDeepCloneable.cs b/src/Umbraco.Core/Models/IDeepCloneable.cs similarity index 65% rename from src/Umbraco.Core/Models/EntityBase/IDeepCloneable.cs rename to src/Umbraco.Core/Models/IDeepCloneable.cs index 62d8ba7f77..3932446e2f 100644 --- a/src/Umbraco.Core/Models/EntityBase/IDeepCloneable.cs +++ b/src/Umbraco.Core/Models/IDeepCloneable.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Core.Models.EntityBase +namespace Umbraco.Core.Models { public interface IDeepCloneable { diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 0f71c0b783..0b3234438c 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -194,7 +194,7 @@ - + diff --git a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs index 13a590ad3a..789dee709c 100644 --- a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs +++ b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs @@ -1,92 +1,9 @@ using System; using NUnit.Framework; using Umbraco.Core.Models; -using Umbraco.Core.Models.Membership; namespace Umbraco.Tests.Models { - [TestFixture] - public class UserTypeTests - { - [Test] - public void Can_Deep_Clone() - { - var item = new UserType() - { - Id = 3, - Key = Guid.NewGuid(), - UpdateDate = DateTime.Now, - CreateDate = DateTime.Now, - Name = "Test", - Alias = "test", - Permissions = new[] {"a", "b", "c"} - }; - - var clone = (User)item.DeepClone(); - - Assert.AreNotSame(clone, item); - Assert.AreEqual(clone, item); - - //Verify normal properties with reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) - { - Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); - } - } - } - - [TestFixture] - public class UserTests - { - [Test] - public void Can_Deep_Clone() - { - var item = new User(new UserType(){Id = 3}) - { - Id = 3, - Key = Guid.NewGuid(), - UpdateDate = DateTime.Now, - CreateDate = DateTime.Now, - Name = "Test", - Comments = "comments", - DefaultPermissions = new[]{"a","b","c"}, - DefaultToLiveEditing = false, - Email = "test@test.com", - Language = "en", - FailedPasswordAttempts = 3, - IsApproved = true, - IsLockedOut = true, - LastLockoutDate = DateTime.Now, - LastLoginDate = DateTime.Now, - LastPasswordChangeDate = DateTime.Now, - Password = "test pass", - PasswordAnswer = "answer", - PasswordQuestion = "question", - //ProviderUserKey = "user key", - SessionTimeout = 5, - StartContentId = 3, - StartMediaId = 8, - Username = "username" - }; - - var clone = (User)item.DeepClone(); - - Assert.AreNotSame(clone, item); - Assert.AreEqual(clone, item); - - Assert.AreNotSame(clone.UserType, item.UserType); - Assert.AreEqual(clone.UserType, item.UserType); - - //Verify normal properties with reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) - { - Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); - } - } - } - [TestFixture] public class UmbracoEntityTests { diff --git a/src/Umbraco.Tests/Models/UserTests.cs b/src/Umbraco.Tests/Models/UserTests.cs new file mode 100644 index 0000000000..369d6d0777 --- /dev/null +++ b/src/Umbraco.Tests/Models/UserTests.cs @@ -0,0 +1,57 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class UserTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new User(new UserType(){Id = 3}) + { + Id = 3, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + CreateDate = DateTime.Now, + Name = "Test", + Comments = "comments", + DefaultPermissions = new[]{"a","b","c"}, + DefaultToLiveEditing = false, + Email = "test@test.com", + Language = "en", + FailedPasswordAttempts = 3, + IsApproved = true, + IsLockedOut = true, + LastLockoutDate = DateTime.Now, + LastLoginDate = DateTime.Now, + LastPasswordChangeDate = DateTime.Now, + Password = "test pass", + PasswordAnswer = "answer", + PasswordQuestion = "question", + //ProviderUserKey = "user key", + SessionTimeout = 5, + StartContentId = 3, + StartMediaId = 8, + Username = "username" + }; + + var clone = (User)item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + + Assert.AreNotSame(clone.UserType, item.UserType); + Assert.AreEqual(clone.UserType, item.UserType); + + //Verify normal properties with reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/UserTypeTests.cs b/src/Umbraco.Tests/Models/UserTypeTests.cs new file mode 100644 index 0000000000..20d4b0d63f --- /dev/null +++ b/src/Umbraco.Tests/Models/UserTypeTests.cs @@ -0,0 +1,37 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models.Membership; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class UserTypeTests + { + [Test] + public void Can_Deep_Clone() + { + var item = new UserType() + { + Id = 3, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + CreateDate = DateTime.Now, + Name = "Test", + Alias = "test", + Permissions = new[] {"a", "b", "c"} + }; + + var clone = (UserType)item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + + //Verify normal properties with reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 38e41ffe68..da9d6a60cd 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -173,6 +173,8 @@ + + From 1f2e46a19541800259967a604265e10c5e18ae0a Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Apr 2014 13:39:42 +1000 Subject: [PATCH 07/25] Simplifies much of the deep cloning with the DeepCloneHelper and adds tests --- src/Umbraco.Core/Models/Content.cs | 13 +- src/Umbraco.Core/Models/ContentBase.cs | 16 -- src/Umbraco.Core/Models/ContentType.cs | 10 - src/Umbraco.Core/Models/ContentTypeBase.cs | 17 +- .../Models/ContentTypeCompositionBase.cs | 10 +- src/Umbraco.Core/Models/DeepCloneHelper.cs | 127 ++++++++++ src/Umbraco.Core/Models/DictionaryItem.cs | 8 +- .../Models/DictionaryTranslation.cs | 6 - src/Umbraco.Core/Models/EntityBase/Entity.cs | 6 +- src/Umbraco.Core/Models/File.cs | 14 +- src/Umbraco.Core/Models/IDeepCloneable.cs | 2 + src/Umbraco.Core/Models/Membership/User.cs | 9 +- src/Umbraco.Core/Models/Property.cs | 16 +- src/Umbraco.Core/Models/PropertyGroup.cs | 10 - src/Umbraco.Core/Models/PropertyType.cs | 13 +- src/Umbraco.Core/Models/Relation.cs | 6 - src/Umbraco.Core/Models/Task.cs | 6 - src/Umbraco.Core/Models/Template.cs | 14 +- src/Umbraco.Core/Models/UmbracoEntity.cs | 9 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Tests/Models/ContentTypeTests.cs | 15 +- .../Models/DeepCloneHelperTests.cs | 224 ++++++++++++++++++ src/Umbraco.Tests/Models/LanguageTests.cs | 4 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 24 files changed, 402 insertions(+), 155 deletions(-) create mode 100644 src/Umbraco.Core/Models/DeepCloneHelper.cs create mode 100644 src/Umbraco.Tests/Models/DeepCloneHelperTests.cs diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 0f843f9565..5409d9e217 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -438,16 +438,11 @@ namespace Umbraco.Core.Models public override object DeepClone() { - var clone = base.DeepClone(); + var clone = (Content)base.DeepClone(); - var asContent = (Content)clone; - if (Template != null) - { - asContent.Template = (ITemplate)Template.DeepClone(); - } - - asContent._contentType = (IContentType)ContentType.DeepClone(); - asContent.ResetDirtyProperties(true); + //need to manually clone this since it's not settable + clone._contentType = (IContentType)ContentType.DeepClone(); + clone.ResetDirtyProperties(true); return clone; diff --git a/src/Umbraco.Core/Models/ContentBase.cs b/src/Umbraco.Core/Models/ContentBase.cs index 75af29263f..37d483325c 100644 --- a/src/Umbraco.Core/Models/ContentBase.cs +++ b/src/Umbraco.Core/Models/ContentBase.cs @@ -471,21 +471,5 @@ namespace Umbraco.Core.Models } } - public override object DeepClone() - { - var clone = base.DeepClone(); - - //cast to this object to set the complex properties - var asContentBase = (IContentBase)clone; - asContentBase.Properties = (PropertyCollection)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 12c528278b..710efb9e3a 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -175,15 +175,5 @@ namespace Umbraco.Core.Models return clone; } - public override object DeepClone() - { - var clone = base.DeepClone(); - - var contentType = (ContentType)clone; - contentType.AllowedTemplates = AllowedTemplates.Select(t => (ITemplate)t.DeepClone()).ToArray(); - contentType.ResetDirtyProperties(true); - - 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 ade9e8b9d9..760c0e2116 100644 --- a/src/Umbraco.Core/Models/ContentTypeBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; @@ -577,20 +577,5 @@ namespace Umbraco.Core.Models propertyType.ResetDirtyProperties(); } } - - public override object DeepClone() - { - var clone = base.DeepClone(); - - var contentType = (ContentTypeBase)clone; - - contentType.AllowedContentTypes = AllowedContentTypes.Select(x => (ContentTypeSort)x.DeepClone()).ToList(); - contentType.PropertyGroups = (PropertyGroupCollection)PropertyGroups.DeepClone(); - contentType.PropertyTypes = PropertyTypes.Select(x => (PropertyType)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 b8b48e7eec..7569d31d8e 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -220,12 +220,12 @@ namespace Umbraco.Core.Models public override object DeepClone() { - var clone = base.DeepClone(); + var clone = (ContentTypeCompositionBase)base.DeepClone(); - var contentType = (ContentTypeCompositionBase)clone; - contentType.RemovedContentTypeKeyTracker = new List(); - contentType._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList(); - contentType.ResetDirtyProperties(true); + //need to manually assign since this is an internal field and will not be automatically mapped + clone.RemovedContentTypeKeyTracker = new List(); + clone._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList(); + clone.ResetDirtyProperties(true); return clone; } diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs new file mode 100644 index 0000000000..88cc879b85 --- /dev/null +++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Umbraco.Core.Models +{ + internal static class DeepCloneHelper + { + /// + /// Used to deep clone any reference properties on the object (should be done after a MemberwiseClone for which the outcome is 'output') + /// + /// + /// + /// + public static void DeepCloneRefProperties(IDeepCloneable input, IDeepCloneable output) + { + var inputType = input.GetType(); + var outputType = output.GetType(); + + if (inputType != outputType) + { + throw new InvalidOperationException("Both the input and output types must be the same"); + } + + var refProperties = inputType.GetProperties() + .Where(x => x.PropertyType.IsValueType == false + //settable + && x.CanWrite + //non-indexed + && x.GetIndexParameters().Any() == false); + + foreach (var propertyInfo in refProperties) + { + if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType)) + { + //this ref property is also deep cloneable so clone it + var result = (IDeepCloneable)propertyInfo.GetValue(input, null); + + if (result != null) + { + //set the cloned value to the property + propertyInfo.SetValue(output, result.DeepClone(), null); + } + } + else if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType) + && TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType) == false) + { + IList newList; + if (propertyInfo.PropertyType.IsGenericType + && (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof (IEnumerable<>) + || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof (ICollection<>) + || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>))) + { + //if it is a IEnumerable<>, IList or ICollection<> we'll use a List<> + var genericType = typeof (List<>).MakeGenericType(propertyInfo.PropertyType.GetGenericArguments()); + newList = (IList) Activator.CreateInstance(genericType); + } + else if (propertyInfo.PropertyType.IsArray + || (propertyInfo.PropertyType.IsInterface && propertyInfo.PropertyType.IsGenericType == false)) + { + //if its an array, we'll create a list to work with first and then convert to array later + //otherwise if its just a regular derivitave of IEnumerable, we can use a list too + newList = new List(); + } + else + { + //its a custom IEnumerable, we'll try to create it + try + { + var custom = Activator.CreateInstance(propertyInfo.PropertyType); + //if it's an IList we can work with it, otherwise we cannot + newList = custom as IList; + if (newList == null) + { + continue; + } + } + catch (Exception) + { + //could not create this type so we'll skip it + continue; + } + } + + var enumerable = (IEnumerable)propertyInfo.GetValue(input, null); + if (enumerable == null) continue; + + var isDeepClonableItems = false; + foreach (var o in enumerable) + { + var dc = o as IDeepCloneable; + if (dc != null) + { + isDeepClonableItems = true; + newList.Add(dc.DeepClone()); + } + else if (isDeepClonableItems) + { + //if not all items are deep cloneable throw an exception + throw new InvalidOperationException("Cannot deep clone items in a collection that are not all " + typeof(IDeepCloneable)); + } + } + + if (propertyInfo.PropertyType.IsArray) + { + //need to convert to array + var arr = (object[])Activator.CreateInstance(propertyInfo.PropertyType, newList.Count); + for (int i = 0; i < newList.Count; i++) + { + arr[i] = newList[i]; + } + //set the cloned collection + propertyInfo.SetValue(output, arr, null); + } + else + { + //set the cloned collection + propertyInfo.SetValue(output, newList, null); + } + + } + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Models/DictionaryItem.cs b/src/Umbraco.Core/Models/DictionaryItem.cs index b29e618f83..6c2ee47714 100644 --- a/src/Umbraco.Core/Models/DictionaryItem.cs +++ b/src/Umbraco.Core/Models/DictionaryItem.cs @@ -97,12 +97,6 @@ namespace Umbraco.Core.Models if(ParentId == Guid.Empty) _parentId = new Guid("41c7638d-f529-4bff-853e-59a0c2fb1bde"); } - - public override object DeepClone() - { - var clone = (DictionaryItem)base.DeepClone(); - clone.Translations = Translations.Select(x => (IDictionaryTranslation) x.DeepClone()).ToList(); - return clone; - } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/DictionaryTranslation.cs b/src/Umbraco.Core/Models/DictionaryTranslation.cs index b815ab8c5b..f1c1af0eb7 100644 --- a/src/Umbraco.Core/Models/DictionaryTranslation.cs +++ b/src/Umbraco.Core/Models/DictionaryTranslation.cs @@ -66,11 +66,5 @@ namespace Umbraco.Core.Models } } - public override object DeepClone() - { - var clone = (DictionaryTranslation)base.DeepClone(); - clone.Language = (ILanguage)Language.DeepClone(); - return 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 27dad979fc..cc05d90214 100644 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs @@ -235,8 +235,10 @@ namespace Umbraco.Core.Models.EntityBase { //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); + var clone = (Entity)MemberwiseClone(); + //Automatically deep clone ref properties that are IDeepCloneable + DeepCloneHelper.DeepCloneRefProperties(this, clone); + clone.ResetDirtyProperties(true); return clone; //Using data contract serializer - has issues diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index 49f2557d40..0bd80956dd 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -107,17 +107,13 @@ namespace Umbraco.Core.Models public override object DeepClone() { - var clone = base.DeepClone(); + var clone = (File)base.DeepClone(); - var asFile = (File)clone; - asFile._alias = Alias; - asFile._name = Name; + //need to manually assign since they are readonly properties + clone._alias = Alias; + clone._name = Name; - var tracksChanges = clone as TracksChangesEntityBase; - if (tracksChanges != null) - { - tracksChanges.ResetDirtyProperties(true); - } + clone.ResetDirtyProperties(true); return clone; } diff --git a/src/Umbraco.Core/Models/IDeepCloneable.cs b/src/Umbraco.Core/Models/IDeepCloneable.cs index 3932446e2f..5f41f6d828 100644 --- a/src/Umbraco.Core/Models/IDeepCloneable.cs +++ b/src/Umbraco.Core/Models/IDeepCloneable.cs @@ -1,3 +1,5 @@ +using System.Reflection; + namespace Umbraco.Core.Models { public interface IDeepCloneable diff --git a/src/Umbraco.Core/Models/Membership/User.cs b/src/Umbraco.Core/Models/Membership/User.cs index 073423ff4e..11ee83d069 100644 --- a/src/Umbraco.Core/Models/Membership/User.cs +++ b/src/Umbraco.Core/Models/Membership/User.cs @@ -437,14 +437,7 @@ namespace Umbraco.Core.Models.Membership _removedSections.Add(e.OldItems.Cast().First()); } } - - public override object DeepClone() - { - var clone = (User)base.DeepClone(); - clone.UserType = (UserType)UserType.DeepClone(); - return clone; - } - + /// /// Internal class used to wrap the user in a profile /// diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index 04aa545612..f177ff209c 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -145,18 +145,12 @@ namespace Umbraco.Core.Models public override object DeepClone() { - var clone = base.DeepClone(); - - var asProperty = (Property)clone; - asProperty._propertyType = (PropertyType)PropertyType.DeepClone(); - asProperty.ResetDirtyProperties(true); - - var tracksChanges = clone as TracksChangesEntityBase; - if (tracksChanges != null) - { - tracksChanges.ResetDirtyProperties(true); - } + var clone = (Property)base.DeepClone(); + //need to manually assign since this is a readonly property + clone._propertyType = (PropertyType)PropertyType.DeepClone(); + clone.ResetDirtyProperties(true); + return clone; } } diff --git a/src/Umbraco.Core/Models/PropertyGroup.cs b/src/Umbraco.Core/Models/PropertyGroup.cs index 000ae3959a..64ebab2961 100644 --- a/src/Umbraco.Core/Models/PropertyGroup.cs +++ b/src/Umbraco.Core/Models/PropertyGroup.cs @@ -159,15 +159,5 @@ namespace Umbraco.Core.Models return clone; } - public override object DeepClone() - { - var clone = base.DeepClone(); - - var propertyGroup = (PropertyGroup)clone; - propertyGroup.PropertyTypes = (PropertyTypeCollection)PropertyTypes.DeepClone(); - propertyGroup.ResetDirtyProperties(true); - - return clone; - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 28014ed2d6..04f9ca4d80 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -421,21 +421,16 @@ namespace Umbraco.Core.Models public override object DeepClone() { - var clone = base.DeepClone(); - - var asPropertyType = (PropertyType)clone; + var clone = (PropertyType)base.DeepClone(); + //need to manually assign the Lazy value as it will not be automatically mapped if (PropertyGroupId != null) { var propGroupId = PropertyGroupId.Value; - asPropertyType._propertyGroupId = new Lazy(() => propGroupId); + clone._propertyGroupId = new Lazy(() => propGroupId); } - var tracksChanges = clone as TracksChangesEntityBase; - if (tracksChanges != null) - { - tracksChanges.ResetDirtyProperties(true); - } + clone.ResetDirtyProperties(true); return clone; } diff --git a/src/Umbraco.Core/Models/Relation.cs b/src/Umbraco.Core/Models/Relation.cs index 2eef36cf73..f08e4b147e 100644 --- a/src/Umbraco.Core/Models/Relation.cs +++ b/src/Umbraco.Core/Models/Relation.cs @@ -108,11 +108,5 @@ namespace Umbraco.Core.Models get { return _relationType.Id; } } - public override object DeepClone() - { - var clone = (Relation)base.DeepClone(); - clone.RelationType = (RelationType)RelationType.DeepClone(); - return clone; - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Task.cs b/src/Umbraco.Core/Models/Task.cs index 9a4b9eaa71..0f58814eea 100644 --- a/src/Umbraco.Core/Models/Task.cs +++ b/src/Umbraco.Core/Models/Task.cs @@ -133,11 +133,5 @@ namespace Umbraco.Core.Models } } - public override object DeepClone() - { - var clone = (Task) base.DeepClone(); - clone.TaskType = (TaskType)TaskType.DeepClone(); - return clone; - } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 808e4627b7..2ea27939c5 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -199,17 +199,13 @@ namespace Umbraco.Core.Models public override object DeepClone() { - var clone = base.DeepClone(); + var clone = (Template)base.DeepClone(); - var asTemplate = (Template)clone; - asTemplate._alias = Alias; - asTemplate._name = Name; + //need to manually assign since they are readonly properties + clone._alias = Alias; + clone._name = Name; - var tracksChanges = clone as TracksChangesEntityBase; - if (tracksChanges != null) - { - tracksChanges.ResetDirtyProperties(true); - } + clone.ResetDirtyProperties(true); return clone; } diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs index 347f6be5c7..1fd33bd6a8 100644 --- a/src/Umbraco.Core/Models/UmbracoEntity.cs +++ b/src/Umbraco.Core/Models/UmbracoEntity.cs @@ -290,14 +290,7 @@ namespace Umbraco.Core.Models /// Some entities may expose additional data that other's might not, this custom data will be available in this collection /// public IList UmbracoProperties { get; set; } - - public override object DeepClone() - { - var clone = (UmbracoEntity)base.DeepClone(); - clone.UmbracoProperties = UmbracoProperties.Select(x => (UmbracoProperty) x.DeepClone()).ToList(); - return clone; - } - + internal class UmbracoProperty : IDeepCloneable { public Guid DataTypeControlId { get; set; } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 0b3234438c..62f5ecfb73 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -194,6 +194,7 @@ + diff --git a/src/Umbraco.Tests/Models/ContentTypeTests.cs b/src/Umbraco.Tests/Models/ContentTypeTests.cs index 9f2e97bbc9..ebf47fe4f7 100644 --- a/src/Umbraco.Tests/Models/ContentTypeTests.cs +++ b/src/Umbraco.Tests/Models/ContentTypeTests.cs @@ -21,12 +21,6 @@ namespace Umbraco.Tests.Models Assert.AreEqual(clone.SortOrder, contentType.SortOrder); Assert.AreEqual(clone.Alias, contentType.Alias); - //This double verifies by reflection - var allProps = clone.GetType().GetProperties(); - foreach (var propertyInfo in allProps) - { - Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(contentType, null)); - } } [Test] @@ -41,6 +35,8 @@ namespace Umbraco.Tests.Models { propertyType.Id = ++i; } + contentType.AllowedTemplates = new[] { new Template("-1,2", "Name", "name") { Id = 200 }, new Template("-1,3", "Name2", "name2") { Id = 201 } }; + contentType.AllowedContentTypes = new[] {new ContentTypeSort(new Lazy(() => 888), 8, "sub"), new ContentTypeSort(new Lazy(() => 889), 9, "sub2")}; contentType.Id = 10; contentType.CreateDate = DateTime.Now; contentType.CreatorId = 22; @@ -70,6 +66,12 @@ namespace Umbraco.Tests.Models Assert.AreEqual(clone, contentType); Assert.AreEqual(clone.Id, contentType.Id); Assert.AreEqual(((IUmbracoEntity)clone).AdditionalData, ((IUmbracoEntity)contentType).AdditionalData); + Assert.AreEqual(clone.AllowedTemplates.Count(), contentType.AllowedTemplates.Count()); + for (var index = 0; index < contentType.AllowedTemplates.Count(); index++) + { + Assert.AreNotSame(clone.AllowedTemplates.ElementAt(index), contentType.AllowedTemplates.ElementAt(index)); + Assert.AreEqual(clone.AllowedTemplates.ElementAt(index), contentType.AllowedTemplates.ElementAt(index)); + } Assert.AreEqual(clone.PropertyGroups.Count, contentType.PropertyGroups.Count); for (var index = 0; index < contentType.PropertyGroups.Count; index++) { @@ -82,6 +84,7 @@ namespace Umbraco.Tests.Models Assert.AreNotSame(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); Assert.AreEqual(clone.PropertyTypes.ElementAt(index), contentType.PropertyTypes.ElementAt(index)); } + Assert.AreEqual(clone.CreateDate, contentType.CreateDate); Assert.AreEqual(clone.CreatorId, contentType.CreatorId); Assert.AreEqual(clone.Key, contentType.Key); diff --git a/src/Umbraco.Tests/Models/DeepCloneHelperTests.cs b/src/Umbraco.Tests/Models/DeepCloneHelperTests.cs new file mode 100644 index 0000000000..cf5cdc3204 --- /dev/null +++ b/src/Umbraco.Tests/Models/DeepCloneHelperTests.cs @@ -0,0 +1,224 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class DeepCloneHelperTests + { + [Test] + public void Deep_Clone_Ref_Properties() + { + var test1 = new Test1() + { + MyTest1 = new Test1(), + MyTest2 = new Test2() + }; + + var clone = (Test1)test1.DeepClone(); + + Assert.AreNotSame(test1, clone); + Assert.AreNotSame(test1.MyTest1, clone.MyTest1); + Assert.AreSame(test1.MyTest2, clone.MyTest2); + } + + [Test] + public void Deep_Clone_Array_Property() + { + var test1 = new Test3() + { + MyTest1 = new object[] { new Test1(), new Test1() } + }; + + var clone = (Test3)test1.DeepClone(); + + Assert.AreNotSame(test1, clone); + Assert.AreEqual(test1.MyTest1.Count(), clone.MyTest1.Count()); + for (int i = 0; i < test1.MyTest1.Count(); i++) + { + Assert.IsNotNull(clone.MyTest1.ElementAt(i)); + Assert.AreNotSame(clone.MyTest1.ElementAt(i), test1.MyTest1.ElementAt(i)); + } + } + + [Test] + public void Deep_Clone_Typed_Array_Property() + { + var test1 = new Test4() + { + MyTest1 = new[] { new Test1(), new Test1() } + }; + + var clone = (Test4)test1.DeepClone(); + + Assert.AreNotSame(test1, clone); + Assert.AreEqual(test1.MyTest1.Count(), clone.MyTest1.Count()); + for (int i = 0; i < test1.MyTest1.Count(); i++) + { + Assert.IsNotNull(clone.MyTest1.ElementAt(i)); + Assert.AreNotSame(clone.MyTest1.ElementAt(i), test1.MyTest1.ElementAt(i)); + } + } + + [Test] + public void Deep_Clone_Enumerable_Property() + { + var test1 = new Test5() + { + MyTest1 = new[] { new Test1(), new Test1() } + }; + + var clone = (Test5)test1.DeepClone(); + + Assert.AreNotSame(test1, clone); + + Assert.AreEqual(test1.MyTest1.Cast().Count(), clone.MyTest1.Cast().Count()); + for (int i = 0; i < test1.MyTest1.Cast().Count(); i++) + { + Assert.IsNotNull(clone.MyTest1.Cast().ElementAt(i)); + Assert.AreNotSame(clone.MyTest1.Cast().ElementAt(i), test1.MyTest1.Cast().ElementAt(i)); + } + } + + [Test] + public void Deep_Clone_Typed_Enumerable_Property() + { + var test1 = new Test6() + { + MyTest1 = new[] { new Test1(), new Test1() } + }; + + var clone = (Test6)test1.DeepClone(); + + Assert.AreNotSame(test1, clone); + Assert.AreEqual(test1.MyTest1.Count(), clone.MyTest1.Count()); + for (int i = 0; i < test1.MyTest1.Count(); i++) + { + Assert.IsNotNull(clone.MyTest1.ElementAt(i)); + Assert.AreNotSame(clone.MyTest1.ElementAt(i), test1.MyTest1.ElementAt(i)); + } + } + + [Test] + public void Deep_Clone_Custom_Enumerable_Property() + { + var test1 = new Test7() + { + MyTest1 = new List { new Test1(), new Test1() } + }; + + var clone = (Test7)test1.DeepClone(); + + Assert.AreNotSame(test1, clone); + Assert.AreEqual(test1.MyTest1.Count(), clone.MyTest1.Count()); + for (int i = 0; i < test1.MyTest1.Count(); i++) + { + Assert.IsNotNull(clone.MyTest1.ElementAt(i)); + Assert.AreNotSame(clone.MyTest1.ElementAt(i), test1.MyTest1.ElementAt(i)); + } + } + + [Test] + public void Deep_Clone_Custom_Enumerable_Interface_Property() + { + var test1 = new Test8() + { + MyTest1 = new List { new Test1(), new Test1() } + }; + + var clone = (Test8)test1.DeepClone(); + + Assert.AreNotSame(test1, clone); + Assert.AreEqual(test1.MyTest1.Count(), clone.MyTest1.Count()); + for (int i = 0; i < test1.MyTest1.Count(); i++) + { + Assert.IsNotNull(clone.MyTest1.ElementAt(i)); + Assert.AreNotSame(clone.MyTest1.ElementAt(i), test1.MyTest1.ElementAt(i)); + } + } + + [Test] + public void Cannot_Deep_Clone_Collection_Properties_That_Are_Not_Cloneable() + { + var test1 = new Test3() + { + MyTest1 = new object[] { new Test1(), "hello" } + }; + + Assert.Throws(() => test1.DeepClone()); + + } + + public class Test1 : BaseCloneable + { + public string Name { get; set; } + public int Age { get; set; } + + public Test1 MyTest1 { get; set; } + public Test2 MyTest2 { get; set; } + + } + + public class Test2 + { + public string Name { get; set; } + public Test1 MyTest1 { get; set; } + } + + public class Test3 : BaseCloneable + { + public string Name { get; set; } + public object[] MyTest1 { get; set; } + + } + + public class Test4 : BaseCloneable + { + public string Name { get; set; } + public Test1[] MyTest1 { get; set; } + + } + + public class Test5 : BaseCloneable + { + public string Name { get; set; } + public IEnumerable MyTest1 { get; set; } + + } + + public class Test6 : BaseCloneable + { + public string Name { get; set; } + public IEnumerable MyTest1 { get; set; } + + } + + public class Test7 : BaseCloneable + { + public string Name { get; set; } + public List MyTest1 { get; set; } + + } + + public class Test8 : BaseCloneable + { + public string Name { get; set; } + public ICollection MyTest1 { get; set; } + + } + + public abstract class BaseCloneable : IDeepCloneable + { + public object DeepClone() + { + var clone = (IDeepCloneable)MemberwiseClone(); + DeepCloneHelper.DeepCloneRefProperties(this, clone); + return clone; + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/LanguageTests.cs b/src/Umbraco.Tests/Models/LanguageTests.cs index 0637aa1b99..13d380afde 100644 --- a/src/Umbraco.Tests/Models/LanguageTests.cs +++ b/src/Umbraco.Tests/Models/LanguageTests.cs @@ -13,9 +13,9 @@ namespace Umbraco.Tests.Models var item = new Language("en-AU") { CreateDate = DateTime.Now, - CultureName = "en", + CultureName = "AU", Id = 11, - IsoCode = "AU", + IsoCode = "en", Key = Guid.NewGuid(), UpdateDate = DateTime.Now }; diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index da9d6a60cd..bcec215b6b 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -162,6 +162,7 @@ + From 6e166bdba72bd60702db36ddd7a3f12d057ebb40 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Apr 2014 13:40:20 +1000 Subject: [PATCH 08/25] publicizes DeepCloneHelper --- src/Umbraco.Core/Models/DeepCloneHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs index 88cc879b85..a55658d8e3 100644 --- a/src/Umbraco.Core/Models/DeepCloneHelper.cs +++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs @@ -5,7 +5,7 @@ using System.Linq; namespace Umbraco.Core.Models { - internal static class DeepCloneHelper + public static class DeepCloneHelper { /// /// Used to deep clone any reference properties on the object (should be done after a MemberwiseClone for which the outcome is 'output') From ac7cb8febeefdef8f2d0decdf39eded91dcc8d40 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Apr 2014 14:01:26 +1000 Subject: [PATCH 09/25] Fixes cache providers to ensure it stores and resolves clones --- .../Caching/InMemoryCacheProvider.cs | 24 ++++++++++++++++--- .../Caching/RuntimeCacheProvider.cs | 14 ++++++++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Caching/InMemoryCacheProvider.cs b/src/Umbraco.Core/Persistence/Caching/InMemoryCacheProvider.cs index 5544c74494..7572dcd5c5 100644 --- a/src/Umbraco.Core/Persistence/Caching/InMemoryCacheProvider.cs +++ b/src/Umbraco.Core/Persistence/Caching/InMemoryCacheProvider.cs @@ -37,7 +37,10 @@ namespace Umbraco.Core.Persistence.Caching var containsKey = _cache.ContainsKey(compositeKey); if (containsKey) { - return _cache[compositeKey]; + var result = _cache[compositeKey]; + + //IMPORTANT: we must clone to resolve, see: http://issues.umbraco.org/issue/U4-4259 + return (IEntity)result.DeepClone(); } return null; @@ -56,7 +59,12 @@ namespace Umbraco.Core.Persistence.Caching into key let containsKey = _cache.ContainsKey(key) where containsKey - select _cache[key]).ToList(); + select _cache[key] + into result + //don't return null objects + where result != null + //IMPORTANT: we must clone to resolve, see: http://issues.umbraco.org/issue/U4-4259 + select (IEntity)result.DeepClone()).ToList(); return list; } @@ -67,7 +75,14 @@ namespace Umbraco.Core.Persistence.Caching /// public IEnumerable GetAllByType(Type type) { - var list = _cache.Keys.Where(key => key.Contains(type.Name)).Select(key => _cache[key]).ToList(); + var list = _cache.Keys + .Where(key => key.Contains(type.Name)) + .Select(key => _cache[key]) + //don't return null objects + .Where(result => result != null) + //IMPORTANT: we must clone to resolve, see: http://issues.umbraco.org/issue/U4-4259 + .Select(result => (IEntity)result.DeepClone()) + .ToList(); return list; } @@ -78,6 +93,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(); + _cache.AddOrUpdate(GetCompositeId(type, entity.Id), entity, (x, y) => entity); } diff --git a/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs b/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs index ff6db47860..c2ca53a240 100644 --- a/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs @@ -67,8 +67,11 @@ namespace Umbraco.Core.Persistence.Caching { //ensure the key doesn't exist anymore in the tracker _keyTracker.Remove(key); + return null; } - return result; + + //IMPORTANT: we must clone to resolve, see: http://issues.umbraco.org/issue/U4-4259 + return (IEntity)result.DeepClone(); } public IEnumerable GetByIds(Type type, List ids) @@ -88,7 +91,8 @@ namespace Umbraco.Core.Persistence.Caching } else { - collection.Add(result); + //IMPORTANT: we must clone to resolve, see: http://issues.umbraco.org/issue/U4-4259 + collection.Add((IEntity)result.DeepClone()); } } return collection; @@ -113,7 +117,8 @@ namespace Umbraco.Core.Persistence.Caching } else { - collection.Add(result); + //IMPORTANT: we must clone to resolve, see: http://issues.umbraco.org/issue/U4-4259 + collection.Add((IEntity)result.DeepClone()); } } } @@ -122,6 +127,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 key = GetCompositeId(type, entity.Id); _keyTracker.TryAdd(key); From 7aa1f480c9719fc0175ad2cde0deb4000ade8785 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Apr 2014 15:33:21 +1000 Subject: [PATCH 10/25] Fixes up some cloning with value types and collections --- src/Umbraco.Core/Models/DeepCloneHelper.cs | 33 ++++++++++++++----- .../Models/DeepCloneHelperTests.cs | 14 ++++++-- src/Umbraco.Tests/Models/UserTests.cs | 4 +-- src/Umbraco.Tests/Models/UserTypeTests.cs | 1 + 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs index a55658d8e3..1127ef78e7 100644 --- a/src/Umbraco.Core/Models/DeepCloneHelper.cs +++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs @@ -48,13 +48,13 @@ namespace Umbraco.Core.Models { IList newList; if (propertyInfo.PropertyType.IsGenericType - && (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof (IEnumerable<>) - || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof (ICollection<>) + && (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>) + || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>) || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>))) { //if it is a IEnumerable<>, IList or ICollection<> we'll use a List<> - var genericType = typeof (List<>).MakeGenericType(propertyInfo.PropertyType.GetGenericArguments()); - newList = (IList) Activator.CreateInstance(genericType); + var genericType = typeof(List<>).MakeGenericType(propertyInfo.PropertyType.GetGenericArguments()); + newList = (IList)Activator.CreateInstance(genericType); } else if (propertyInfo.PropertyType.IsArray || (propertyInfo.PropertyType.IsInterface && propertyInfo.PropertyType.IsGenericType == false)) @@ -86,20 +86,35 @@ namespace Umbraco.Core.Models var enumerable = (IEnumerable)propertyInfo.GetValue(input, null); if (enumerable == null) continue; - var isDeepClonableItems = false; + var isUsableType = true; + + //now clone each item foreach (var o in enumerable) { + //first check if the item is deep cloneable and copy that way var dc = o as IDeepCloneable; if (dc != null) { - isDeepClonableItems = true; newList.Add(dc.DeepClone()); } - else if (isDeepClonableItems) + else if (o is string || o.GetType().IsValueType) { - //if not all items are deep cloneable throw an exception - throw new InvalidOperationException("Cannot deep clone items in a collection that are not all " + typeof(IDeepCloneable)); + //check if the item is a value type or a string, then we can just use it + newList.Add(o); } + else + { + //this will occur if the item is not a string or value type or IDeepCloneable, in this case we cannot + // clone each element, we'll need to skip this property, people will have to manually clone this list + isUsableType = false; + break; + } + } + + //if this was not usable, skip this property + if (isUsableType == false) + { + continue; } if (propertyInfo.PropertyType.IsArray) diff --git a/src/Umbraco.Tests/Models/DeepCloneHelperTests.cs b/src/Umbraco.Tests/Models/DeepCloneHelperTests.cs index cf5cdc3204..d82d0d2ebd 100644 --- a/src/Umbraco.Tests/Models/DeepCloneHelperTests.cs +++ b/src/Umbraco.Tests/Models/DeepCloneHelperTests.cs @@ -146,11 +146,19 @@ namespace Umbraco.Tests.Models { var test1 = new Test3() { - MyTest1 = new object[] { new Test1(), "hello" } + MyTest1 = new object[] + { + new Test1(), "hello", + //not cloneable so this property will get skipped + new Test2() + } }; - Assert.Throws(() => test1.DeepClone()); - + var clone = (Test3)test1.DeepClone(); + + //it skipped this property so these will now be the same + Assert.AreSame(clone.MyTest1, test1.MyTest1); + } public class Test1 : BaseCloneable diff --git a/src/Umbraco.Tests/Models/UserTests.cs b/src/Umbraco.Tests/Models/UserTests.cs index 369d6d0777..043593f0ec 100644 --- a/src/Umbraco.Tests/Models/UserTests.cs +++ b/src/Umbraco.Tests/Models/UserTests.cs @@ -28,8 +28,8 @@ namespace Umbraco.Tests.Models LastLockoutDate = DateTime.Now, LastLoginDate = DateTime.Now, LastPasswordChangeDate = DateTime.Now, - Password = "test pass", - PasswordAnswer = "answer", + //Password = "test pass", + //PasswordAnswer = "answer", PasswordQuestion = "question", //ProviderUserKey = "user key", SessionTimeout = 5, diff --git a/src/Umbraco.Tests/Models/UserTypeTests.cs b/src/Umbraco.Tests/Models/UserTypeTests.cs index 20d4b0d63f..d4f9e75f22 100644 --- a/src/Umbraco.Tests/Models/UserTypeTests.cs +++ b/src/Umbraco.Tests/Models/UserTypeTests.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using NUnit.Framework; using Umbraco.Core.Models.Membership; From 9b344a5a69cfc8ff6fa1844f50ac529b701d983c Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Apr 2014 16:44:08 +1000 Subject: [PATCH 11/25] Added tests for member groups and member cloning. Added tests to verify that all entities can be serialized without issue. --- src/Umbraco.Core/Models/DeepCloneHelper.cs | 12 +- src/Umbraco.Core/Models/Member.cs | 38 +++-- src/Umbraco.Tests/Models/ContentTests.cs | 43 +++++ src/Umbraco.Tests/Models/ContentTypeTests.cs | 116 +++++++++++++ .../Models/DataTypeDefinitionTests.cs | 28 ++++ .../Models/DictionaryItemTests.cs | 54 ++++++ .../Models/DictionaryTranslationTests.cs | 27 +++ src/Umbraco.Tests/Models/LanguageTests.cs | 21 +++ src/Umbraco.Tests/Models/MemberGroupTests.cs | 73 ++++++++ src/Umbraco.Tests/Models/MemberTests.cs | 156 ++++++++++++++++++ .../Models/PropertyGroupTests.cs | 58 +++++++ src/Umbraco.Tests/Models/PropertyTypeTests.cs | 29 ++++ src/Umbraco.Tests/Models/RelationTests.cs | 23 +++ src/Umbraco.Tests/Models/RelationTypeTests.cs | 21 +++ src/Umbraco.Tests/Models/StylesheetTests.cs | 21 ++- src/Umbraco.Tests/Models/TaskTests.cs | 25 +++ src/Umbraco.Tests/Models/TaskTypeTests.cs | 19 +++ src/Umbraco.Tests/Models/TemplateTests.cs | 26 +++ .../Models/UmbracoEntityTests.cs | 45 +++++ src/Umbraco.Tests/Models/UserTests.cs | 39 +++++ src/Umbraco.Tests/Models/UserTypeTests.cs | 22 +++ src/Umbraco.Tests/Umbraco.Tests.csproj | 2 + 22 files changed, 882 insertions(+), 16 deletions(-) create mode 100644 src/Umbraco.Tests/Models/MemberGroupTests.cs create mode 100644 src/Umbraco.Tests/Models/MemberTests.cs diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs index 1127ef78e7..aaee03f963 100644 --- a/src/Umbraco.Core/Models/DeepCloneHelper.cs +++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs @@ -24,11 +24,13 @@ namespace Umbraco.Core.Models } var refProperties = inputType.GetProperties() - .Where(x => x.PropertyType.IsValueType == false - //settable - && x.CanWrite - //non-indexed - && x.GetIndexParameters().Any() == false); + .Where(x => + //reference type but not string + x.PropertyType.IsValueType == false && x.PropertyType != typeof (string) + //settable + && x.CanWrite + //non-indexed + && x.GetIndexParameters().Any() == false); foreach (var propertyInfo in refProperties) { diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index ca87fa11c7..019b644afc 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Models [DataContract(IsReference = true)] public class Member : ContentBase, IMember { - private readonly IMemberType _contentType; + private IMemberType _contentType; private readonly string _contentTypeAlias; private string _username; private string _email; @@ -153,7 +153,7 @@ namespace Umbraco.Core.Models /// /// Gets or sets the raw password value /// - [DataMember] + [IgnoreDataMember] public string RawPasswordValue { get { return _rawPasswordValue; } @@ -180,7 +180,7 @@ namespace Umbraco.Core.Models /// Alias: umbracoPasswordRetrievalQuestionPropertyTypeAlias /// Part of the standard properties collection. /// - [IgnoreDataMember] + [DataMember] public string PasswordQuestion { get @@ -240,7 +240,7 @@ namespace Umbraco.Core.Models /// Alias: umbracoCommentPropertyTypeAlias /// Part of the standard properties collection. /// - [IgnoreDataMember] + [DataMember] public string Comments { get @@ -269,7 +269,7 @@ namespace Umbraco.Core.Models /// Alias: umbracoApprovePropertyTypeAlias /// Part of the standard properties collection. /// - [IgnoreDataMember] + [DataMember] public bool IsApproved { get @@ -304,7 +304,7 @@ namespace Umbraco.Core.Models /// Alias: umbracoLockPropertyTypeAlias /// Part of the standard properties collection. /// - [IgnoreDataMember] + [DataMember] public bool IsLockedOut { get @@ -336,7 +336,7 @@ namespace Umbraco.Core.Models /// Alias: umbracoLastLoginPropertyTypeAlias /// Part of the standard properties collection. /// - [IgnoreDataMember] + [DataMember] public DateTime LastLoginDate { get @@ -368,7 +368,7 @@ namespace Umbraco.Core.Models /// Alias: umbracoMemberLastPasswordChange /// Part of the standard properties collection. /// - [IgnoreDataMember] + [DataMember] public DateTime LastPasswordChangeDate { get @@ -400,7 +400,7 @@ namespace Umbraco.Core.Models /// Alias: umbracoMemberLastLockout /// Part of the standard properties collection. /// - [IgnoreDataMember] + [DataMember] public DateTime LastLockoutDate { get @@ -433,7 +433,7 @@ namespace Umbraco.Core.Models /// Alias: umbracoFailedPasswordAttemptsPropertyTypeAlias /// Part of the standard properties collection. /// - [IgnoreDataMember] + [DataMember] public int FailedPasswordAttempts { get @@ -554,11 +554,17 @@ namespace Umbraco.Core.Models /* Internal experiment - only used for mapping queries. * Adding these to have first level properties instead of the Properties collection. */ + [IgnoreDataMember] internal string LongStringPropertyValue { get; set; } + [IgnoreDataMember] internal string ShortStringPropertyValue { get; set; } + [IgnoreDataMember] internal int IntegerropertyValue { get; set; } + [IgnoreDataMember] internal bool BoolPropertyValue { get; set; } + [IgnoreDataMember] internal DateTime DateTimePropertyValue { get; set; } + [IgnoreDataMember] internal string PropertyTypeAlias { get; set; } private Attempt WarnIfPropertyTypeNotFoundOnGet(string propertyAlias, string propertyName, T defaultVal) @@ -628,5 +634,17 @@ namespace Umbraco.Core.Models return true; } + + public override object DeepClone() + { + var clone = (Member)base.DeepClone(); + + //need to manually clone this since it's not settable + clone._contentType = (IMemberType)ContentType.DeepClone(); + clone.ResetDirtyProperties(true); + + return clone; + + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index f676569c5f..6f9dbc5d46 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -7,6 +7,7 @@ using Moq; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Serialization; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -225,6 +226,48 @@ namespace Umbraco.Tests.Models } } + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + // Arrange + var contentType = MockedContentTypes.CreateTextpageContentType(); + contentType.Id = 99; + var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1); + var i = 200; + foreach (var property in content.Properties) + { + property.Id = ++i; + } + content.Id = 10; + content.CreateDate = DateTime.Now; + content.CreatorId = 22; + content.ExpireDate = DateTime.Now; + content.Key = Guid.NewGuid(); + content.Language = "en"; + content.Level = 3; + content.Path = "-1,4,10"; + content.ReleaseDate = DateTime.Now; + content.ChangePublishedState(PublishedState.Published); + content.SortOrder = 5; + content.Template = new Template("-1,2,3,4", "Test Template", "testTemplate") + { + Id = 88 + }; + content.Trashed = false; + content.UpdateDate = DateTime.Now; + content.Version = Guid.NewGuid(); + content.WriterId = 23; + + ((IUmbracoEntity)content).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)content).AdditionalData.Add("test2", "hello"); + + var result = ss.ToStream(content); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + /*[Test] public void Cannot_Change_Property_With_Invalid_Value() { diff --git a/src/Umbraco.Tests/Models/ContentTypeTests.cs b/src/Umbraco.Tests/Models/ContentTypeTests.cs index ebf47fe4f7..af94f80fd4 100644 --- a/src/Umbraco.Tests/Models/ContentTypeTests.cs +++ b/src/Umbraco.Tests/Models/ContentTypeTests.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Serialization; using Umbraco.Tests.TestHelpers.Entities; namespace Umbraco.Tests.Models @@ -108,6 +109,48 @@ namespace Umbraco.Tests.Models } } + [Test] + public void Can_Serialize_Content_Type_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + // Arrange + var contentType = MockedContentTypes.CreateTextpageContentType(); + contentType.Id = 99; + + var i = 200; + foreach (var propertyType in contentType.PropertyTypes) + { + propertyType.Id = ++i; + } + contentType.AllowedTemplates = new[] { new Template("-1,2", "Name", "name") { Id = 200 }, new Template("-1,3", "Name2", "name2") { Id = 201 } }; + contentType.AllowedContentTypes = new[] { new ContentTypeSort(new Lazy(() => 888), 8, "sub"), new ContentTypeSort(new Lazy(() => 889), 9, "sub2") }; + contentType.Id = 10; + contentType.CreateDate = DateTime.Now; + contentType.CreatorId = 22; + contentType.SetDefaultTemplate(new Template("-1,2,3,4", "Test Template", "testTemplate") + { + Id = 88 + }); + contentType.Description = "test"; + contentType.Icon = "icon"; + contentType.IsContainer = true; + contentType.Thumbnail = "thumb"; + contentType.Key = Guid.NewGuid(); + contentType.Level = 3; + contentType.Path = "-1,4,10"; + contentType.SortOrder = 5; + contentType.Trashed = false; + contentType.UpdateDate = DateTime.Now; + + ((IUmbracoEntity)contentType).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)contentType).AdditionalData.Add("test2", "hello"); + + var result = ss.ToStream(contentType); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + [Test] public void Can_Deep_Clone_Media_Type() { @@ -177,6 +220,42 @@ namespace Umbraco.Tests.Models } } + [Test] + public void Can_Serialize_Media_Type_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + // Arrange + var contentType = MockedContentTypes.CreateImageMediaType(); + contentType.Id = 99; + + var i = 200; + foreach (var propertyType in contentType.PropertyTypes) + { + propertyType.Id = ++i; + } + contentType.Id = 10; + contentType.CreateDate = DateTime.Now; + contentType.CreatorId = 22; + contentType.Description = "test"; + contentType.Icon = "icon"; + contentType.IsContainer = true; + contentType.Thumbnail = "thumb"; + contentType.Key = Guid.NewGuid(); + contentType.Level = 3; + contentType.Path = "-1,4,10"; + contentType.SortOrder = 5; + contentType.Trashed = false; + contentType.UpdateDate = DateTime.Now; + + ((IUmbracoEntity)contentType).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)contentType).AdditionalData.Add("test2", "hello"); + + var result = ss.ToStream(contentType); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + [Test] public void Can_Deep_Clone_Member_Type() { @@ -247,5 +326,42 @@ namespace Umbraco.Tests.Models Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(contentType, null)); } } + + [Test] + public void Can_Serialize_Member_Type_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + // Arrange + var contentType = MockedContentTypes.CreateSimpleMemberType(); + contentType.Id = 99; + + var i = 200; + foreach (var propertyType in contentType.PropertyTypes) + { + propertyType.Id = ++i; + } + contentType.Id = 10; + contentType.CreateDate = DateTime.Now; + contentType.CreatorId = 22; + contentType.Description = "test"; + contentType.Icon = "icon"; + contentType.IsContainer = true; + contentType.Thumbnail = "thumb"; + contentType.Key = Guid.NewGuid(); + contentType.Level = 3; + contentType.Path = "-1,4,10"; + contentType.SortOrder = 5; + contentType.Trashed = false; + contentType.UpdateDate = DateTime.Now; + contentType.SetMemberCanEditProperty("title", true); + contentType.SetMemberCanViewProperty("bodyText", true); + ((IUmbracoEntity)contentType).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)contentType).AdditionalData.Add("test2", "hello"); + + var result = ss.ToStream(contentType); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs b/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs index e731b3610d..d066fbacaf 100644 --- a/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs +++ b/src/Umbraco.Tests/Models/DataTypeDefinitionTests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models { @@ -50,5 +51,32 @@ namespace Umbraco.Tests.Models Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(dtd, null)); } } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var dtd = new DataTypeDefinition(9, Guid.NewGuid()) + { + CreateDate = DateTime.Now, + CreatorId = 5, + DatabaseType = DataTypeDatabaseType.Nvarchar, + Id = 4, + Key = Guid.NewGuid(), + Level = 7, + Name = "Test", + ParentId = 9, + Path = "-1,2", + SortOrder = 8, + Trashed = true, + UpdateDate = DateTime.Now + }; + + var result = ss.ToStream(dtd); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/DictionaryItemTests.cs b/src/Umbraco.Tests/Models/DictionaryItemTests.cs index 32fa2f127e..ff57b5eec6 100644 --- a/src/Umbraco.Tests/Models/DictionaryItemTests.cs +++ b/src/Umbraco.Tests/Models/DictionaryItemTests.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models { @@ -78,5 +79,58 @@ namespace Umbraco.Tests.Models Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); } } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new DictionaryItem("blah") + { + CreateDate = DateTime.Now, + Id = 8, + ItemKey = "blah", + Key = Guid.NewGuid(), + ParentId = Guid.NewGuid(), + UpdateDate = DateTime.Now, + Translations = new[] + { + new DictionaryTranslation(new Language("en-AU") + { + CreateDate = DateTime.Now, + CultureName = "en", + Id = 11, + IsoCode = "AU", + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, "colour") + { + CreateDate = DateTime.Now, + Id = 88, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, + new DictionaryTranslation(new Language("en-US") + { + CreateDate = DateTime.Now, + CultureName = "en", + Id = 12, + IsoCode = "US", + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, "color") + { + CreateDate = DateTime.Now, + Id = 89, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, + } + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs b/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs index 2c585989c4..d0a8c21581 100644 --- a/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs +++ b/src/Umbraco.Tests/Models/DictionaryTranslationTests.cs @@ -1,6 +1,7 @@ using System; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models { @@ -45,5 +46,31 @@ namespace Umbraco.Tests.Models Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); } } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new DictionaryTranslation(new Language("en-AU") + { + CreateDate = DateTime.Now, + CultureName = "en", + Id = 11, + IsoCode = "AU", + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }, "colour") + { + CreateDate = DateTime.Now, + Id = 88, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/LanguageTests.cs b/src/Umbraco.Tests/Models/LanguageTests.cs index 13d380afde..5d79364fb5 100644 --- a/src/Umbraco.Tests/Models/LanguageTests.cs +++ b/src/Umbraco.Tests/Models/LanguageTests.cs @@ -1,6 +1,7 @@ using System; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models { @@ -37,5 +38,25 @@ namespace Umbraco.Tests.Models Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); } } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new Language("en-AU") + { + CreateDate = DateTime.Now, + CultureName = "AU", + Id = 11, + IsoCode = "en", + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/MemberGroupTests.cs b/src/Umbraco.Tests/Models/MemberGroupTests.cs new file mode 100644 index 0000000000..d39ddc93f3 --- /dev/null +++ b/src/Umbraco.Tests/Models/MemberGroupTests.cs @@ -0,0 +1,73 @@ +using System; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Serialization; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class MemberGroupTests + { + [Test] + public void Can_Deep_Clone() + { + // Arrange + var group = new MemberGroup() + { + CreateDate = DateTime.Now, + CreatorId = 4, + Id = 6, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + Name = "asdf" + }; + group.AdditionalData.Add("test1", 123); + group.AdditionalData.Add("test2", "hello"); + + // Act + var clone = (MemberGroup)group.DeepClone(); + + // Assert + Assert.AreNotSame(clone, group); + Assert.AreEqual(clone, group); + Assert.AreEqual(clone.Id, group.Id); + Assert.AreEqual(clone.AdditionalData, group.AdditionalData); + Assert.AreEqual(clone.AdditionalData.Count, group.AdditionalData.Count); + Assert.AreEqual(clone.CreateDate, group.CreateDate); + Assert.AreEqual(clone.CreatorId, group.CreatorId); + Assert.AreEqual(clone.Key, group.Key); + Assert.AreEqual(clone.UpdateDate, group.UpdateDate); + Assert.AreEqual(clone.Name, group.Name); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(group, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var group = new MemberGroup() + { + CreateDate = DateTime.Now, + CreatorId = 4, + Id = 6, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + Name = "asdf" + }; + group.AdditionalData.Add("test1", 123); + group.AdditionalData.Add("test2", "hello"); + + var result = ss.ToStream(group); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/MemberTests.cs b/src/Umbraco.Tests/Models/MemberTests.cs new file mode 100644 index 0000000000..62e3602232 --- /dev/null +++ b/src/Umbraco.Tests/Models/MemberTests.cs @@ -0,0 +1,156 @@ +using System; +using System.Linq; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; +using Umbraco.Core.Serialization; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Models +{ + [TestFixture] + public class MemberTests + { + [Test] + public void Can_Deep_Clone() + { + // Arrange + var memberType = MockedContentTypes.CreateSimpleMemberType("memberType", "Member Type"); + memberType.Id = 99; + var member = MockedMember.CreateSimpleMember(memberType, "Name", "email@email.com", "pass", "user", Guid.NewGuid()); + var i = 200; + foreach (var property in member.Properties) + { + property.Id = ++i; + } + member.Id = 10; + member.CreateDate = DateTime.Now; + member.CreatorId = 22; + member.Comments = "comments"; + member.Key = Guid.NewGuid(); + member.FailedPasswordAttempts = 22; + member.Level = 3; + member.Path = "-1,4,10"; + member.Groups = new[] {"group1", "group2"}; + member.IsApproved = true; + member.IsLockedOut = true; + member.LastLockoutDate = DateTime.Now; + member.LastLoginDate = DateTime.Now; + member.LastPasswordChangeDate = DateTime.Now; + member.PasswordQuestion = "question"; + member.ProviderUserKey = Guid.NewGuid(); + member.RawPasswordAnswerValue = "raw answer"; + member.RawPasswordValue = "raw pass"; + member.SortOrder = 5; + member.Trashed = false; + member.UpdateDate = DateTime.Now; + member.Version = Guid.NewGuid(); + ((IUmbracoEntity)member).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)member).AdditionalData.Add("test2", "hello"); + + // Act + var clone = (Member)member.DeepClone(); + + // Assert + Assert.AreNotSame(clone, member); + Assert.AreEqual(clone, member); + Assert.AreEqual(clone.Id, member.Id); + Assert.AreEqual(clone.Version, member.Version); + Assert.AreEqual(((IUmbracoEntity)clone).AdditionalData, ((IUmbracoEntity)member).AdditionalData); + Assert.AreNotSame(clone.ContentType, member.ContentType); + Assert.AreEqual(clone.ContentType, member.ContentType); + Assert.AreEqual(clone.ContentType.PropertyGroups.Count, member.ContentType.PropertyGroups.Count); + for (var index = 0; index < member.ContentType.PropertyGroups.Count; index++) + { + Assert.AreNotSame(clone.ContentType.PropertyGroups[index], member.ContentType.PropertyGroups[index]); + Assert.AreEqual(clone.ContentType.PropertyGroups[index], member.ContentType.PropertyGroups[index]); + } + Assert.AreEqual(clone.ContentType.PropertyTypes.Count(), member.ContentType.PropertyTypes.Count()); + for (var index = 0; index < member.ContentType.PropertyTypes.Count(); index++) + { + Assert.AreNotSame(clone.ContentType.PropertyTypes.ElementAt(index), member.ContentType.PropertyTypes.ElementAt(index)); + Assert.AreEqual(clone.ContentType.PropertyTypes.ElementAt(index), member.ContentType.PropertyTypes.ElementAt(index)); + } + Assert.AreEqual(clone.ContentTypeId, member.ContentTypeId); + Assert.AreEqual(clone.CreateDate, member.CreateDate); + Assert.AreEqual(clone.CreatorId, member.CreatorId); + Assert.AreEqual(clone.Comments, member.Comments); + Assert.AreEqual(clone.Key, member.Key); + Assert.AreEqual(clone.FailedPasswordAttempts, member.FailedPasswordAttempts); + Assert.AreEqual(clone.Level, member.Level); + Assert.AreEqual(clone.Path, member.Path); + Assert.AreEqual(clone.Groups, member.Groups); + Assert.AreEqual(clone.Groups.Count(), member.Groups.Count()); + Assert.AreEqual(clone.IsApproved, member.IsApproved); + Assert.AreEqual(clone.IsLockedOut, member.IsLockedOut); + Assert.AreEqual(clone.SortOrder, member.SortOrder); + Assert.AreEqual(clone.LastLockoutDate, member.LastLockoutDate); + Assert.AreNotSame(clone.LastLoginDate, member.LastLoginDate); + Assert.AreEqual(clone.LastPasswordChangeDate, member.LastPasswordChangeDate); + Assert.AreEqual(clone.Trashed, member.Trashed); + Assert.AreEqual(clone.UpdateDate, member.UpdateDate); + Assert.AreEqual(clone.Version, member.Version); + Assert.AreEqual(clone.PasswordQuestion, member.PasswordQuestion); + Assert.AreEqual(clone.ProviderUserKey, member.ProviderUserKey); + Assert.AreEqual(clone.RawPasswordAnswerValue, member.RawPasswordAnswerValue); + Assert.AreEqual(clone.RawPasswordValue, member.RawPasswordValue); + Assert.AreNotSame(clone.Properties, member.Properties); + Assert.AreEqual(clone.Properties.Count(), member.Properties.Count()); + for (var index = 0; index < member.Properties.Count; index++) + { + Assert.AreNotSame(clone.Properties[index], member.Properties[index]); + Assert.AreEqual(clone.Properties[index], member.Properties[index]); + } + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(member, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var memberType = MockedContentTypes.CreateSimpleMemberType("memberType", "Member Type"); + memberType.Id = 99; + var member = MockedMember.CreateSimpleMember(memberType, "Name", "email@email.com", "pass", "user", Guid.NewGuid()); + var i = 200; + foreach (var property in member.Properties) + { + property.Id = ++i; + } + member.Id = 10; + member.CreateDate = DateTime.Now; + member.CreatorId = 22; + member.Comments = "comments"; + member.Key = Guid.NewGuid(); + member.FailedPasswordAttempts = 22; + member.Level = 3; + member.Path = "-1,4,10"; + member.Groups = new[] { "group1", "group2" }; + member.IsApproved = true; + member.IsLockedOut = true; + member.LastLockoutDate = DateTime.Now; + member.LastLoginDate = DateTime.Now; + member.LastPasswordChangeDate = DateTime.Now; + member.PasswordQuestion = "question"; + member.ProviderUserKey = Guid.NewGuid(); + member.RawPasswordAnswerValue = "raw answer"; + member.RawPasswordValue = "raw pass"; + member.SortOrder = 5; + member.Trashed = false; + member.UpdateDate = DateTime.Now; + member.Version = Guid.NewGuid(); + ((IUmbracoEntity)member).AdditionalData.Add("test1", 123); + ((IUmbracoEntity)member).AdditionalData.Add("test2", "hello"); + + var result = ss.ToStream(member); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/PropertyGroupTests.cs b/src/Umbraco.Tests/Models/PropertyGroupTests.cs index 3b3af26ab7..f3dad6546f 100644 --- a/src/Umbraco.Tests/Models/PropertyGroupTests.cs +++ b/src/Umbraco.Tests/Models/PropertyGroupTests.cs @@ -1,6 +1,7 @@ using System; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models { @@ -86,5 +87,62 @@ namespace Umbraco.Tests.Models } } + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var pg = new PropertyGroup( + new PropertyTypeCollection(new[] + { + new PropertyType(Guid.NewGuid(), DataTypeDatabaseType.Nvarchar) + { + Id = 3, + Alias = "test", + CreateDate = DateTime.Now, + DataTypeDefinitionId = 5, + DataTypeId = Guid.NewGuid(), + Description = "testing", + Key = Guid.NewGuid(), + Mandatory = true, + Name = "Test", + PropertyGroupId = new Lazy(() => 11), + SortOrder = 9, + UpdateDate = DateTime.Now, + ValidationRegExp = "xxxx", + DataTypeDatabaseType = DataTypeDatabaseType.Nvarchar + }, + new PropertyType(Guid.NewGuid(), DataTypeDatabaseType.Nvarchar) + { + Id = 4, + Alias = "test2", + CreateDate = DateTime.Now, + DataTypeDefinitionId = 6, + DataTypeId = Guid.NewGuid(), + Description = "testing2", + Key = Guid.NewGuid(), + Mandatory = true, + Name = "Test2", + PropertyGroupId = new Lazy(() => 12), + SortOrder = 10, + UpdateDate = DateTime.Now, + ValidationRegExp = "yyyy", + DataTypeDatabaseType = DataTypeDatabaseType.Nvarchar + } + })) + { + Id = 77, + CreateDate = DateTime.Now, + Key = Guid.NewGuid(), + Name = "Group1", + SortOrder = 555, + UpdateDate = DateTime.Now, + ParentId = 9 + }; + + var result = ss.ToStream(pg); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/PropertyTypeTests.cs b/src/Umbraco.Tests/Models/PropertyTypeTests.cs index 68ce1c5cfc..6acb9aca7e 100644 --- a/src/Umbraco.Tests/Models/PropertyTypeTests.cs +++ b/src/Umbraco.Tests/Models/PropertyTypeTests.cs @@ -1,6 +1,7 @@ using System; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models { @@ -55,5 +56,33 @@ namespace Umbraco.Tests.Models } } + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var pt = new PropertyType(Guid.NewGuid(), DataTypeDatabaseType.Nvarchar) + { + Id = 3, + Alias = "test", + CreateDate = DateTime.Now, + DataTypeDefinitionId = 5, + DataTypeId = Guid.NewGuid(), + Description = "testing", + Key = Guid.NewGuid(), + Mandatory = true, + Name = "Test", + PropertyGroupId = new Lazy(() => 11), + SortOrder = 9, + UpdateDate = DateTime.Now, + ValidationRegExp = "xxxx", + DataTypeDatabaseType = DataTypeDatabaseType.Nvarchar + }; + + var result = ss.ToStream(pt); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/RelationTests.cs b/src/Umbraco.Tests/Models/RelationTests.cs index 3293bd76cf..fdc3ae874b 100644 --- a/src/Umbraco.Tests/Models/RelationTests.cs +++ b/src/Umbraco.Tests/Models/RelationTests.cs @@ -1,6 +1,7 @@ using System; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models { @@ -44,5 +45,27 @@ namespace Umbraco.Tests.Models Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); } } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new Relation(9, 8, new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test") + { + Id = 66 + }) + { + Comment = "test comment", + CreateDate = DateTime.Now, + Id = 4, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/RelationTypeTests.cs b/src/Umbraco.Tests/Models/RelationTypeTests.cs index 69850bcb51..2022ab912d 100644 --- a/src/Umbraco.Tests/Models/RelationTypeTests.cs +++ b/src/Umbraco.Tests/Models/RelationTypeTests.cs @@ -1,6 +1,7 @@ using System; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models { @@ -40,5 +41,25 @@ namespace Umbraco.Tests.Models Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); } } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new RelationType(Guid.NewGuid(), Guid.NewGuid(), "test") + { + Id = 66, + CreateDate = DateTime.Now, + IsBidirectional = true, + Key = Guid.NewGuid(), + Name = "Test", + UpdateDate = DateTime.Now + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/StylesheetTests.cs b/src/Umbraco.Tests/Models/StylesheetTests.cs index 7d65a9130d..d45aed9cae 100644 --- a/src/Umbraco.Tests/Models/StylesheetTests.cs +++ b/src/Umbraco.Tests/Models/StylesheetTests.cs @@ -1,6 +1,8 @@ -using System.Linq; +using System; +using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models { @@ -128,5 +130,22 @@ namespace Umbraco.Tests.Models Assert.That(properties, Is.Not.Null); Assert.That(properties.Any(), Is.True); } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var stylesheet = new Stylesheet("/css/styles.css"); + stylesheet.Content = @"@media screen and (min-width: 600px) and (min-width: 900px) { + .class { + background: #666; + } + }"; + + var result = ss.ToStream(stylesheet); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/TaskTests.cs b/src/Umbraco.Tests/Models/TaskTests.cs index 8518697f24..377746cbaf 100644 --- a/src/Umbraco.Tests/Models/TaskTests.cs +++ b/src/Umbraco.Tests/Models/TaskTests.cs @@ -1,6 +1,7 @@ using System; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models { @@ -48,5 +49,29 @@ namespace Umbraco.Tests.Models } } + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new Task(new TaskType("test") { Id = 3 }) + { + AssigneeUserId = 4, + Closed = true, + Comment = "blah", + CreateDate = DateTime.Now, + EntityId = 99, + Id = 2, + Key = Guid.NewGuid(), + OwnerUserId = 89, + TaskType = new TaskType("asdf") { Id = 99 }, + UpdateDate = DateTime.Now + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/TaskTypeTests.cs b/src/Umbraco.Tests/Models/TaskTypeTests.cs index 65f6a8ef34..e83f8dc3cf 100644 --- a/src/Umbraco.Tests/Models/TaskTypeTests.cs +++ b/src/Umbraco.Tests/Models/TaskTypeTests.cs @@ -1,6 +1,7 @@ using System; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models { @@ -38,5 +39,23 @@ namespace Umbraco.Tests.Models } + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new TaskType("test") + { + Id = 3, + Alias = "test", + CreateDate = DateTime.Now, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/TemplateTests.cs b/src/Umbraco.Tests/Models/TemplateTests.cs index 12752a8efa..bf63387d61 100644 --- a/src/Umbraco.Tests/Models/TemplateTests.cs +++ b/src/Umbraco.Tests/Models/TemplateTests.cs @@ -1,6 +1,7 @@ using System; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models { @@ -50,5 +51,30 @@ namespace Umbraco.Tests.Models } } + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new Template("-1,2,3", "Test", "test") + { + Id = 3, + CreateDate = DateTime.Now, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + Content = "blah", + CreatorId = 66, + Level = 55, + ParentId = 2, + SortOrder = 99, + MasterTemplateAlias = "master", + MasterTemplateId = new Lazy(() => 88) + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } + } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs index c6c9b788ac..813594b54d 100644 --- a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs +++ b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs @@ -1,6 +1,7 @@ using System; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models { @@ -95,5 +96,49 @@ namespace Umbraco.Tests.Models Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); } } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new UmbracoEntity() + { + Id = 3, + ContentTypeAlias = "test1", + CreatorId = 4, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + CreateDate = DateTime.Now, + Name = "Test", + ParentId = 5, + SortOrder = 6, + Path = "-1,23", + Level = 7, + ContentTypeIcon = "icon", + ContentTypeThumbnail = "thumb", + HasChildren = true, + HasPendingChanges = true, + IsDraft = true, + IsPublished = true, + NodeObjectTypeId = Guid.NewGuid() + }; + item.AdditionalData.Add("test1", 3); + item.AdditionalData.Add("test2", "valuie"); + item.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty() + { + Value = "test", + DataTypeControlId = Guid.NewGuid() + }); + item.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty() + { + Value = "test2", + DataTypeControlId = Guid.NewGuid() + }); + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/UserTests.cs b/src/Umbraco.Tests/Models/UserTests.cs index 043593f0ec..84b0fe317d 100644 --- a/src/Umbraco.Tests/Models/UserTests.cs +++ b/src/Umbraco.Tests/Models/UserTests.cs @@ -1,6 +1,7 @@ using System; using NUnit.Framework; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models { @@ -53,5 +54,43 @@ namespace Umbraco.Tests.Models Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); } } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new User(new UserType() { Id = 3 }) + { + Id = 3, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + CreateDate = DateTime.Now, + Name = "Test", + Comments = "comments", + DefaultPermissions = new[] { "a", "b", "c" }, + DefaultToLiveEditing = false, + Email = "test@test.com", + Language = "en", + FailedPasswordAttempts = 3, + IsApproved = true, + IsLockedOut = true, + LastLockoutDate = DateTime.Now, + LastLoginDate = DateTime.Now, + LastPasswordChangeDate = DateTime.Now, + //Password = "test pass", + //PasswordAnswer = "answer", + PasswordQuestion = "question", + //ProviderUserKey = "user key", + SessionTimeout = 5, + StartContentId = 3, + StartMediaId = 8, + Username = "username" + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Models/UserTypeTests.cs b/src/Umbraco.Tests/Models/UserTypeTests.cs index d4f9e75f22..01e4d6fd89 100644 --- a/src/Umbraco.Tests/Models/UserTypeTests.cs +++ b/src/Umbraco.Tests/Models/UserTypeTests.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models.Membership; +using Umbraco.Core.Serialization; namespace Umbraco.Tests.Models { @@ -34,5 +35,26 @@ namespace Umbraco.Tests.Models Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); } } + + [Test] + public void Can_Serialize_Without_Error() + { + var ss = new SerializationService(new JsonNetSerializer()); + + var item = new UserType() + { + Id = 3, + Key = Guid.NewGuid(), + UpdateDate = DateTime.Now, + CreateDate = DateTime.Now, + Name = "Test", + Alias = "test", + Permissions = new[] { "a", "b", "c" } + }; + + var result = ss.ToStream(item); + var json = result.ResultStream.ToJsonString(); + Console.WriteLine(json); + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index e472228a21..a40f1a4688 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -171,6 +171,8 @@ + + From ad8ab9f46efa340ca45f1372978090d9b187a99e Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Apr 2014 16:48:52 +1000 Subject: [PATCH 12/25] Fixes media uploader issue to ensure non-approved file types are not saved --- .../umbraco/webservices/MediaUploader.ashx.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/MediaUploader.ashx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/MediaUploader.ashx.cs index 97532cf590..e240f7ae37 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/MediaUploader.ashx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/MediaUploader.ashx.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Configuration; +using System.IO; using System.Linq; using System.Web; using System.Web.Configuration; @@ -154,6 +155,14 @@ namespace umbraco.presentation.umbraco.webservices // get the current file var uploadFile = context.Request.Files[j]; + //Are we allowed to upload this? + var ext = uploadFile.FileName.Substring(uploadFile.FileName.LastIndexOf('.') + 1).ToLower(); + if (UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext)) + { + LogHelper.Warn("Cannot upload file " + uploadFile + ", it is not an approved file type"); + continue; + } + using (var inputStream = uploadFile.InputStream) { // if there was a file uploded From 2f679a52f43e69ce4a607ab1649d2e8acde2182a Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Apr 2014 16:52:45 +1000 Subject: [PATCH 13/25] fix build --- .../umbraco/webservices/MediaUploader.ashx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/MediaUploader.ashx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/MediaUploader.ashx.cs index e240f7ae37..f6212fac9c 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/MediaUploader.ashx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/MediaUploader.ashx.cs @@ -157,7 +157,7 @@ namespace umbraco.presentation.umbraco.webservices //Are we allowed to upload this? var ext = uploadFile.FileName.Substring(uploadFile.FileName.LastIndexOf('.') + 1).ToLower(); - if (UmbracoConfig.For.UmbracoSettings().Content.DisallowedUploadFiles.Contains(ext)) + if (UmbracoSettings.DisallowedUploadFiles.Contains(ext)) { LogHelper.Warn("Cannot upload file " + uploadFile + ", it is not an approved file type"); continue; From d95396624878cde0122c1de94c18bea8dac7c30f Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 16 Apr 2014 17:01:26 +1000 Subject: [PATCH 14/25] Completes: U4-4670 Add .ContainsAny string extension to Umbraco.Core.StringExtensions --- src/Umbraco.Core/Dynamics/ExtensionMethods.cs | 28 +++---------------- src/Umbraco.Core/StringExtensions.cs | 10 +++++++ 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/Umbraco.Core/Dynamics/ExtensionMethods.cs b/src/Umbraco.Core/Dynamics/ExtensionMethods.cs index 0637864ceb..8439278b2e 100644 --- a/src/Umbraco.Core/Dynamics/ExtensionMethods.cs +++ b/src/Umbraco.Core/Dynamics/ExtensionMethods.cs @@ -36,42 +36,22 @@ namespace Umbraco.Core.Dynamics [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsAny(this string haystack, IEnumerable needles) { - if (haystack == null) throw new ArgumentNullException("haystack"); - if (!string.IsNullOrEmpty(haystack) || needles.Any()) - { - return needles.Any(haystack.Contains); - } - return false; + return StringExtensions.ContainsAny(haystack, needles); } [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsAny(this string haystack, params string[] needles) { - if (haystack == null) throw new ArgumentNullException("haystack"); - if (!string.IsNullOrEmpty(haystack) || needles.Length > 0) - { - return needles.Any(haystack.Contains); - } - return false; + return StringExtensions.ContainsAny(haystack, needles); } [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsAny(this string haystack, StringComparison comparison, IEnumerable needles) { - if (haystack == null) throw new ArgumentNullException("haystack"); - if (!string.IsNullOrEmpty(haystack) || needles.Any()) - { - return needles.Any(value => haystack.IndexOf(value, comparison) >= 0); - } - return false; + return StringExtensions.ContainsAny(haystack, needles, comparison); } [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsAny(this string haystack, StringComparison comparison, params string[] needles) { - if (haystack == null) throw new ArgumentNullException("haystack"); - if (!string.IsNullOrEmpty(haystack) || needles.Length > 0) - { - return needles.Any(value => haystack.IndexOf(value, comparison) >= 0); - } - return false; + return StringExtensions.ContainsAny(haystack, needles, comparison); } [Obsolete("This method should not be used and will be removed in the future")] public static bool ContainsInsensitive(this string haystack, string needle) diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index ac5e8b0ec6..5864b9d159 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -1180,5 +1180,15 @@ namespace Umbraco.Core */ } + public static bool ContainsAny(this string haystack, IEnumerable needles, StringComparison comparison = StringComparison.CurrentCulture) + { + if (haystack == null) throw new ArgumentNullException("haystack"); + if (string.IsNullOrEmpty(haystack) == false || needles.Any()) + { + return needles.Any(value => haystack.IndexOf(value) >= 0); + } + return false; + } + } } From 1da901be96eaa0dae1f1c0e492058c5e4c1387f2 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 7 Apr 2014 15:03:18 +0200 Subject: [PATCH 15/25] U4-4575 - refactor prop. conversion logic --- src/Umbraco.Web/Models/PublishedProperty.cs | 89 +++++++++---------- .../PublishedCache/MemberPublishedContent.cs | 2 +- 2 files changed, 45 insertions(+), 46 deletions(-) diff --git a/src/Umbraco.Web/Models/PublishedProperty.cs b/src/Umbraco.Web/Models/PublishedProperty.cs index b24d1260a3..0865f8f915 100644 --- a/src/Umbraco.Web/Models/PublishedProperty.cs +++ b/src/Umbraco.Web/Models/PublishedProperty.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -25,58 +26,56 @@ namespace Umbraco.Web.Models /// Ensures that all conversions took place correctly. internal static IEnumerable MapProperties( IEnumerable propertyTypes, IEnumerable properties, - Func map) + Func map) { - var peResolver = DataTypesResolver.Current; - var dtService = ApplicationContext.Current.Services.DataTypeService; - return MapProperties(propertyTypes, properties, peResolver, dtService, map); + return propertyTypes.Select(x => + { + var property = properties.SingleOrDefault(xx => xx.Alias == x.PropertyTypeAlias); + var value = property == null ? null : property.Value; + return map(x, ConvertPropertyValueFromDbToData(x, value)); + }); } /// - /// Maps a collection of Property to a collection of IPublishedProperty for a specified collection of PublishedPropertyType. + /// Converts a database property value to a "data" value ie a value that we can pass to + /// IPropertyValueConverter. /// - /// The published property types. - /// The properties. - /// A mapping function. - /// A DataTypesResolver instance. - /// An IDataTypeService instance. - /// A collection of IPublishedProperty corresponding to the collection of PublishedPropertyType - /// and taking values from the collection of Property. - /// Ensures that all conversions took place correctly. - internal static IEnumerable MapProperties( - IEnumerable propertyTypes, IEnumerable properties, - DataTypesResolver dataTypesResolver, IDataTypeService dataTypeService, - Func map) + /// The published property type. + /// The value. + /// The converted value. + internal static object ConvertPropertyValueFromDbToData(PublishedPropertyType propertyType, object value) { - return propertyTypes - .Select(x => - { - var p = properties.SingleOrDefault(xx => xx.Alias == x.PropertyTypeAlias); - var v = p == null || p.Value == null ? null : p.Value; - if (v != null) - { - // note - not sure about the performance here - var dataTypeDefinition = global::umbraco.cms.businesslogic.datatype.DataTypeDefinition - .GetDataTypeDefinition(x.DataTypeId); - var dataType = dataTypeDefinition.DataType; - if (dataType != null) - { - var data = dataType.Data; - data.Value = v; - var n = data.ToXMl(new XmlDocument()); - if (n.NodeType == XmlNodeType.CDATA || n.NodeType == XmlNodeType.Text) - v = n.InnerText; - else if (n.NodeType == XmlNodeType.Element) - v = n.InnerXml; - // note - is there anything else we should take care of? - } - } - // fixme - means that the IPropertyValueConverter will always get a string - // fixme and never an int or DateTime that's in the DB unless the value editor has - // fixme a way to say it's OK to use what's in the DB? + if (value == null) return null; - return map(x, p, v); - }); + // We are converting to string, even for database values which are integer or + // DateTime, which is not optimum. Doing differently would require that we have a way to tell + // whether the conversion to XML string changes something or not... which we don't, and we + // don't want to implement it as PropertyValueEditor.ConvertDbToXml/String should die anyway. + + // Don't think about improving the situation here: this is a corner case and the real + // thing to do is to get rig of PropertyValueEditor.ConvertDbToXml/String. + + // works but better use the new API + //var dataTypeDefinition = global::umbraco.cms.businesslogic.datatype.DataTypeDefinition.GetDataTypeDefinition(propertyType.DataTypeId); + //var dataType = dataTypeDefinition.DataType; + + // transient resolver will create a new object each time we call it + // so it is safe to alter DataTypeDefinitionId, Data, etc. + var dataType = DataTypesResolver.Current.GetById(propertyType.PropertyEditorGuid); + if (dataType != null) + { + dataType.DataTypeDefinitionId = propertyType.DataTypeId; // required else conversion fails + var data = dataType.Data; + data.Value = value; + var n = data.ToXMl(new XmlDocument()); + if (n.NodeType == XmlNodeType.CDATA || n.NodeType == XmlNodeType.Text) + value = n.InnerText; + else if (n.NodeType == XmlNodeType.Element) + value = n.InnerXml; + // assuming there are no other node types that we need to take care of + } + + return value; } } } diff --git a/src/Umbraco.Web/PublishedCache/MemberPublishedContent.cs b/src/Umbraco.Web/PublishedCache/MemberPublishedContent.cs index e538d135d3..bdbc28f3cf 100644 --- a/src/Umbraco.Web/PublishedCache/MemberPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/MemberPublishedContent.cs @@ -34,7 +34,7 @@ namespace Umbraco.Web.PublishedCache throw new InvalidOperationException("Could not get member type with alias " + _member.ContentTypeAlias); } _properties = PublishedProperty.MapProperties(_publishedMemberType.PropertyTypes, _member.Properties, - (t, p, v) => new RawValueProperty(t, v ?? string.Empty)) + (t, v) => new RawValueProperty(t, v ?? string.Empty)) .ToArray(); } From 306d83cbd6e2a0e8de15098b0696dea36215e982 Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 9 Apr 2014 16:06:15 +0200 Subject: [PATCH 16/25] Bugfix published content extension .Next() --- src/Umbraco.Web/PublishedContentExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 484e7697b8..411d1a9e7b 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1385,7 +1385,7 @@ namespace Umbraco.Web { if (b4) { - if (c == content) + if (c.Id == content.Id) b4 = false; else if (wrap && wrapped == null && predicate(c)) wrapped = c; From bf65bf5bab539b16db83d3539f6a4f8f2b50142e Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 17 Apr 2014 14:23:37 +1000 Subject: [PATCH 17/25] Got pre-values caching properly and data types using the runtime cache provider correctly + unit tests. --- src/Umbraco.Core/Cache/CacheKeys.cs | 1 + .../Cache/CacheProviderExtensions.cs | 6 + .../Cache/DictionaryCacheProviderBase.cs | 20 +++ src/Umbraco.Core/Cache/ICacheProvider.cs | 8 ++ src/Umbraco.Core/Cache/NullCacheProvider.cs | 5 + .../Cache/ObjectCacheRuntimeCacheProvider.cs | 7 + src/Umbraco.Core/Cache/StaticCacheProvider.cs | 7 + .../DataTypeDefinitionRepository.cs | 93 +++++++++++- .../IDataTypeDefinitionRepository.cs | 3 +- .../Persistence/RepositoryFactory.cs | 5 +- src/Umbraco.Core/Services/DataTypeService.cs | 19 ++- src/Umbraco.Core/StringExtensions.cs | 9 ++ .../DataTypeDefinitionRepositoryTest.cs | 135 +++++++++++++++++- .../Cache/DataTypeCacheRefresher.cs | 10 +- 14 files changed, 307 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index 1278ff0e33..c1f82b0d89 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -53,5 +53,6 @@ namespace Umbraco.Core.Cache public const string StylesheetPropertyCacheKey = "UmbracoStylesheetProperty"; public const string DataTypeCacheKey = "UmbracoDataTypeDefinition"; + public const string DataTypePreValuesCacheKey = "UmbracoPreVal"; } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/CacheProviderExtensions.cs b/src/Umbraco.Core/Cache/CacheProviderExtensions.cs index 7b621d013f..dc05d88e22 100644 --- a/src/Umbraco.Core/Cache/CacheProviderExtensions.cs +++ b/src/Umbraco.Core/Cache/CacheProviderExtensions.cs @@ -41,6 +41,12 @@ namespace Umbraco.Core.Cache return result.Select(x => x.TryConvertTo().Result); } + public static IEnumerable GetCacheItemsByKeyExpression(this ICacheProvider provider, string regexString) + { + var result = provider.GetCacheItemsByKeyExpression(regexString); + return result.Select(x => x.TryConvertTo().Result); + } + public static T GetCacheItem(this ICacheProvider provider, string cacheKey) { var result = provider.GetCacheItem(cacheKey); diff --git a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs index e38dee57c4..9de7ac25fa 100644 --- a/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs +++ b/src/Umbraco.Core/Cache/DictionaryCacheProviderBase.cs @@ -171,6 +171,26 @@ namespace Umbraco.Core.Cache select c.Value).ToList(); } + public IEnumerable GetCacheItemsByKeyExpression(string regexString) + { + var found = new List(); + foreach (var item in DictionaryCache) + { + var c = new DictionaryItemWrapper(item); + var s = c.Key as string; + if (s != null) + { + var withoutPrefix = s.TrimStart(string.Format("{0}-", CacheItemPrefix)); + if (Regex.IsMatch(withoutPrefix, regexString)) + { + found.Add(c.Value); + } + } + } + + return found; + } + /// /// Returns a cache item by key, does not update the cache if it isn't there. /// diff --git a/src/Umbraco.Core/Cache/ICacheProvider.cs b/src/Umbraco.Core/Cache/ICacheProvider.cs index e9bdde3ff2..4ee95c0063 100644 --- a/src/Umbraco.Core/Cache/ICacheProvider.cs +++ b/src/Umbraco.Core/Cache/ICacheProvider.cs @@ -16,7 +16,15 @@ namespace Umbraco.Core.Cache void ClearCacheByKeySearch(string keyStartsWith); void ClearCacheByKeyExpression(string regexString); IEnumerable GetCacheItemsByKeySearch(string keyStartsWith); + IEnumerable GetCacheItemsByKeyExpression(string regexString); + + /// + /// Returns an item with a given key + /// + /// + /// object GetCacheItem(string cacheKey); + object GetCacheItem(string cacheKey, Func getCacheItem); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/NullCacheProvider.cs b/src/Umbraco.Core/Cache/NullCacheProvider.cs index ba88637661..f4e449499b 100644 --- a/src/Umbraco.Core/Cache/NullCacheProvider.cs +++ b/src/Umbraco.Core/Cache/NullCacheProvider.cs @@ -43,6 +43,11 @@ namespace Umbraco.Core.Cache return Enumerable.Empty(); } + public IEnumerable GetCacheItemsByKeyExpression(string regexString) + { + return Enumerable.Empty(); + } + public virtual object GetCacheItem(string cacheKey) { return default(object); diff --git a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs index aa6a5bbb22..b8e1b6baef 100644 --- a/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/ObjectCacheRuntimeCacheProvider.cs @@ -114,6 +114,13 @@ namespace Umbraco.Core.Cache select c.Value).ToList(); } + public IEnumerable GetCacheItemsByKeyExpression(string regexString) + { + return (from c in MemoryCache + where Regex.IsMatch(c.Key, regexString) + select c.Value).ToList(); + } + public virtual object GetCacheItem(string cacheKey) { var result = MemoryCache.Get(cacheKey); diff --git a/src/Umbraco.Core/Cache/StaticCacheProvider.cs b/src/Umbraco.Core/Cache/StaticCacheProvider.cs index cd58fc3dde..9c448efa6a 100644 --- a/src/Umbraco.Core/Cache/StaticCacheProvider.cs +++ b/src/Umbraco.Core/Cache/StaticCacheProvider.cs @@ -59,6 +59,13 @@ namespace Umbraco.Core.Cache select c.Value).ToList(); } + public IEnumerable GetCacheItemsByKeyExpression(string regexString) + { + return (from KeyValuePair c in StaticCache + where Regex.IsMatch(c.Key, regexString) + select c.Value).ToList(); + } + public virtual object GetCacheItem(string cacheKey) { var result = StaticCache[cacheKey]; diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index 3798bb53d9..423802fbb5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Data; using System.Globalization; using System.Linq; +using System.Threading; +using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; @@ -11,6 +13,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; namespace Umbraco.Core.Persistence.Repositories { @@ -19,16 +22,22 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class DataTypeDefinitionRepository : PetaPocoRepositoryBase, IDataTypeDefinitionRepository { - public DataTypeDefinitionRepository(IDatabaseUnitOfWork work) + private readonly CacheHelper _cacheHelper; + + public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper) : base(work) { + _cacheHelper = cacheHelper; } - public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) + public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache, CacheHelper cacheHelper) : base(work, cache) { + _cacheHelper = cacheHelper; } + private readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); + #region Overrides of RepositoryBase protected override IDataTypeDefinition PerformGet(int id) @@ -238,5 +247,85 @@ AND umbracoNode.id <> @id", } #endregion + + public PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId) + { + using (var l = new UpgradeableReadLock(Locker)) + { + var cached = _cacheHelper.RuntimeCache.GetCacheItemsByKeySearch(GetPrefixedCacheKey(dataTypeId)); + if (cached != null && cached.Any()) + { + //return from the cache + return cached.First(); + } + + l.UpgradeToWriteLock(); + + return GetAndCachePreValueCollection(dataTypeId); + } + } + + public string GetPreValueAsString(int preValueId) + { + using (var l = new UpgradeableReadLock(Locker)) + { + //We need to see if we can find the cached PreValueCollection based on the cache key above + + var regex = CacheKeys.DataTypePreValuesCacheKey + @"[\d]+-[,\d]*" + preValueId + @"[,\d$]*"; + + var cached = _cacheHelper.RuntimeCache.GetCacheItemsByKeyExpression(regex); + if (cached != null && cached.Any()) + { + //return from the cache + var collection = cached.First(); + var preVal = collection.PreValuesAsArray.Single(x => x.Id == preValueId); + return preVal.Value; + } + + l.UpgradeToWriteLock(); + + //go and find the data type id for the pre val id passed in + + var dto = Database.FirstOrDefault("WHERE id = @preValueId", new { preValueId = preValueId }); + if (dto == null) + { + return string.Empty; + } + // go cache the collection + var preVals = GetAndCachePreValueCollection(dto.DataTypeNodeId); + + //return the single value for this id + var pv = preVals.PreValuesAsArray.Single(x => x.Id == preValueId); + return pv.Value; + } + } + + private string GetPrefixedCacheKey(int dataTypeId) + { + return CacheKeys.DataTypePreValuesCacheKey + dataTypeId + "-"; + } + + private PreValueCollection GetAndCachePreValueCollection(int dataTypeId) + { + //go get the data + var dtos = Database.Fetch("WHERE datatypeNodeId = @Id", new { Id = dataTypeId }); + var list = dtos.Select(x => new Tuple(new PreValue(x.Id, x.Value), x.Alias, x.SortOrder)).ToList(); + var collection = DataTypeService.PreValueConverter.ConvertToPreValuesCollection(list); + + //now create the cache key, this needs to include all pre-value ids so that we can use this cached item in the GetPreValuesAsString method + //the key will be: "UmbracoPreValDATATYPEID-CSVOFPREVALIDS + + var key = GetPrefixedCacheKey(dataTypeId) + + string.Join(",", collection.FormatAsDictionary().Select(x => x.Value.Id).ToArray()); + + //store into cache + _cacheHelper.RuntimeCache.InsertCacheItem(key, () => collection, + //30 mins + new TimeSpan(0, 0, 30), + //sliding is true + true); + + return collection; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs index 7a39a2cdc1..77fe5edc89 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs @@ -4,6 +4,7 @@ namespace Umbraco.Core.Persistence.Repositories { public interface IDataTypeDefinitionRepository : IRepositoryQueryable { - + PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId); + string GetPreValueAsString(int preValueId); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 627a317202..39488dd951 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -55,14 +55,15 @@ namespace Umbraco.Core.Persistence return new ContentTypeRepository( uow, _disableAllCache ? (IRepositoryCacheProvider)NullCacheProvider.Current : RuntimeCacheProvider.Current, - new TemplateRepository(uow, NullCacheProvider.Current)); + CreateTemplateRepository(uow)); } public virtual IDataTypeDefinitionRepository CreateDataTypeDefinitionRepository(IDatabaseUnitOfWork uow) { return new DataTypeDefinitionRepository( uow, - NullCacheProvider.Current); + _disableAllCache ? (IRepositoryCacheProvider)NullCacheProvider.Current : RuntimeCacheProvider.Current, + _cacheHelper); } public virtual IDictionaryRepository CreateDictionaryRepository(IDatabaseUnitOfWork uow) diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 8a1e41a707..45bb7016ef 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -108,10 +108,11 @@ namespace Umbraco.Core.Services /// An enumerable list of string values public IEnumerable GetPreValuesByDataTypeId(int id) { - using (var uow = _uowProvider.GetUnitOfWork()) + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(_uowProvider.GetUnitOfWork())) { - var dtos = uow.Database.Fetch("WHERE datatypeNodeId = @Id", new { Id = id }); - var list = dtos.Select(x => x.Value).ToList(); + var collection = repository.GetPreValuesCollectionByDataTypeId(id); + //now convert the collection to a string list + var list = collection.PreValuesAsArray.Select(x => x.Value).ToList(); return list; } } @@ -123,12 +124,9 @@ namespace Umbraco.Core.Services /// public PreValueCollection GetPreValuesCollectionByDataTypeId(int id) { - using (var uow = _uowProvider.GetUnitOfWork()) + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(_uowProvider.GetUnitOfWork())) { - var dtos = uow.Database.Fetch("WHERE datatypeNodeId = @Id", new { Id = id }); - var list = dtos.Select(x => new Tuple(new PreValue(x.Id, x.Value), x.Alias, x.SortOrder)).ToList(); - - return PreValueConverter.ConvertToPreValuesCollection(list); + return repository.GetPreValuesCollectionByDataTypeId(id); } } @@ -139,10 +137,9 @@ namespace Umbraco.Core.Services /// PreValue as a string public string GetPreValueAsString(int id) { - using (var uow = _uowProvider.GetUnitOfWork()) + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(_uowProvider.GetUnitOfWork())) { - var dto = uow.Database.FirstOrDefault("WHERE id = @Id", new { Id = id }); - return dto != null ? dto.Value : string.Empty; + return repository.GetPreValueAsString(id); } } diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 5864b9d159..18a3136c8f 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -1190,5 +1190,14 @@ namespace Umbraco.Core return false; } + public static bool CsvContains(this string csv, string value) + { + if (string.IsNullOrEmpty(csv)) + { + return false; + } + var idCheckList = csv.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries); + return idCheckList.Contains(value); + } } } diff --git a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index 7d828dfc48..6fc1d19181 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -3,14 +3,16 @@ using System.Data; 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.Models.Rdbms; using Umbraco.Core.Persistence; -using Umbraco.Core.Persistence.Caching; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Tests.TestHelpers; +using NullCacheProvider = Umbraco.Core.Persistence.Caching.NullCacheProvider; namespace Umbraco.Tests.Persistence.Repositories { @@ -26,7 +28,7 @@ namespace Umbraco.Tests.Persistence.Repositories private DataTypeDefinitionRepository CreateRepository(IDatabaseUnitOfWork unitOfWork) { - var dataTypeDefinitionRepository = new DataTypeDefinitionRepository(unitOfWork, NullCacheProvider.Current); + var dataTypeDefinitionRepository = new DataTypeDefinitionRepository(unitOfWork, NullCacheProvider.Current, CacheHelper.CreateDisabledCacheHelper()); return dataTypeDefinitionRepository; } @@ -302,6 +304,135 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Get_Pre_Value_Collection() + { + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + + int dtid; + using (var repository = CreateRepository(unitOfWork)) + { + var dataTypeDefinition = new DataTypeDefinition(-1, new Guid(Constants.PropertyEditors.RadioButtonList)) { Name = "test" }; + repository.AddOrUpdate(dataTypeDefinition); + unitOfWork.Commit(); + dtid = dataTypeDefinition.Id; + } + + DatabaseContext.Database.Insert(new DataTypePreValueDto() { DataTypeNodeId = dtid, SortOrder = 0, Value = "test1"}); + DatabaseContext.Database.Insert(new DataTypePreValueDto() { DataTypeNodeId = dtid, SortOrder = 1, Value = "test2" }); + + using (var repository = CreateRepository(unitOfWork)) + { + var collection = repository.GetPreValuesCollectionByDataTypeId(dtid); + Assert.AreEqual(2, collection.PreValuesAsArray.Count()); + } + } + + [Test] + public void Can_Get_Pre_Value_As_String() + { + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + + int dtid; + using (var repository = CreateRepository(unitOfWork)) + { + var dataTypeDefinition = new DataTypeDefinition(-1, new Guid(Constants.PropertyEditors.RadioButtonList)) { Name = "test" }; + repository.AddOrUpdate(dataTypeDefinition); + unitOfWork.Commit(); + dtid = dataTypeDefinition.Id; + } + + var id = DatabaseContext.Database.Insert(new DataTypePreValueDto() { DataTypeNodeId = dtid, SortOrder = 0, Value = "test1" }); + DatabaseContext.Database.Insert(new DataTypePreValueDto() { DataTypeNodeId = dtid, SortOrder = 1, Value = "test2" }); + + using (var repository = CreateRepository(unitOfWork)) + { + var val = repository.GetPreValueAsString(Convert.ToInt32(id)); + Assert.AreEqual("test1", val); + } + } + + [Test] + public void Can_Get_Pre_Value_Collection_With_Cache() + { + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + + var cache = new CacheHelper(new ObjectCacheRuntimeCacheProvider(), new StaticCacheProvider(), new StaticCacheProvider()); + + Func creator = () => new DataTypeDefinitionRepository( + unitOfWork, + NullCacheProvider.Current, cache); + + DataTypeDefinition dtd; + using (var repository = creator()) + { + dtd = new DataTypeDefinition(-1, new Guid(Constants.PropertyEditors.RadioButtonList)) { Name = "test" }; + repository.AddOrUpdate(dtd); + unitOfWork.Commit(); + } + + DatabaseContext.Database.Insert(new DataTypePreValueDto() { DataTypeNodeId = dtd.Id, SortOrder = 0, Value = "test1" }); + DatabaseContext.Database.Insert(new DataTypePreValueDto() { DataTypeNodeId = dtd.Id, SortOrder = 1, Value = "test2" }); + + //this will cache the result + using (var repository = creator()) + { + var collection = repository.GetPreValuesCollectionByDataTypeId(dtd.Id); + } + + var cached = cache.RuntimeCache.GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-"); + + Assert.IsNotNull(cached); + Assert.AreEqual(1, cached.Count()); + Assert.AreEqual(2, cached.Single().FormatAsDictionary().Count); + } + + [Test] + public void Can_Get_Pre_Value_As_String_With_Cache() + { + var provider = new PetaPocoUnitOfWorkProvider(); + var unitOfWork = provider.GetUnitOfWork(); + + var cache = new CacheHelper(new ObjectCacheRuntimeCacheProvider(), new StaticCacheProvider(), new StaticCacheProvider()); + + Func creator = () => new DataTypeDefinitionRepository( + unitOfWork, + NullCacheProvider.Current, cache); + + DataTypeDefinition dtd; + using (var repository = creator()) + { + dtd = new DataTypeDefinition(-1, new Guid(Constants.PropertyEditors.RadioButtonList)) { Name = "test" }; + repository.AddOrUpdate(dtd); + unitOfWork.Commit(); + } + + var id = DatabaseContext.Database.Insert(new DataTypePreValueDto() { DataTypeNodeId = dtd.Id, SortOrder = 0, Value = "test1" }); + DatabaseContext.Database.Insert(new DataTypePreValueDto() { DataTypeNodeId = dtd.Id, SortOrder = 1, Value = "test2" }); + + //this will cache the result + using (var repository = creator()) + { + var val = repository.GetPreValueAsString(Convert.ToInt32(id)); + } + + var cached = cache.RuntimeCache.GetCacheItemsByKeySearch(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-"); + + Assert.IsNotNull(cached); + Assert.AreEqual(1, cached.Count()); + Assert.AreEqual(2, cached.Single().FormatAsDictionary().Count); + + using (var repository = creator()) + { + //ensure it still gets resolved! + var val = repository.GetPreValueAsString(Convert.ToInt32(id)); + Assert.AreEqual("test1", val); + } + } + [TearDown] public override void TearDown() { diff --git a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs index f8c6de39cc..f91c08b455 100644 --- a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs @@ -129,11 +129,15 @@ namespace Umbraco.Web.Cache payloads.ForEach(payload => { //clear both the Id and Unique Id cache since we cache both in the legacy classes :( - ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch( - string.Format("{0}{1}", CacheKeys.DataTypeCacheKey, payload.Id)); - ApplicationContext.Current.ApplicationCache.ClearCacheByKeySearch( + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( + string.Format("{0}{1}", CacheKeys.DataTypeCacheKey, payload.Id)); + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( string.Format("{0}{1}", CacheKeys.DataTypeCacheKey, payload.UniqueId)); + //clears the prevalue cache + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( + string.Format("{0}{1}", CacheKeys.DataTypePreValuesCacheKey, payload.Id)); + PublishedContentType.ClearDataType(payload.Id); }); From 595b039ebe6d489225a921dee8fdadd28f705704 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 17 Apr 2014 17:41:08 +1000 Subject: [PATCH 18/25] Working on U4-4701 Fixup DataTypeService and repository with cache, pre-values are now persisted properly using units of work with it's own mini internal repo. --- src/Umbraco.Core/Models/PreValue.cs | 2 +- .../DataTypeDefinitionRepository.cs | 240 ++++++++++++++++++ .../IDataTypeDefinitionRepository.cs | 7 +- src/Umbraco.Core/Services/DataTypeService.cs | 149 ++++------- src/Umbraco.Core/Services/IDataTypeService.cs | 15 +- src/Umbraco.Core/Services/PackagingService.cs | 8 +- .../Services/DataTypeServiceTests.cs | 101 ++++++++ 7 files changed, 419 insertions(+), 103 deletions(-) diff --git a/src/Umbraco.Core/Models/PreValue.cs b/src/Umbraco.Core/Models/PreValue.cs index d0bdcc6d11..92cada007a 100644 --- a/src/Umbraco.Core/Models/PreValue.cs +++ b/src/Umbraco.Core/Models/PreValue.cs @@ -19,7 +19,7 @@ /// /// The value stored for the pre-value field /// - public string Value { get; private set; } + public string Value { get; set; } /// /// The database id for the pre-value field value diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index 423802fbb5..31deac0e94 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -14,6 +14,7 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Services; +using NullCacheProvider = Umbraco.Core.Persistence.Caching.NullCacheProvider; namespace Umbraco.Core.Persistence.Repositories { @@ -23,17 +24,20 @@ namespace Umbraco.Core.Persistence.Repositories internal class DataTypeDefinitionRepository : PetaPocoRepositoryBase, IDataTypeDefinitionRepository { private readonly CacheHelper _cacheHelper; + private DataTypePreValueRepository _preValRepository; public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper) : base(work) { _cacheHelper = cacheHelper; + _preValRepository = new DataTypePreValueRepository(work, NullCacheProvider.Current); } public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache, CacheHelper cacheHelper) : base(work, cache) { _cacheHelper = cacheHelper; + _preValRepository = new DataTypePreValueRepository(work, NullCacheProvider.Current); } private readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); @@ -126,6 +130,42 @@ namespace Umbraco.Core.Persistence.Repositories #region Unit of Work Implementation + public override void PersistUpdatedItem(IEntity entity) + { + if (entity is PreValue) + { + _preValRepository.PersistUpdatedItem(entity); + } + else + { + base.PersistUpdatedItem(entity); + } + } + + public override void PersistNewItem(IEntity entity) + { + if (entity is PreValue) + { + _preValRepository.PersistNewItem(entity); + } + else + { + base.PersistNewItem(entity); + } + } + + public override void PersistDeletedItem(IEntity entity) + { + if (entity is PreValue) + { + _preValRepository.PersistDeletedItem(entity); + } + else + { + base.PersistDeletedItem(entity); + } + } + protected override void PersistNewItem(IDataTypeDefinition entity) { ((DataTypeDefinition)entity).AddingEntity(); @@ -300,6 +340,82 @@ AND umbracoNode.id <> @id", } } + public void AddOrUpdatePreValues(int dataTypeId, IDictionary values) + { + var dtd = Get(dataTypeId); + if (dtd == null) + { + throw new InvalidOperationException("No data type found with id " + dataTypeId); + } + AddOrUpdatePreValues(dtd, values); + } + + public void AddOrUpdatePreValues(IDataTypeDefinition dataType, IDictionary values) + { + var currentVals = new DataTypePreValueDto[]{}; + if (dataType.HasIdentity) + { + //first just get all pre-values for this data type so we can compare them to see if we need to insert or update or replace + var sql = new Sql().Select("*") + .From() + .Where(dto => dto.DataTypeNodeId == dataType.Id) + .OrderBy(dto => dto.SortOrder); + currentVals = Database.Fetch(sql).ToArray(); + } + + //already existing, need to be updated + var valueIds = values.Where(x => x.Value.Id > 0).Select(x => x.Value.Id).ToArray(); + var existingByIds = currentVals.Where(x => valueIds.Contains(x.Id)).ToArray(); + + //These ones need to be removed from the db, they no longer exist in the new values + var deleteById = currentVals.Where(x => valueIds.Contains(x.Id) == false); + + foreach (var d in deleteById) + { + _preValRepository.Delete(new PreValueEntity + { + Alias = d.Alias, + Id = d.Id, + Value = d.Value, + DataType = dataType, + SortOrder = d.SortOrder + }); + } + + var sortOrder = 1; + + foreach (var pre in values) + { + var existing = existingByIds.FirstOrDefault(valueDto => valueDto.Id == pre.Value.Id); + if (existing != null) + { + existing.Value = pre.Value.Value; + existing.SortOrder = sortOrder; + _preValRepository.AddOrUpdate(new PreValueEntity + { + Alias = existing.Alias, + Id = existing.Id, + SortOrder = existing.SortOrder, + Value = existing.Value, + DataType = dataType, + }); + } + else + { + _preValRepository.AddOrUpdate(new PreValueEntity + { + Alias = pre.Key, + SortOrder = sortOrder, + Value = pre.Value.Value, + DataType = dataType, + }); + } + + sortOrder++; + } + + } + private string GetPrefixedCacheKey(int dataTypeId) { return CacheKeys.DataTypePreValuesCacheKey + dataTypeId + "-"; @@ -327,5 +443,129 @@ AND umbracoNode.id <> @id", return collection; } + + /// + /// Private class to handle pre-value crud based on units of work with transactions + /// + public class PreValueEntity : Entity, IAggregateRoot + { + public string Value { get; set; } + public string Alias { get; set; } + public IDataTypeDefinition DataType { get; set; } + public int SortOrder { get; set; } + } + + /// + /// Private class to handle pre-value crud based on standard principles and units of work with transactions + /// + private class DataTypePreValueRepository : PetaPocoRepositoryBase + { + public DataTypePreValueRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache) + { + } + + #region Not implemented (don't need to for the purposes of this repo) + protected override PreValueEntity PerformGet(int id) + { + throw new NotImplementedException(); + } + + protected override IEnumerable PerformGetAll(params int[] ids) + { + throw new NotImplementedException(); + } + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotImplementedException(); + } + + protected override Sql GetBaseQuery(bool isCount) + { + throw new NotImplementedException(); + } + + protected override string GetBaseWhereClause() + { + throw new NotImplementedException(); + } + + protected override IEnumerable GetDeleteClauses() + { + return new List(); + } + + protected override Guid NodeObjectTypeId + { + get { throw new NotImplementedException(); } + } + #endregion + + protected override void PersistDeletedItem(PreValueEntity entity) + { + Database.Execute( + "DELETE FROM cmsDataTypePreValues WHERE id=@Id", + new { Id = entity.Id }); + } + + protected override void PersistNewItem(PreValueEntity entity) + { + if (entity.DataType.HasIdentity == false) + { + throw new InvalidOperationException("Cannot insert a pre value for a data type that has no identity"); + } + + //Cannot add a duplicate alias + var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsDataTypePreValues +WHERE alias = @alias +AND datatypeNodeId = @dtdid", + new { alias = entity.Alias, dtdid = entity.DataType.Id }); + if (exists > 0) + { + throw new DuplicateNameException("A pre value with the alias " + entity.Alias + " already exists for this data type"); + } + + var dto = new DataTypePreValueDto + { + DataTypeNodeId = entity.DataType.Id, + Value = entity.Value, + SortOrder = entity.SortOrder, + Alias = entity.Alias + }; + Database.Insert(dto); + } + + protected override void PersistUpdatedItem(PreValueEntity entity) + { + if (entity.DataType.HasIdentity == false) + { + throw new InvalidOperationException("Cannot update a pre value for a data type that has no identity"); + } + + //Cannot change to a duplicate alias + var exists = Database.ExecuteScalar(@"SELECT COUNT(*) FROM cmsDataTypePreValues +WHERE alias = @alias +AND datatypeNodeId = @dtdid +AND id <> @id", + new { id = entity.Id, alias = entity.Alias, dtdid = entity.DataType.Id }); + if (exists > 0) + { + throw new DuplicateNameException("A pre value with the alias " + entity.Alias + " already exists for this data type"); + } + + var dto = new DataTypePreValueDto + { + DataTypeNodeId = entity.DataType.Id, + Id = entity.Id, + Value = entity.Value, + SortOrder = entity.SortOrder, + Alias = entity.Alias + }; + Database.Update(dto); + } + } + } + + } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs index 77fe5edc89..f04b90c2e0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IDataTypeDefinitionRepository.cs @@ -1,4 +1,6 @@ -using Umbraco.Core.Models; +using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { @@ -6,5 +8,8 @@ namespace Umbraco.Core.Persistence.Repositories { PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId); string GetPreValueAsString(int preValueId); + + void AddOrUpdatePreValues(IDataTypeDefinition dataType, IDictionary values); + void AddOrUpdatePreValues(int dataTypeId, IDictionary values); } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 45bb7016ef..78a340913e 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -153,17 +153,14 @@ namespace Umbraco.Core.Services if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this)) return; - using (new WriteLock(Locker)) + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) { - var uow = _uowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) - { - dataTypeDefinition.CreatorId = userId; - repository.AddOrUpdate(dataTypeDefinition); - uow.Commit(); + dataTypeDefinition.CreatorId = userId; + repository.AddOrUpdate(dataTypeDefinition); + uow.Commit(); - Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinition, false), this); - } + Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinition, false), this); } Audit.Add(AuditTypes.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); @@ -179,31 +176,29 @@ namespace Umbraco.Core.Services if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinitions), this)) return; - using (new WriteLock(Locker)) + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) { - var uow = _uowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) + foreach (var dataTypeDefinition in dataTypeDefinitions) { - foreach (var dataTypeDefinition in dataTypeDefinitions) - { - dataTypeDefinition.CreatorId = userId; - repository.AddOrUpdate(dataTypeDefinition); - } - uow.Commit(); - - Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinitions, false), this); + dataTypeDefinition.CreatorId = userId; + repository.AddOrUpdate(dataTypeDefinition); } + uow.Commit(); + + Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinitions, false), this); } + Audit.Add(AuditTypes.Save, string.Format("Save DataTypeDefinition performed by user"), userId, -1); } /// /// Saves a list of PreValues for a given DataTypeDefinition /// - /// Id of the DataTypeDefinition to save PreValues for + /// Id of the DataTypeDefinition to save PreValues for /// List of string values to save [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] - public void SavePreValues(int id, IEnumerable values) + public void SavePreValues(int dataTypeId, IEnumerable values) { //TODO: Should we raise an event here since we are really saving values for the data type? @@ -215,7 +210,7 @@ namespace Umbraco.Core.Services { var sortOrderObj = uow.Database.ExecuteScalar( - "SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = id }); + "SELECT max(sortorder) FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId", new { DataTypeId = dataTypeId }); int sortOrder; if (sortOrderObj == null || int.TryParse(sortOrderObj.ToString(), out sortOrder) == false) { @@ -224,7 +219,7 @@ namespace Umbraco.Core.Services foreach (var value in values) { - var dto = new DataTypePreValueDto { DataTypeNodeId = id, Value = value, SortOrder = sortOrder }; + var dto = new DataTypePreValueDto { DataTypeNodeId = dataTypeId, Value = value, SortOrder = sortOrder }; uow.Database.Insert(dto); sortOrder++; } @@ -238,26 +233,40 @@ namespace Umbraco.Core.Services /// /// Saves/updates the pre-values /// - /// + /// /// /// /// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors /// like 'dropdown list publishing keys' /// - public void SavePreValues(int id, IDictionary values) + public void SavePreValues(int dataTypeId, IDictionary values) + { + var dtd = this.GetDataTypeDefinitionById(dataTypeId); + if (dtd == null) + { + throw new InvalidOperationException("No data type found for id " + dataTypeId); + } + SavePreValues(dtd, values); + } + + /// + /// Saves/updates the pre-values + /// + /// + /// + /// + /// We need to actually look up each pre-value and maintain it's id if possible - this is because of silly property editors + /// like 'dropdown list publishing keys' + /// + public void SavePreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values) { //TODO: Should we raise an event here since we are really saving values for the data type? - using (new WriteLock(Locker)) + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) { - using (var uow = _uowProvider.GetUnitOfWork()) - { - using (var transaction = uow.Database.GetTransaction()) - { - AddOrUpdatePreValues(id, values, uow); - transaction.Complete(); - } - } + repository.AddOrUpdatePreValues(dataTypeDefinition, values); + uow.Commit(); } } @@ -272,73 +281,25 @@ namespace Umbraco.Core.Services if (Saving.IsRaisedEventCancelled(new SaveEventArgs(dataTypeDefinition), this)) return; - using (new WriteLock(Locker)) + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) { - var uow = (PetaPocoUnitOfWork)_uowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) - { - dataTypeDefinition.CreatorId = userId; - repository.AddOrUpdate(dataTypeDefinition); + dataTypeDefinition.CreatorId = userId; - //complete the transaction, but run the delegate before the db transaction is finalized - uow.Commit(database => AddOrUpdatePreValues(dataTypeDefinition.Id, values, uow)); + //add/update the dtd + repository.AddOrUpdate(dataTypeDefinition); - Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinition, false), this); - } + //add/update the prevalues + repository.AddOrUpdatePreValues(dataTypeDefinition, values); + + uow.Commit(); + + Saved.RaiseEvent(new SaveEventArgs(dataTypeDefinition, false), this); } Audit.Add(AuditTypes.Save, string.Format("Save DataTypeDefinition performed by user"), userId, dataTypeDefinition.Id); } - private void AddOrUpdatePreValues(int id, IDictionary preValueCollection, IDatabaseUnitOfWork uow) - { - //first just get all pre-values for this data type so we can compare them to see if we need to insert or update or replace - var sql = new Sql().Select("*") - .From() - .Where(dto => dto.DataTypeNodeId == id) - .OrderBy(dto => dto.SortOrder); - var currentVals = uow.Database.Fetch(sql).ToArray(); - - //already existing, need to be updated - var valueIds = preValueCollection.Where(x => x.Value.Id > 0).Select(x => x.Value.Id).ToArray(); - var existingByIds = currentVals.Where(x => valueIds.Contains(x.Id)).ToArray(); - - //These ones need to be removed from the db, they no longer exist in the new values - var deleteById = currentVals.Where(x => valueIds.Contains(x.Id) == false); - - foreach (var d in deleteById) - { - uow.Database.Execute( - "DELETE FROM cmsDataTypePreValues WHERE datatypeNodeId = @DataTypeId AND id=@Id", - new { DataTypeId = id, Id = d.Id }); - } - - var sortOrder = 1; - - foreach (var pre in preValueCollection) - { - var existing = existingByIds.FirstOrDefault(valueDto => valueDto.Id == pre.Value.Id); - if (existing != null) - { - existing.Value = pre.Value.Value; - existing.SortOrder = sortOrder; - uow.Database.Update(existing); - } - else - { - var dto = new DataTypePreValueDto - { - DataTypeNodeId = id, - Value = pre.Value.Value, - SortOrder = sortOrder, - Alias = pre.Key - }; - uow.Database.Insert(dto); - } - - sortOrder++; - } - } /// /// Deletes an diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index a0c53bc38a..4e66154203 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -93,17 +93,24 @@ namespace Umbraco.Core.Services /// /// Saves a list of PreValues for a given DataTypeDefinition /// - /// Id of the DataTypeDefinition to save PreValues for + /// Id of the DataTypeDefinition to save PreValues for /// List of string values to save [Obsolete("This should no longer be used, use the alternative SavePreValues or SaveDataTypeAndPreValues methods instead. This will only insert pre-values without keys")] - void SavePreValues(int id, IEnumerable values); + void SavePreValues(int dataTypeId, IEnumerable values); /// /// Saves a list of PreValues for a given DataTypeDefinition /// - /// Id of the DataTypeDefinition to save PreValues for + /// Id of the DataTypeDefinition to save PreValues for /// List of key/value pairs to save - void SavePreValues(int id, IDictionary values); + void SavePreValues(int dataTypeId, IDictionary values); + + /// + /// Saves a list of PreValues for a given DataTypeDefinition + /// + /// The DataTypeDefinition to save PreValues for + /// List of key/value pairs to save + void SavePreValues(IDataTypeDefinition dataTypeDefinition, IDictionary values); /// /// Saves the data type and it's prevalues diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 64c03a6426..902021b2e3 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -786,11 +786,13 @@ namespace Umbraco.Core.Services .Select(x => x.Attribute("Value").Value); var valuesWithKeys = prevaluesElement.Elements("PreValue") - .Where(x => ((string) x.Attribute("Alias")).IsNullOrWhiteSpace() == false) - .ToDictionary(key => (string) key.Attribute("Alias"), val => new PreValue((string) val.Attribute("Value"))); + .Where(x => ((string) x.Attribute("Alias")).IsNullOrWhiteSpace() == false) + .ToDictionary( + key => (string) key.Attribute("Alias"), + val => new PreValue((string) val.Attribute("Value"))); //save the values with keys - _dataTypeService.SavePreValues(dataTypeDefinition.Id, valuesWithKeys); + _dataTypeService.SavePreValues(dataTypeDefinition, valuesWithKeys); //save the values without keys (this is legacy) _dataTypeService.SavePreValues(dataTypeDefinition.Id, valuesWithoutKeys); diff --git a/src/Umbraco.Tests/Services/DataTypeServiceTests.cs b/src/Umbraco.Tests/Services/DataTypeServiceTests.cs index ad161cfa54..6235ae8a88 100644 --- a/src/Umbraco.Tests/Services/DataTypeServiceTests.cs +++ b/src/Umbraco.Tests/Services/DataTypeServiceTests.cs @@ -74,6 +74,36 @@ namespace Umbraco.Tests.Services var dataTypeService = ServiceContext.DataTypeService; var textfieldId = new Guid("ec15c1e5-9d90-422a-aa52-4f7622c63bea"); + // Act + IDataTypeDefinition dataTypeDefinition = new DataTypeDefinition(-1, textfieldId) { Name = "Testing prevals", DatabaseType = DataTypeDatabaseType.Ntext }; + dataTypeService.Save(dataTypeDefinition); + dataTypeService.SavePreValues(dataTypeDefinition, new Dictionary + { + {"preVal1", new PreValue("Hello")}, + {"preVal2", new PreValue("World")} + }); + //re-get + dataTypeDefinition = dataTypeService.GetDataTypeDefinitionById(dataTypeDefinition.Id); + var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id); + + // Assert + Assert.That(dataTypeDefinition, Is.Not.Null); + Assert.That(dataTypeDefinition.HasIdentity, Is.True); + Assert.AreEqual(true, preVals.IsDictionaryBased); + Assert.AreEqual(2, preVals.PreValuesAsDictionary.Keys.Count); + Assert.AreEqual("preVal1", preVals.PreValuesAsDictionary.Keys.First()); + Assert.AreEqual("preVal2", preVals.PreValuesAsDictionary.Keys.Last()); + Assert.AreEqual("Hello", preVals.PreValuesAsDictionary["preVal1"].Value); + Assert.AreEqual("World", preVals.PreValuesAsDictionary["preVal2"].Value); + } + + [Test] + public void DataTypeService_Can_Persist_Dtd_And_Dictionary_Based_Pre_Values() + { + // Arrange + var dataTypeService = ServiceContext.DataTypeService; + var textfieldId = new Guid("ec15c1e5-9d90-422a-aa52-4f7622c63bea"); + // Act IDataTypeDefinition dataTypeDefinition = new DataTypeDefinition(-1, textfieldId) { Name = "Testing prevals", DatabaseType = DataTypeDatabaseType.Ntext }; dataTypeService.SaveDataTypeAndPreValues(dataTypeDefinition, new Dictionary @@ -96,6 +126,77 @@ namespace Umbraco.Tests.Services Assert.AreEqual("World", preVals.PreValuesAsDictionary["preVal2"].Value); } + [Test] + public void DataTypeService_Can_Update_Pre_Values() + { + // Arrange + var dataTypeService = ServiceContext.DataTypeService; + var textfieldId = new Guid("ec15c1e5-9d90-422a-aa52-4f7622c63bea"); + + // Act + IDataTypeDefinition dataTypeDefinition = new DataTypeDefinition(-1, textfieldId) { Name = "Testing prevals", DatabaseType = DataTypeDatabaseType.Ntext }; + dataTypeService.SaveDataTypeAndPreValues(dataTypeDefinition, new Dictionary + { + {"preVal1", new PreValue("Hello")}, + {"preVal2", new PreValue("World")} + }); + //re-get + dataTypeDefinition = dataTypeService.GetDataTypeDefinitionById(dataTypeDefinition.Id); + var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id); + + //update them (ensure Ids are there!) + var asDictionary = preVals.FormatAsDictionary(); + asDictionary["preVal1"].Value = "Hello2"; + asDictionary["preVal2"].Value = "World2"; + + dataTypeService.SavePreValues(dataTypeDefinition, asDictionary); + + var preValsAgain = dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id); + + // Assert + + Assert.AreEqual(preVals.PreValuesAsDictionary.Values.First().Id, preValsAgain.PreValuesAsDictionary.Values.First().Id); + Assert.AreEqual(preVals.PreValuesAsDictionary.Values.Last().Id, preValsAgain.PreValuesAsDictionary.Values.Last().Id); + Assert.AreEqual("preVal1", preValsAgain.PreValuesAsDictionary.Keys.First()); + Assert.AreEqual("preVal2", preValsAgain.PreValuesAsDictionary.Keys.Last()); + Assert.AreEqual("Hello2", preValsAgain.PreValuesAsDictionary["preVal1"].Value); + Assert.AreEqual("World2", preValsAgain.PreValuesAsDictionary["preVal2"].Value); + } + + [Test] + public void DataTypeService_Can_Remove_Pre_Value() + { + // Arrange + var dataTypeService = ServiceContext.DataTypeService; + var textfieldId = new Guid("ec15c1e5-9d90-422a-aa52-4f7622c63bea"); + + // Act + IDataTypeDefinition dataTypeDefinition = new DataTypeDefinition(-1, textfieldId) { Name = "Testing prevals", DatabaseType = DataTypeDatabaseType.Ntext }; + dataTypeService.SaveDataTypeAndPreValues(dataTypeDefinition, new Dictionary + { + {"preVal1", new PreValue("Hello")}, + {"preVal2", new PreValue("World")} + }); + //re-get + dataTypeDefinition = dataTypeService.GetDataTypeDefinitionById(dataTypeDefinition.Id); + var preVals = dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id); + + //update them (ensure Ids are there!) + var asDictionary = preVals.FormatAsDictionary(); + asDictionary.Remove("preVal2"); + + dataTypeService.SavePreValues(dataTypeDefinition, asDictionary); + + var preValsAgain = dataTypeService.GetPreValuesCollectionByDataTypeId(dataTypeDefinition.Id); + + // Assert + + Assert.AreEqual(1, preValsAgain.FormatAsDictionary().Count); + Assert.AreEqual(preVals.PreValuesAsDictionary.Values.First().Id, preValsAgain.PreValuesAsDictionary.Values.First().Id); + Assert.AreEqual("preVal1", preValsAgain.PreValuesAsDictionary.Keys.First()); + + } + [Test] public void DataTypeService_Can_Persist_Array_Based_Pre_Values() { From 1a13366cc96982e78af0a152d93f457c4b88e5a5 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 17 Apr 2014 18:03:34 +1000 Subject: [PATCH 19/25] Working on U4-4701 Fixup DataTypeService and repository with cache - the deletion logic all now happens in the transaction before it was done quite poorly for updating content types :( --- .../DataTypeDefinitionRepository.cs | 42 +++++++++++++++++-- .../Repositories/RepositoryBase.cs | 2 +- .../Persistence/RepositoryFactory.cs | 3 +- src/Umbraco.Core/Services/DataTypeService.cs | 26 +----------- .../DataTypeDefinitionRepositoryTest.cs | 15 ++++--- 5 files changed, 54 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index 31deac0e94..2da9829ece 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -24,19 +24,24 @@ namespace Umbraco.Core.Persistence.Repositories internal class DataTypeDefinitionRepository : PetaPocoRepositoryBase, IDataTypeDefinitionRepository { private readonly CacheHelper _cacheHelper; - private DataTypePreValueRepository _preValRepository; + private readonly IContentTypeRepository _contentTypeRepository; + private readonly DataTypePreValueRepository _preValRepository; - public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper) + public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, + IContentTypeRepository contentTypeRepository) : base(work) { _cacheHelper = cacheHelper; + _contentTypeRepository = contentTypeRepository; _preValRepository = new DataTypePreValueRepository(work, NullCacheProvider.Current); } - public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache, CacheHelper cacheHelper) + public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache, CacheHelper cacheHelper, + IContentTypeRepository contentTypeRepository) : base(work, cache) { _cacheHelper = cacheHelper; + _contentTypeRepository = contentTypeRepository; _preValRepository = new DataTypePreValueRepository(work, NullCacheProvider.Current); } @@ -96,6 +101,37 @@ namespace Umbraco.Core.Persistence.Repositories } } + /// + /// Override the delete method so that we can ensure that all related content type's are updated as part of the overall transaction + /// + /// + public override void Delete(IDataTypeDefinition entity) + { + //Find ContentTypes using this IDataTypeDefinition on a PropertyType + var query = Query.Builder.Where(x => x.DataTypeDefinitionId == entity.Id); + var contentTypes = _contentTypeRepository.GetByQuery(query); + + //Loop through the list of results and remove the PropertyTypes that references the DataTypeDefinition that is being deleted + foreach (var contentType in contentTypes) + { + if (contentType == null) continue; + + foreach (var group in contentType.PropertyGroups) + { + var types = @group.PropertyTypes.Where(x => x.DataTypeDefinitionId == entity.Id).ToList(); + foreach (var propertyType in types) + { + @group.PropertyTypes.Remove(propertyType); + } + } + + _contentTypeRepository.AddOrUpdate(contentType); + } + + //call the base method to queue the deletion of this data type + base.Delete(entity); + } + #endregion #region Overrides of PetaPocoRepositoryBase diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index 737ed7daef..e29413aa52 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -69,7 +69,7 @@ namespace Umbraco.Core.Persistence.Repositories /// Deletes the passed in entity /// /// - public void Delete(TEntity entity) + public virtual void Delete(TEntity entity) { if(_work != null) { diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 39488dd951..b7bf0062c6 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -63,7 +63,8 @@ namespace Umbraco.Core.Persistence return new DataTypeDefinitionRepository( uow, _disableAllCache ? (IRepositoryCacheProvider)NullCacheProvider.Current : RuntimeCacheProvider.Current, - _cacheHelper); + _cacheHelper, + CreateContentTypeRepository(uow)); } public virtual IDictionaryRepository CreateDictionaryRepository(IDatabaseUnitOfWork uow) diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index 78a340913e..b7a22be6fd 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -316,31 +316,9 @@ namespace Umbraco.Core.Services return; var uow = _uowProvider.GetUnitOfWork(); - using (var repository = _repositoryFactory.CreateContentTypeRepository(uow)) + using (var repository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow)) { - //Find ContentTypes using this IDataTypeDefinition on a PropertyType - var query = Query.Builder.Where(x => x.DataTypeDefinitionId == dataTypeDefinition.Id); - var contentTypes = repository.GetByQuery(query); - - //Loop through the list of results and remove the PropertyTypes that references the DataTypeDefinition that is being deleted - foreach (var contentType in contentTypes) - { - if (contentType == null) continue; - - foreach (var group in contentType.PropertyGroups) - { - var types = @group.PropertyTypes.Where(x => x.DataTypeDefinitionId == dataTypeDefinition.Id).ToList(); - foreach (var propertyType in types) - { - @group.PropertyTypes.Remove(propertyType); - } - } - - repository.AddOrUpdate(contentType); - } - - var dataTypeRepository = _repositoryFactory.CreateDataTypeDefinitionRepository(uow); - dataTypeRepository.Delete(dataTypeDefinition); + repository.Delete(dataTypeDefinition); uow.Commit(); diff --git a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs index 6fc1d19181..e4f4be3074 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DataTypeDefinitionRepositoryTest.cs @@ -28,7 +28,10 @@ namespace Umbraco.Tests.Persistence.Repositories private DataTypeDefinitionRepository CreateRepository(IDatabaseUnitOfWork unitOfWork) { - var dataTypeDefinitionRepository = new DataTypeDefinitionRepository(unitOfWork, NullCacheProvider.Current, CacheHelper.CreateDisabledCacheHelper()); + var dataTypeDefinitionRepository = new DataTypeDefinitionRepository( + unitOfWork, NullCacheProvider.Current, CacheHelper.CreateDisabledCacheHelper(), + new ContentTypeRepository(unitOfWork, NullCacheProvider.Current, + new TemplateRepository(unitOfWork, NullCacheProvider.Current))); return dataTypeDefinitionRepository; } @@ -363,8 +366,9 @@ namespace Umbraco.Tests.Persistence.Repositories var cache = new CacheHelper(new ObjectCacheRuntimeCacheProvider(), new StaticCacheProvider(), new StaticCacheProvider()); Func creator = () => new DataTypeDefinitionRepository( - unitOfWork, - NullCacheProvider.Current, cache); + unitOfWork, NullCacheProvider.Current, cache, + new ContentTypeRepository(unitOfWork, NullCacheProvider.Current, + new TemplateRepository(unitOfWork, NullCacheProvider.Current))); DataTypeDefinition dtd; using (var repository = creator()) @@ -399,8 +403,9 @@ namespace Umbraco.Tests.Persistence.Repositories var cache = new CacheHelper(new ObjectCacheRuntimeCacheProvider(), new StaticCacheProvider(), new StaticCacheProvider()); Func creator = () => new DataTypeDefinitionRepository( - unitOfWork, - NullCacheProvider.Current, cache); + unitOfWork, NullCacheProvider.Current, cache, + new ContentTypeRepository(unitOfWork, NullCacheProvider.Current, + new TemplateRepository(unitOfWork, NullCacheProvider.Current))); DataTypeDefinition dtd; using (var repository = creator()) From cf6775ec3191e0637e39d139b65e80413147825d Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 17 Apr 2014 18:05:12 +1000 Subject: [PATCH 20/25] moves an internal class --- .../DataTypeDefinitionRepository.cs | 40 ++++++++++++++++++- src/Umbraco.Core/Services/DataTypeService.cs | 36 +---------------- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index 2da9829ece..dc73f3584a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -462,7 +462,7 @@ AND umbracoNode.id <> @id", //go get the data var dtos = Database.Fetch("WHERE datatypeNodeId = @Id", new { Id = dataTypeId }); var list = dtos.Select(x => new Tuple(new PreValue(x.Id, x.Value), x.Alias, x.SortOrder)).ToList(); - var collection = DataTypeService.PreValueConverter.ConvertToPreValuesCollection(list); + var collection = PreValueConverter.ConvertToPreValuesCollection(list); //now create the cache key, this needs to include all pre-value ids so that we can use this cached item in the GetPreValuesAsString method //the key will be: "UmbracoPreValDATATYPEID-CSVOFPREVALIDS @@ -600,7 +600,43 @@ AND id <> @id", Database.Update(dto); } } - + + internal static class PreValueConverter + { + /// + /// Converts the tuple to a pre-value collection + /// + /// + /// + internal static PreValueCollection ConvertToPreValuesCollection(IEnumerable> list) + { + //now we need to determine if they are dictionary based, otherwise they have to be array based + var dictionary = new Dictionary(); + + //need to check all of the keys, if there's only one and it is empty then it's an array + var keys = list.Select(x => x.Item2).Distinct().ToArray(); + if (keys.Length == 1 && keys[0].IsNullOrWhiteSpace()) + { + return new PreValueCollection(list.OrderBy(x => x.Item3).Select(x => x.Item1)); + } + + foreach (var item in list + .OrderBy(x => x.Item3) //we'll order them first so we maintain the order index in the dictionary + .GroupBy(x => x.Item2)) //group by alias + { + if (item.Count() > 1) + { + //if there's more than 1 item per key, then it cannot be a dictionary, just return the array + return new PreValueCollection(list.OrderBy(x => x.Item3).Select(x => x.Item1)); + } + + dictionary.Add(item.Key, item.First().Item1); + } + + return new PreValueCollection(dictionary); + } + } + } diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index b7a22be6fd..936e3ca89a 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -371,40 +371,6 @@ namespace Umbraco.Core.Services public static event TypedEventHandler> Saved; #endregion - internal static class PreValueConverter - { - /// - /// Converts the tuple to a pre-value collection - /// - /// - /// - internal static PreValueCollection ConvertToPreValuesCollection(IEnumerable> list) - { - //now we need to determine if they are dictionary based, otherwise they have to be array based - var dictionary = new Dictionary(); - - //need to check all of the keys, if there's only one and it is empty then it's an array - var keys = list.Select(x => x.Item2).Distinct().ToArray(); - if (keys.Length == 1 && keys[0].IsNullOrWhiteSpace()) - { - return new PreValueCollection(list.OrderBy(x => x.Item3).Select(x => x.Item1)); - } - - foreach (var item in list - .OrderBy(x => x.Item3) //we'll order them first so we maintain the order index in the dictionary - .GroupBy(x => x.Item2)) //group by alias - { - if (item.Count() > 1) - { - //if there's more than 1 item per key, then it cannot be a dictionary, just return the array - return new PreValueCollection(list.OrderBy(x => x.Item3).Select(x => x.Item1)); - } - - dictionary.Add(item.Key, item.First().Item1); - } - - return new PreValueCollection(dictionary); - } - } + } } \ No newline at end of file From 8c303893d2835117036ddc366218198952cbc645 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 17 Apr 2014 18:46:37 +1000 Subject: [PATCH 21/25] updates deep clone to have the false flag for resetting properties (we don't want to remember them when cloning) --- src/Umbraco.Core/Models/Content.cs | 2 +- src/Umbraco.Core/Models/ContentTypeCompositionBase.cs | 2 +- src/Umbraco.Core/Models/EntityBase/Entity.cs | 2 +- src/Umbraco.Core/Models/File.cs | 2 +- src/Umbraco.Core/Models/Member.cs | 2 +- src/Umbraco.Core/Models/Property.cs | 2 +- src/Umbraco.Core/Models/PropertyType.cs | 2 +- src/Umbraco.Core/Models/Template.cs | 2 +- src/Umbraco.Core/Models/UmbracoEntity.cs | 4 ++-- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 5409d9e217..05b78fe5dc 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -442,7 +442,7 @@ namespace Umbraco.Core.Models //need to manually clone this since it's not settable clone._contentType = (IContentType)ContentType.DeepClone(); - clone.ResetDirtyProperties(true); + clone.ResetDirtyProperties(false); return clone; diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 7569d31d8e..c73ae8ee95 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -225,7 +225,7 @@ namespace Umbraco.Core.Models //need to manually assign since this is an internal field and will not be automatically mapped clone.RemovedContentTypeKeyTracker = new List(); clone._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList(); - clone.ResetDirtyProperties(true); + clone.ResetDirtyProperties(false); return clone; } diff --git a/src/Umbraco.Core/Models/EntityBase/Entity.cs b/src/Umbraco.Core/Models/EntityBase/Entity.cs index cc05d90214..b7d054c676 100644 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs @@ -238,7 +238,7 @@ namespace Umbraco.Core.Models.EntityBase var clone = (Entity)MemberwiseClone(); //Automatically deep clone ref properties that are IDeepCloneable DeepCloneHelper.DeepCloneRefProperties(this, clone); - clone.ResetDirtyProperties(true); + clone.ResetDirtyProperties(false); return clone; //Using data contract serializer - has issues diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index 0bd80956dd..d337b82bbe 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -113,7 +113,7 @@ namespace Umbraco.Core.Models clone._alias = Alias; clone._name = Name; - clone.ResetDirtyProperties(true); + clone.ResetDirtyProperties(false); return clone; } diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 722823e9f0..465a6c3b58 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -650,7 +650,7 @@ namespace Umbraco.Core.Models //need to manually clone this since it's not settable clone._contentType = (IMemberType)ContentType.DeepClone(); - clone.ResetDirtyProperties(true); + clone.ResetDirtyProperties(false); return clone; diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index 962a140390..f8696794b1 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -163,7 +163,7 @@ namespace Umbraco.Core.Models //need to manually assign since this is a readonly property clone._propertyType = (PropertyType)PropertyType.DeepClone(); - clone.ResetDirtyProperties(true); + clone.ResetDirtyProperties(false); return clone; } diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index 62576f615b..83a8c09a99 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -463,7 +463,7 @@ namespace Umbraco.Core.Models clone._propertyGroupId = new Lazy(() => propGroupId); } - clone.ResetDirtyProperties(true); + clone.ResetDirtyProperties(false); return clone; } diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 577c9358ad..a0bef57ae8 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -197,7 +197,7 @@ namespace Umbraco.Core.Models clone._alias = Alias; clone._name = Name; - clone.ResetDirtyProperties(true); + clone.ResetDirtyProperties(false); return clone; } diff --git a/src/Umbraco.Core/Models/UmbracoEntity.cs b/src/Umbraco.Core/Models/UmbracoEntity.cs index 7a89f51996..92bdf3c790 100644 --- a/src/Umbraco.Core/Models/UmbracoEntity.cs +++ b/src/Umbraco.Core/Models/UmbracoEntity.cs @@ -306,7 +306,7 @@ namespace Umbraco.Core.Models protected bool Equals(UmbracoProperty other) { - return DataTypeControlId.Equals(other.DataTypeControlId) && string.Equals(Value, other.Value); + return PropertyEditorAlias.Equals(other.PropertyEditorAlias) && string.Equals(Value, other.Value); } public override bool Equals(object obj) @@ -321,7 +321,7 @@ namespace Umbraco.Core.Models { unchecked { - return (DataTypeControlId.GetHashCode()*397) ^ (Value != null ? Value.GetHashCode() : 0); + return (PropertyEditorAlias.GetHashCode() * 397) ^ (Value != null ? Value.GetHashCode() : 0); } } } From 15f4de4ed0aff717621a4e06013a3d69fbdaab53 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 17 Apr 2014 18:52:07 +1000 Subject: [PATCH 22/25] fixes build issues --- src/Umbraco.Tests/Models/PropertyGroupTests.cs | 8 ++++---- src/Umbraco.Tests/Models/PropertyTypeTests.cs | 4 ++-- src/Umbraco.Tests/Models/UmbracoEntityTests.cs | 8 ++++---- src/Umbraco.Tests/Services/PreValueConverterTests.cs | 7 ++++--- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Tests/Models/PropertyGroupTests.cs b/src/Umbraco.Tests/Models/PropertyGroupTests.cs index f3dad6546f..b6b15c83e8 100644 --- a/src/Umbraco.Tests/Models/PropertyGroupTests.cs +++ b/src/Umbraco.Tests/Models/PropertyGroupTests.cs @@ -14,7 +14,7 @@ namespace Umbraco.Tests.Models var pg = new PropertyGroup( new PropertyTypeCollection(new[] { - new PropertyType(Guid.NewGuid(), DataTypeDatabaseType.Nvarchar) + new PropertyType("TestPropertyEditor", DataTypeDatabaseType.Nvarchar) { Id = 3, Alias = "test", @@ -31,7 +31,7 @@ namespace Umbraco.Tests.Models ValidationRegExp = "xxxx", DataTypeDatabaseType = DataTypeDatabaseType.Nvarchar }, - new PropertyType(Guid.NewGuid(), DataTypeDatabaseType.Nvarchar) + new PropertyType("TestPropertyEditor", DataTypeDatabaseType.Nvarchar) { Id = 4, Alias = "test2", @@ -95,7 +95,7 @@ namespace Umbraco.Tests.Models var pg = new PropertyGroup( new PropertyTypeCollection(new[] { - new PropertyType(Guid.NewGuid(), DataTypeDatabaseType.Nvarchar) + new PropertyType("TestPropertyEditor", DataTypeDatabaseType.Nvarchar) { Id = 3, Alias = "test", @@ -112,7 +112,7 @@ namespace Umbraco.Tests.Models ValidationRegExp = "xxxx", DataTypeDatabaseType = DataTypeDatabaseType.Nvarchar }, - new PropertyType(Guid.NewGuid(), DataTypeDatabaseType.Nvarchar) + new PropertyType("TestPropertyEditor2", DataTypeDatabaseType.Nvarchar) { Id = 4, Alias = "test2", diff --git a/src/Umbraco.Tests/Models/PropertyTypeTests.cs b/src/Umbraco.Tests/Models/PropertyTypeTests.cs index 6acb9aca7e..e051791faf 100644 --- a/src/Umbraco.Tests/Models/PropertyTypeTests.cs +++ b/src/Umbraco.Tests/Models/PropertyTypeTests.cs @@ -11,7 +11,7 @@ namespace Umbraco.Tests.Models [Test] public void Can_Deep_Clone() { - var pt = new PropertyType(Guid.NewGuid(), DataTypeDatabaseType.Nvarchar) + var pt = new PropertyType("TestPropertyEditor", DataTypeDatabaseType.Nvarchar) { Id = 3, Alias = "test", @@ -61,7 +61,7 @@ namespace Umbraco.Tests.Models { var ss = new SerializationService(new JsonNetSerializer()); - var pt = new PropertyType(Guid.NewGuid(), DataTypeDatabaseType.Nvarchar) + var pt = new PropertyType("TestPropertyEditor", DataTypeDatabaseType.Nvarchar) { Id = 3, Alias = "test", diff --git a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs index 813594b54d..ac70364e67 100644 --- a/src/Umbraco.Tests/Models/UmbracoEntityTests.cs +++ b/src/Umbraco.Tests/Models/UmbracoEntityTests.cs @@ -50,12 +50,12 @@ namespace Umbraco.Tests.Models item.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty() { Value = "test", - DataTypeControlId = Guid.NewGuid() + PropertyEditorAlias = "TestPropertyEditor" }); item.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty() { Value = "test2", - DataTypeControlId = Guid.NewGuid() + PropertyEditorAlias = "TestPropertyEditor2" }); var clone = (UmbracoEntity)item.DeepClone(); @@ -128,12 +128,12 @@ namespace Umbraco.Tests.Models item.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty() { Value = "test", - DataTypeControlId = Guid.NewGuid() + PropertyEditorAlias = "TestPropertyEditor" }); item.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty() { Value = "test2", - DataTypeControlId = Guid.NewGuid() + PropertyEditorAlias = "TestPropertyEditor2" }); var result = ss.ToStream(item); diff --git a/src/Umbraco.Tests/Services/PreValueConverterTests.cs b/src/Umbraco.Tests/Services/PreValueConverterTests.cs index 63149a28fe..b4eb34ee9f 100644 --- a/src/Umbraco.Tests/Services/PreValueConverterTests.cs +++ b/src/Umbraco.Tests/Services/PreValueConverterTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Services; namespace Umbraco.Tests.Services @@ -21,7 +22,7 @@ namespace Umbraco.Tests.Services new Tuple(new PreValue(13, "value4"), "key4", 1) }; - var result = DataTypeService.PreValueConverter.ConvertToPreValuesCollection(list); + var result = DataTypeDefinitionRepository.PreValueConverter.ConvertToPreValuesCollection(list); Assert.Throws(() => { @@ -47,7 +48,7 @@ namespace Umbraco.Tests.Services new Tuple(new PreValue(13, "value4"), "", 1) }; - var result = DataTypeService.PreValueConverter.ConvertToPreValuesCollection(list); + var result = DataTypeDefinitionRepository.PreValueConverter.ConvertToPreValuesCollection(list); Assert.Throws(() => { @@ -73,7 +74,7 @@ namespace Umbraco.Tests.Services new Tuple(new PreValue(13, "value4"), "key4", 1) }; - var result = DataTypeService.PreValueConverter.ConvertToPreValuesCollection(list); + var result = DataTypeDefinitionRepository.PreValueConverter.ConvertToPreValuesCollection(list); Assert.Throws(() => { From fee23fac0a1e41d4ed6d4dff15861595678e7b28 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 17 Apr 2014 18:54:55 +1000 Subject: [PATCH 23/25] updates deep clone to have the false flag for resetting properties (we don't want to remember them when cloning) Conflicts: src/Umbraco.Core/Models/Member.cs --- src/Umbraco.Core/Models/Content.cs | 2 +- src/Umbraco.Core/Models/ContentTypeCompositionBase.cs | 2 +- src/Umbraco.Core/Models/EntityBase/Entity.cs | 2 +- src/Umbraco.Core/Models/File.cs | 2 +- src/Umbraco.Core/Models/Member.cs | 2 +- src/Umbraco.Core/Models/Property.cs | 2 +- src/Umbraco.Core/Models/PropertyType.cs | 2 +- src/Umbraco.Core/Models/Template.cs | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/Models/Content.cs b/src/Umbraco.Core/Models/Content.cs index 5409d9e217..05b78fe5dc 100644 --- a/src/Umbraco.Core/Models/Content.cs +++ b/src/Umbraco.Core/Models/Content.cs @@ -442,7 +442,7 @@ namespace Umbraco.Core.Models //need to manually clone this since it's not settable clone._contentType = (IContentType)ContentType.DeepClone(); - clone.ResetDirtyProperties(true); + clone.ResetDirtyProperties(false); return clone; diff --git a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs index 7569d31d8e..c73ae8ee95 100644 --- a/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs +++ b/src/Umbraco.Core/Models/ContentTypeCompositionBase.cs @@ -225,7 +225,7 @@ namespace Umbraco.Core.Models //need to manually assign since this is an internal field and will not be automatically mapped clone.RemovedContentTypeKeyTracker = new List(); clone._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList(); - clone.ResetDirtyProperties(true); + clone.ResetDirtyProperties(false); return clone; } diff --git a/src/Umbraco.Core/Models/EntityBase/Entity.cs b/src/Umbraco.Core/Models/EntityBase/Entity.cs index cc05d90214..b7d054c676 100644 --- a/src/Umbraco.Core/Models/EntityBase/Entity.cs +++ b/src/Umbraco.Core/Models/EntityBase/Entity.cs @@ -238,7 +238,7 @@ namespace Umbraco.Core.Models.EntityBase var clone = (Entity)MemberwiseClone(); //Automatically deep clone ref properties that are IDeepCloneable DeepCloneHelper.DeepCloneRefProperties(this, clone); - clone.ResetDirtyProperties(true); + clone.ResetDirtyProperties(false); return clone; //Using data contract serializer - has issues diff --git a/src/Umbraco.Core/Models/File.cs b/src/Umbraco.Core/Models/File.cs index 0bd80956dd..d337b82bbe 100644 --- a/src/Umbraco.Core/Models/File.cs +++ b/src/Umbraco.Core/Models/File.cs @@ -113,7 +113,7 @@ namespace Umbraco.Core.Models clone._alias = Alias; clone._name = Name; - clone.ResetDirtyProperties(true); + clone.ResetDirtyProperties(false); return clone; } diff --git a/src/Umbraco.Core/Models/Member.cs b/src/Umbraco.Core/Models/Member.cs index 019b644afc..7ad25efb67 100644 --- a/src/Umbraco.Core/Models/Member.cs +++ b/src/Umbraco.Core/Models/Member.cs @@ -641,7 +641,7 @@ namespace Umbraco.Core.Models //need to manually clone this since it's not settable clone._contentType = (IMemberType)ContentType.DeepClone(); - clone.ResetDirtyProperties(true); + clone.ResetDirtyProperties(false); return clone; diff --git a/src/Umbraco.Core/Models/Property.cs b/src/Umbraco.Core/Models/Property.cs index 165074dd99..99985ad39a 100644 --- a/src/Umbraco.Core/Models/Property.cs +++ b/src/Umbraco.Core/Models/Property.cs @@ -154,7 +154,7 @@ namespace Umbraco.Core.Models //need to manually assign since this is a readonly property clone._propertyType = (PropertyType)PropertyType.DeepClone(); - clone.ResetDirtyProperties(true); + clone.ResetDirtyProperties(false); return clone; } diff --git a/src/Umbraco.Core/Models/PropertyType.cs b/src/Umbraco.Core/Models/PropertyType.cs index cdc1990766..5f583f3d8e 100644 --- a/src/Umbraco.Core/Models/PropertyType.cs +++ b/src/Umbraco.Core/Models/PropertyType.cs @@ -446,7 +446,7 @@ namespace Umbraco.Core.Models clone._propertyGroupId = new Lazy(() => propGroupId); } - clone.ResetDirtyProperties(true); + clone.ResetDirtyProperties(false); return clone; } diff --git a/src/Umbraco.Core/Models/Template.cs b/src/Umbraco.Core/Models/Template.cs index 2ea27939c5..2a2f59d132 100644 --- a/src/Umbraco.Core/Models/Template.cs +++ b/src/Umbraco.Core/Models/Template.cs @@ -205,7 +205,7 @@ namespace Umbraco.Core.Models clone._alias = Alias; clone._name = Name; - clone.ResetDirtyProperties(true); + clone.ResetDirtyProperties(false); return clone; } From 51309e62ec67b50eb9733893e1949a430aa6e78e Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 17 Apr 2014 19:55:17 +1000 Subject: [PATCH 24/25] Fixes: U4-4714 Sql Azure no longer a database option in 7.1 --- .../src/installer/steps/database.controller.js | 3 ++- src/Umbraco.Web/Install/Controllers/InstallApiController.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js index 5ac118e1f4..5b3e174930 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js +++ b/src/Umbraco.Web.UI.Client/src/installer/steps/database.controller.js @@ -4,7 +4,8 @@ angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseControll $scope.dbs = [ {name: 'Microsoft SQL Server Compact (SQL CE)', id: 0}, {name: 'Microsoft SQL Server', id: 1}, - {name: 'MySQL', id: 2}, + { name: 'Microsoft SQL Azure', id: 3 }, + { name: 'MySQL', id: 2 }, {name: 'Custom connection string', id: -1}]; if(installerService.status.current.model.dbType === undefined){ diff --git a/src/Umbraco.Web/Install/Controllers/InstallApiController.cs b/src/Umbraco.Web/Install/Controllers/InstallApiController.cs index 1d88b9b9a2..6041e23ca5 100644 --- a/src/Umbraco.Web/Install/Controllers/InstallApiController.cs +++ b/src/Umbraco.Web/Install/Controllers/InstallApiController.cs @@ -55,7 +55,8 @@ namespace Umbraco.Web.Install.Controllers public bool PostValidateDatabaseConnection(DatabaseModel model) { var dbHelper = new DatabaseHelper(); - return dbHelper.CheckConnection(model, ApplicationContext); + var canConnect = dbHelper.CheckConnection(model, ApplicationContext); + return canConnect; } /// From b13251778e83ef192d4d1b1e81cb802ef56ca124 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 17 Apr 2014 20:05:55 +1000 Subject: [PATCH 25/25] Fixes: U4-4713 Provider Name not set when using Custom Connection String from the Installer --- src/Umbraco.Core/DatabaseContext.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index 1eecd5a877..dbfd446c39 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -149,7 +149,14 @@ namespace Umbraco.Core /// public void ConfigureDatabaseConnection(string connectionString) { - SaveConnectionString(connectionString, string.Empty); + var provider = DbConnectionExtensions.DetectProviderFromConnectionString(connectionString); + var databaseProvider = provider.ToString(); + var providerName = "System.Data.SqlClient"; + if (databaseProvider.ToLower().Contains("mysql")) + { + providerName = "MySql.Data.MySqlClient"; + } + SaveConnectionString(connectionString, providerName); Initialize(string.Empty); } @@ -169,7 +176,7 @@ namespace Umbraco.Core SaveConnectionString(connectionString, providerName); Initialize(providerName); } - + public string GetDatabaseConnectionString(string server, string databaseName, string user, string password, string databaseProvider, out string providerName) { providerName = "System.Data.SqlClient";