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.
This commit is contained in:
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// A wrapper for any IRuntimeCacheProvider that ensures that all inserts and returns
|
||||
108
src/Umbraco.Core/Collections/DeepCloneableList.cs
Normal file
108
src/Umbraco.Core/Collections/DeepCloneableList.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// A List that can be deep cloned with deep cloned elements and can reset the collection's items dirty flags
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class DeepCloneableList<T> : List<T>, IDeepCloneable, IRememberBeingDirty
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:System.Collections.Generic.List`1"/> class that is empty and has the default initial capacity.
|
||||
/// </summary>
|
||||
public DeepCloneableList()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="T:System.Collections.Generic.List`1"/> class that contains elements copied from the specified collection and has sufficient capacity to accommodate the number of elements copied.
|
||||
/// </summary>
|
||||
/// <param name="collection">The collection whose elements are copied to the new list.</param><exception cref="T:System.ArgumentNullException"><paramref name="collection"/> is null.</exception>
|
||||
public DeepCloneableList(IEnumerable<T> collection) : base(collection)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new list and adds each element as a deep cloned element if it is of type IDeepCloneable
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public object DeepClone()
|
||||
{
|
||||
var newList = new DeepCloneableList<T>();
|
||||
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<IRememberBeingDirty>().Any(x => x.IsDirty());
|
||||
}
|
||||
|
||||
public bool WasDirty()
|
||||
{
|
||||
return this.OfType<IRememberBeingDirty>().Any(x => x.WasDirty());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Always returns false, the list has no properties we need to report
|
||||
/// </summary>
|
||||
/// <param name="propName"></param>
|
||||
/// <returns></returns>
|
||||
public bool IsPropertyDirty(string propName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Always returns false, the list has no properties we need to report
|
||||
/// </summary>
|
||||
/// <param name="propertyName"></param>
|
||||
/// <returns></returns>
|
||||
public bool WasPropertyDirty(string propertyName)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ResetDirtyProperties()
|
||||
{
|
||||
foreach (var dc in this.OfType<IRememberBeingDirty>())
|
||||
{
|
||||
dc.ResetDirtyProperties();
|
||||
}
|
||||
}
|
||||
|
||||
public void ForgetPreviouslyDirtyProperties()
|
||||
{
|
||||
foreach (var dc in this.OfType<IRememberBeingDirty>())
|
||||
{
|
||||
dc.ForgetPreviouslyDirtyProperties();
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetDirtyProperties(bool rememberPreviouslyChangedProperties)
|
||||
{
|
||||
foreach (var dc in this.OfType<IRememberBeingDirty>())
|
||||
{
|
||||
dc.ResetDirtyProperties(rememberPreviouslyChangedProperties);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<List<TEntity>>(GetCacheTypeKey<TEntity>());
|
||||
var found = RuntimeCache.GetCacheItem<DeepCloneableList<TEntity>>(GetCacheTypeKey<TEntity>());
|
||||
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<TEntity>(), () => entityCollection.ToList());
|
||||
RuntimeCache.InsertCacheItem(GetCacheTypeKey<TEntity>(), () => new DeepCloneableList<TEntity>(entityCollection));
|
||||
return entityCollection;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -176,6 +176,7 @@
|
||||
<Compile Include="CodeAnnotations\UmbracoWillObsoleteAttribute.cs" />
|
||||
<Compile Include="CodeAnnotations\UmbracoExperimentalFeatureAttribute.cs" />
|
||||
<Compile Include="CodeAnnotations\UmbracoProposedPublicAttribute.cs" />
|
||||
<Compile Include="Collections\DeepCloneableList.cs" />
|
||||
<Compile Include="ConcurrentHashSet.cs" />
|
||||
<Compile Include="Configuration\BaseRest\IBaseRestSection.cs" />
|
||||
<Compile Include="Configuration\BaseRest\IExtension.cs" />
|
||||
@@ -441,7 +442,7 @@
|
||||
<Compile Include="Persistence\RecordPersistenceType.cs" />
|
||||
<Compile Include="Persistence\Relators\AccessRulesRelator.cs" />
|
||||
<Compile Include="Persistence\Repositories\AuditRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\DeepCloneRuntimeCacheProvider.cs" />
|
||||
<Compile Include="Cache\DeepCloneRuntimeCacheProvider.cs" />
|
||||
<Compile Include="Persistence\Repositories\DomainRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\ExternalLoginRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\Interfaces\IAuditRepository.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<DeepCloneableListTests.TestClone>();
|
||||
original.Add(new DeepCloneableListTests.TestClone());
|
||||
original.Add(new DeepCloneableListTests.TestClone());
|
||||
original.Add(new DeepCloneableListTests.TestClone());
|
||||
|
||||
var val = _provider.GetCacheItem<DeepCloneableList<DeepCloneableListTests.TestClone>>("test", () => original);
|
||||
|
||||
Assert.AreEqual(original.Count, val.Count);
|
||||
foreach (var item in val)
|
||||
{
|
||||
Assert.IsTrue(item.IsClone);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Ensures_Cloned_And_Reset()
|
||||
{
|
||||
|
||||
139
src/Umbraco.Tests/Collections/DeepCloneableListTests.cs
Normal file
139
src/Umbraco.Tests/Collections/DeepCloneableListTests.cs
Normal file
@@ -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<TestClone>();
|
||||
list.Add(new TestClone());
|
||||
list.Add(new TestClone());
|
||||
list.Add(new TestClone());
|
||||
|
||||
var cloned = list.DeepClone() as DeepCloneableList<TestClone>;
|
||||
|
||||
Assert.IsNotNull(cloned);
|
||||
Assert.AreNotSame(list, cloned);
|
||||
Assert.AreEqual(list.Count, cloned.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Clones_Each_Item()
|
||||
{
|
||||
var list = new DeepCloneableList<TestClone>();
|
||||
list.Add(new TestClone());
|
||||
list.Add(new TestClone());
|
||||
list.Add(new TestClone());
|
||||
|
||||
var cloned = (DeepCloneableList<TestClone>) list.DeepClone();
|
||||
|
||||
foreach (var item in cloned)
|
||||
{
|
||||
Assert.IsTrue(item.IsClone);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Cloned_Sequence_Equals()
|
||||
{
|
||||
var list = new DeepCloneableList<TestClone>();
|
||||
list.Add(new TestClone());
|
||||
list.Add(new TestClone());
|
||||
list.Add(new TestClone());
|
||||
|
||||
var cloned = (DeepCloneableList<TestClone>)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<TestClone>
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the current object is equal to another object of the same type.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
|
||||
/// </returns>
|
||||
/// <param name="other">An object to compare with this object.</param>
|
||||
public bool Equals(TestClone other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Id.Equals(other.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified object is equal to the current object.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if the specified object is equal to the current object; otherwise, false.
|
||||
/// </returns>
|
||||
/// <param name="obj">The object to compare with the current object. </param>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serves as the default hash function.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for the current object.
|
||||
/// </returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,6 +175,7 @@
|
||||
<Compile Include="AttemptTests.cs" />
|
||||
<Compile Include="Cache\CacheRefresherTests.cs" />
|
||||
<Compile Include="Cache\DeepCloneRuntimeCacheProviderTests.cs" />
|
||||
<Compile Include="Collections\DeepCloneableListTests.cs" />
|
||||
<Compile Include="Controllers\BackOfficeControllerUnitTests.cs" />
|
||||
<Compile Include="DelegateExtensionsTests.cs" />
|
||||
<Compile Include="Logging\AsyncRollingFileAppenderTest.cs" />
|
||||
|
||||
Reference in New Issue
Block a user