Changes the SetPropertyValueAndDetectChanges to throw an exception if an Enumerable is passed in without using the overload the specifies an IEqualityComparer. Updates all entities that use this method with IEnumerables to specify the correct Equality comparer which now uses the new UnsortedSequenceEqual method. Added unit tests for new EnumerableExtensions and makes the ContainsAll work faster.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
@@ -114,13 +115,7 @@ namespace Umbraco.Core
|
||||
/// <returns></returns>
|
||||
public static bool ContainsAll<TSource>(this IEnumerable<TSource> source, IEnumerable<TSource> other)
|
||||
{
|
||||
var matches = true;
|
||||
foreach (var i in other)
|
||||
{
|
||||
matches = source.Contains(i);
|
||||
if (!matches) break;
|
||||
}
|
||||
return matches;
|
||||
return other.Except(source).Any() == false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -132,7 +127,7 @@ namespace Umbraco.Core
|
||||
/// <returns></returns>
|
||||
public static bool ContainsAny<TSource>(this IEnumerable<TSource> source, IEnumerable<TSource> other)
|
||||
{
|
||||
return other.Any(i => source.Contains(i));
|
||||
return other.Any(source.Contains);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -224,7 +219,7 @@ namespace Umbraco.Core
|
||||
return sequence.Select(
|
||||
x =>
|
||||
{
|
||||
if (typeof(TActual).IsAssignableFrom(x.GetType()))
|
||||
if (x is TActual)
|
||||
{
|
||||
var casted = x as TActual;
|
||||
projection.Invoke(casted);
|
||||
@@ -276,6 +271,34 @@ namespace Umbraco.Core
|
||||
///<param name="items">The enumerable to search.</param>
|
||||
///<param name="item">The item to find.</param>
|
||||
///<returns>The index of the first matching item, or -1 if the item was not found.</returns>
|
||||
public static int IndexOf<T>(this IEnumerable<T> items, T item) { return items.FindIndex(i => EqualityComparer<T>.Default.Equals(item, i)); }
|
||||
public static int IndexOf<T>(this IEnumerable<T> items, T item)
|
||||
{
|
||||
return items.FindIndex(i => EqualityComparer<T>.Default.Equals(item, i));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if 2 lists have equal elements within them regardless of how they are sorted
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="source"></param>
|
||||
/// <param name="other"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// The logic for this is taken from:
|
||||
/// http://stackoverflow.com/questions/4576723/test-whether-two-ienumerablet-have-the-same-values-with-the-same-frequencies
|
||||
///
|
||||
/// There's a few answers, this one seems the best for it's simplicity and based on the comment of Eamon
|
||||
/// </remarks>
|
||||
public static bool UnsortedSequenceEqual<T>(this IEnumerable<T> source, IEnumerable<T> other)
|
||||
{
|
||||
if (source == null && other == null) return true;
|
||||
if (source == null || other == null) return false;
|
||||
|
||||
var list1Groups = source.ToLookup(i => i);
|
||||
var list2Groups = other.ToLookup(i => i);
|
||||
return list1Groups.Count == list2Groups.Count
|
||||
&& list1Groups.All(g => g.Count() == list2Groups[g.Key].Count());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,11 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
_allowedTemplates = value;
|
||||
return _allowedTemplates;
|
||||
}, _allowedTemplates, AllowedTemplatesSelector);
|
||||
}, _allowedTemplates, AllowedTemplatesSelector,
|
||||
//Custom comparer for enumerable
|
||||
new DelegateEqualityComparer<IEnumerable<ITemplate>>(
|
||||
(templates, enumerable) => templates.UnsortedSequenceEqual(enumerable),
|
||||
templates => templates.GetHashCode()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -351,7 +351,11 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
_allowedContentTypes = value;
|
||||
return _allowedContentTypes;
|
||||
}, _allowedContentTypes, AllowedContentTypesSelector);
|
||||
}, _allowedContentTypes, AllowedContentTypesSelector,
|
||||
//Custom comparer for enumerable
|
||||
new DelegateEqualityComparer<IEnumerable<ContentTypeSort>>(
|
||||
(sorts, enumerable) => sorts.UnsortedSequenceEqual(enumerable),
|
||||
sorts => sorts.GetHashCode()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,11 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
_translations = value;
|
||||
return _translations;
|
||||
}, _translations, TranslationsSelector);
|
||||
}, _translations, TranslationsSelector,
|
||||
//Custom comparer for enumerable
|
||||
new DelegateEqualityComparer<IEnumerable<IDictionaryTranslation>>(
|
||||
(enumerable, translations) => enumerable.UnsortedSequenceEqual(translations),
|
||||
enumerable => enumerable.GetHashCode()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
@@ -166,7 +167,36 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
/// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set
|
||||
/// to the same value, so it's really not dirty.
|
||||
/// </remarks>
|
||||
internal virtual bool SetPropertyValueAndDetectChanges<T>(Func<T, T> setValue, T value, PropertyInfo propertySelector)
|
||||
internal bool SetPropertyValueAndDetectChanges<T>(Func<T, T> setValue, T value, PropertyInfo propertySelector)
|
||||
{
|
||||
if ((typeof(T) == typeof(string) == false) && TypeHelper.IsTypeAssignableFrom<IEnumerable>(typeof(T)))
|
||||
{
|
||||
throw new InvalidOperationException("This method does not support IEnumerable instances. For IEnumerable instances a manual custom equality check will be required");
|
||||
}
|
||||
|
||||
return SetPropertyValueAndDetectChanges(setValue, value, propertySelector,
|
||||
new DelegateEqualityComparer<T>(
|
||||
//Standard Equals comparison
|
||||
(arg1, arg2) => Equals(arg1, arg2),
|
||||
arg => arg.GetHashCode()));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did
|
||||
/// it will ensure that the property has a dirty flag set.
|
||||
/// </summary>
|
||||
/// <param name="setValue"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="propertySelector"></param>
|
||||
/// <param name="comparer">The equality comparer to use</param>
|
||||
/// <returns>returns true if the value changed</returns>
|
||||
/// <remarks>
|
||||
/// This is required because we don't want a property to show up as "dirty" if the value is the same. For example, when we
|
||||
/// save a document type, nearly all properties are flagged as dirty just because we've 'reset' them, but they are all set
|
||||
/// to the same value, so it's really not dirty.
|
||||
/// </remarks>
|
||||
internal bool SetPropertyValueAndDetectChanges<T>(Func<T, T> setValue, T value, PropertyInfo propertySelector, IEqualityComparer<T> comparer)
|
||||
{
|
||||
var initVal = value;
|
||||
var newVal = setValue(value);
|
||||
@@ -174,7 +204,7 @@ namespace Umbraco.Core.Models.EntityBase
|
||||
//don't track changes, just set the value (above)
|
||||
if (_changeTrackingEnabled == false) return false;
|
||||
|
||||
if (Equals(initVal, newVal) == false)
|
||||
if (comparer.Equals(initVal, newVal) == false)
|
||||
{
|
||||
OnPropertyChanged(propertySelector);
|
||||
return true;
|
||||
|
||||
@@ -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;
|
||||
@@ -67,7 +68,11 @@ namespace Umbraco.Core.Models.Membership
|
||||
{
|
||||
_permissions = value;
|
||||
return _permissions;
|
||||
}, _permissions, PermissionsSelector);
|
||||
}, _permissions, PermissionsSelector,
|
||||
//Custom comparer for enumerable
|
||||
new DelegateEqualityComparer<IEnumerable<string>>(
|
||||
(enum1, enum2) => enum1.UnsortedSequenceEqual(enum2),
|
||||
enum1 => enum1.GetHashCode()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
@@ -120,7 +123,7 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
bool typeValidation = _propertyType.IsPropertyTypeValid(value);
|
||||
|
||||
if (!typeValidation)
|
||||
if (typeValidation == false)
|
||||
throw new Exception(
|
||||
string.Format(
|
||||
"Type validation failed. The value type: '{0}' does not match the DataType in PropertyType with alias: '{1}'",
|
||||
@@ -130,7 +133,21 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
_value = value;
|
||||
return _value;
|
||||
}, _value, ValueSelector);
|
||||
}, _value, ValueSelector,
|
||||
new DelegateEqualityComparer<object>(
|
||||
(o, o1) =>
|
||||
{
|
||||
//Custom comparer for enumerable if it is enumerable
|
||||
if (o == null && o1 == null) return true;
|
||||
if (o == null || o1 == null) return false;
|
||||
var enum1 = o as IEnumerable;
|
||||
var enum2 = o1 as IEnumerable;
|
||||
if (enum1 != null && enum2 != null)
|
||||
{
|
||||
return enum1.Cast<object>().UnsortedSequenceEqual(enum2.Cast<object>());
|
||||
}
|
||||
return o.Equals(o1);
|
||||
}, o => o.GetHashCode()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,35 @@ namespace Umbraco.Tests
|
||||
public class EnumerableExtensionsTests
|
||||
{
|
||||
|
||||
[Test]
|
||||
public void Unsorted_Sequence_Equal()
|
||||
{
|
||||
var list1 = new[] { 1, 2, 3, 4, 5, 6 };
|
||||
var list2 = new[] { 6, 5, 3, 2, 1, 4 };
|
||||
var list3 = new[] { 6, 5, 4, 3, 2, 2 };
|
||||
|
||||
Assert.IsTrue(list1.UnsortedSequenceEqual(list2));
|
||||
Assert.IsTrue(list2.UnsortedSequenceEqual(list1));
|
||||
Assert.IsFalse(list1.UnsortedSequenceEqual(list3));
|
||||
|
||||
Assert.IsTrue(((IEnumerable<object>)null).UnsortedSequenceEqual(null));
|
||||
Assert.IsFalse(((IEnumerable<int>)null).UnsortedSequenceEqual(list1));
|
||||
Assert.IsFalse(list1.UnsortedSequenceEqual(null));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Contains_All()
|
||||
{
|
||||
var list1 = new[] {1, 2, 3, 4, 5, 6};
|
||||
var list2 = new[] {6, 5, 3, 2, 1, 4};
|
||||
var list3 = new[] {6, 5, 4, 3};
|
||||
|
||||
Assert.IsTrue(list1.ContainsAll(list2));
|
||||
Assert.IsTrue(list2.ContainsAll(list1));
|
||||
Assert.IsTrue(list1.ContainsAll(list3));
|
||||
Assert.IsFalse(list3.ContainsAll(list1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Flatten_List_2()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user