From 4f40fff5ee009a3830bb8770aa5ab66fe04be6ca Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 6 Jan 2016 14:17:51 +0100 Subject: [PATCH] Moves DeepCloneRuntimeCacheProvider to Cache namespace. Creates DeepCloneableList + tests. Updates RepositoryBase to use DeepCloneableList when GetAllCacheAsCollection is used (so that all entries that get cached are deep cloned into and out-of the cache). Adds test for DeepCloneRuntimeCacheProvider for dealing with this list type. --- .../DeepCloneRuntimeCacheProvider.cs | 3 +- .../Collections/DeepCloneableList.cs | 108 ++++++++++++++ .../Repositories/RepositoryBase.cs | 5 +- .../Persistence/RepositoryFactory.cs | 1 + src/Umbraco.Core/Umbraco.Core.csproj | 3 +- .../DeepCloneRuntimeCacheProviderTests.cs | 19 +++ .../Collections/DeepCloneableListTests.cs | 139 ++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 8 files changed, 274 insertions(+), 5 deletions(-) rename src/Umbraco.Core/{Persistence/Repositories => Cache}/DeepCloneRuntimeCacheProvider.cs (98%) create mode 100644 src/Umbraco.Core/Collections/DeepCloneableList.cs create mode 100644 src/Umbraco.Tests/Collections/DeepCloneableListTests.cs diff --git a/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs similarity index 98% rename from src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs rename to src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs index 0b5b42660d..861c6b803e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DeepCloneRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/DeepCloneRuntimeCacheProvider.cs @@ -2,11 +2,10 @@ using System; using System.Collections.Generic; using System.Linq; using System.Web.Caching; -using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; -namespace Umbraco.Core.Persistence.Repositories +namespace Umbraco.Core.Cache { /// /// A wrapper for any IRuntimeCacheProvider that ensures that all inserts and returns diff --git a/src/Umbraco.Core/Collections/DeepCloneableList.cs b/src/Umbraco.Core/Collections/DeepCloneableList.cs new file mode 100644 index 0000000000..365bf53b06 --- /dev/null +++ b/src/Umbraco.Core/Collections/DeepCloneableList.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Umbraco.Core.Models; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Collections +{ + /// + /// A List that can be deep cloned with deep cloned elements and can reset the collection's items dirty flags + /// + /// + internal class DeepCloneableList : List, IDeepCloneable, IRememberBeingDirty + { + /// + /// Initializes a new instance of the class that is empty and has the default initial capacity. + /// + public DeepCloneableList() + { + } + + /// + /// Initializes a new instance of the class that contains elements copied from the specified collection and has sufficient capacity to accommodate the number of elements copied. + /// + /// The collection whose elements are copied to the new list. is null. + public DeepCloneableList(IEnumerable collection) : base(collection) + { + } + + /// + /// Creates a new list and adds each element as a deep cloned element if it is of type IDeepCloneable + /// + /// + public object DeepClone() + { + var newList = new DeepCloneableList(); + foreach (var item in this) + { + var dc = item as IDeepCloneable; + if (dc != null) + { + newList.Add((T) dc.DeepClone()); + } + else + { + newList.Add(item); + } + } + return newList; + } + + public bool IsDirty() + { + return this.OfType().Any(x => x.IsDirty()); + } + + public bool WasDirty() + { + return this.OfType().Any(x => x.WasDirty()); + } + + /// + /// Always returns false, the list has no properties we need to report + /// + /// + /// + public bool IsPropertyDirty(string propName) + { + return false; + } + + /// + /// Always returns false, the list has no properties we need to report + /// + /// + /// + public bool WasPropertyDirty(string propertyName) + { + return false; + } + + public void ResetDirtyProperties() + { + foreach (var dc in this.OfType()) + { + dc.ResetDirtyProperties(); + } + } + + public void ForgetPreviouslyDirtyProperties() + { + foreach (var dc in this.OfType()) + { + dc.ForgetPreviouslyDirtyProperties(); + } + } + + public void ResetDirtyProperties(bool rememberPreviouslyChangedProperties) + { + foreach (var dc in this.OfType()) + { + dc.ResetDirtyProperties(rememberPreviouslyChangedProperties); + } + } + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index d5febf6e0f..2ce3537bb9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Cache; +using Umbraco.Core.Collections; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; @@ -173,7 +174,7 @@ namespace Umbraco.Core.Persistence.Repositories TEntity[] allEntities; if (RepositoryCacheOptions.GetAllCacheAsCollection) { - var found = RuntimeCache.GetCacheItem>(GetCacheTypeKey()); + var found = RuntimeCache.GetCacheItem>(GetCacheTypeKey()); allEntities = found == null ? new TEntity[] {} : found.WhereNotNull().ToArray(); } else @@ -229,7 +230,7 @@ namespace Umbraco.Core.Persistence.Repositories if (RepositoryCacheOptions.GetAllCacheAsCollection) { //when this is true, we don't want to cache each item individually, we want to cache the result as a single collection - RuntimeCache.InsertCacheItem(GetCacheTypeKey(), () => entityCollection.ToList()); + RuntimeCache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList(entityCollection)); return entityCollection; } diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index f3d1702946..d99ae8f464 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -1,5 +1,6 @@ using Umbraco.Core.Configuration; using System; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Logging; diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index bf28645754..d294bdaddf 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -176,6 +176,7 @@ + @@ -441,7 +442,7 @@ - + diff --git a/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs b/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs index 44deb1da40..63225f6725 100644 --- a/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs +++ b/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs @@ -4,9 +4,11 @@ using System.Web; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Collections; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Persistence.Repositories; +using Umbraco.Tests.Collections; namespace Umbraco.Tests.Cache { @@ -36,6 +38,23 @@ namespace Umbraco.Tests.Cache get { return _provider; } } + [Test] + public void Clones_List() + { + var original = new DeepCloneableList(); + original.Add(new DeepCloneableListTests.TestClone()); + original.Add(new DeepCloneableListTests.TestClone()); + original.Add(new DeepCloneableListTests.TestClone()); + + var val = _provider.GetCacheItem>("test", () => original); + + Assert.AreEqual(original.Count, val.Count); + foreach (var item in val) + { + Assert.IsTrue(item.IsClone); + } + } + [Test] public void Ensures_Cloned_And_Reset() { diff --git a/src/Umbraco.Tests/Collections/DeepCloneableListTests.cs b/src/Umbraco.Tests/Collections/DeepCloneableListTests.cs new file mode 100644 index 0000000000..fcc50df60c --- /dev/null +++ b/src/Umbraco.Tests/Collections/DeepCloneableListTests.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NUnit.Framework; +using Umbraco.Core.Collections; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Collections +{ + [TestFixture] + public class DeepCloneableListTests + { + [Test] + public void Deep_Clones_All_Elements() + { + var list = new DeepCloneableList(); + list.Add(new TestClone()); + list.Add(new TestClone()); + list.Add(new TestClone()); + + var cloned = list.DeepClone() as DeepCloneableList; + + Assert.IsNotNull(cloned); + Assert.AreNotSame(list, cloned); + Assert.AreEqual(list.Count, cloned.Count); + } + + [Test] + public void Clones_Each_Item() + { + var list = new DeepCloneableList(); + list.Add(new TestClone()); + list.Add(new TestClone()); + list.Add(new TestClone()); + + var cloned = (DeepCloneableList) list.DeepClone(); + + foreach (var item in cloned) + { + Assert.IsTrue(item.IsClone); + } + } + + [Test] + public void Cloned_Sequence_Equals() + { + var list = new DeepCloneableList(); + list.Add(new TestClone()); + list.Add(new TestClone()); + list.Add(new TestClone()); + + var cloned = (DeepCloneableList)list.DeepClone(); + + //Test that each item in the sequence is equal - based on the equality comparer of TestClone (i.e. it's ID) + Assert.IsTrue(list.SequenceEqual(cloned)); + + //Test that each instance in the list is not the same one + foreach (var item in list) + { + var clone = cloned.Single(x => x.Id == item.Id); + Assert.AreNotSame(item, clone); + } + } + + public class TestClone : IDeepCloneable, IEquatable + { + public TestClone(Guid id) + { + Id = id; + IsClone = true; + } + + public TestClone() + { + Id = Guid.NewGuid(); + } + + public Guid Id { get; private set; } + public bool IsClone { get; private set; } + + public object DeepClone() + { + return new TestClone(Id); + } + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + public bool Equals(TestClone other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return Id.Equals(other.Id); + } + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// + /// true if the specified object is equal to the current object; otherwise, false. + /// + /// The object to compare with the current object. + 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((TestClone)obj); + } + + /// + /// Serves as the default hash function. + /// + /// + /// A hash code for the current object. + /// + public override int GetHashCode() + { + return Id.GetHashCode(); + } + + public static bool operator ==(TestClone left, TestClone right) + { + return Equals(left, right); + } + + public static bool operator !=(TestClone left, TestClone right) + { + return Equals(left, right) == false; + } + } + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 59a9166e5a..e689fe08f9 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -175,6 +175,7 @@ +