-
Notifications
You must be signed in to change notification settings - Fork 731
Description
Description
The BeEquivalentTo method for IEnumerable, when not using strict ordering or any options, now performs very poorly on version 6.0.0. For example To compare 100 objects in version 5 it is a reasonable amount of time, ~1 second, but in version 6 it is > 10 seconds. The amount of time it takes depends on the number of properties as well.
Complete minimal example reproducing the issue
ExampleObject GetObject(int i)
{
return new ExampleObject
{
Id = i.ToString(),
Value1 = i.ToString(),
Value2 = i.ToString(),
Value3 = i.ToString(),
Value4 = i.ToString(),
Value5 = i.ToString(),
Value6 = i.ToString(),
Value7 = i.ToString(),
Value8 = i.ToString(),
Value9 = i.ToString(),
Value10 = i.ToString(),
Value11 = i.ToString(),
Value12 = i.ToString(),
};
}
var list1 = new List<ExampleObject>();
var list2 = new List<ExampleObject>();
var maxAmount = 100;
for (var i = 0; i < maxAmount; i++)
{
list1.Add(GetObject(i));
list2.Add(GetObject(maxAmount - 1 - i));
}
var timer = Stopwatch.StartNew();
list1.Should().BeEquivalentTo(list2);
timer.Stop();
Debug.WriteLine(timer.ElapsedMilliseconds); // 18,539 on version 6.0.0 1,512 on version 5.10.3
public class ExampleObject
{
[Key]
public string Id { get; set; }
public string Value1 { get; set; }
public string Value2 { get; set; }
public string Value3 { get; set; }
public string Value4 { get; set; }
public string Value5 { get; set; }
public string Value6 { get; set; }
public string Value7 { get; set; }
public string Value8 { get; set; }
public string Value9 { get; set; }
public string Value10 { get; set; }
public string Value11 { get; set; }
public string Value12 { get; set; }
}Expected behavior:
Expected better performance
Actual behavior:
Poor performance
Versions
Version 6.0.0
Additional Information
One workaround is to perform the ordering and use the performant strict ordering.
So instead of this which performs poorly:
list1.Should().BeEquivalentTo(list2);
Something like this would be used which performs well:
list1.OrderBy(m => m.Id).Should().BeEquivalentTo(list2.OrderBy(m => m.Id), options => options.WithStrictOrdering());
The current generic workaround I am using, for non strict ordering, looks to see if there are any keys for the object type being compared. If there are keys, the enumerables are ordered by the key and then strict ordering is used which is very efficient :). The code below is using some of our custom code to determine keys, (which is determined using reflection or a registry or user defined keys for objects with multiple properties making up the key) and perform the ordering. But thought i would share it in case it was a helpful concept to use reflection to see if there is an attribute of key that the object could be ordered by for more efficient comparison when the assertion does not care about order.
public static void AssertEquivalent<TModel>(IEnumerable<TModel> first, IEnumerable<TModel> second)
{
var context = new DataObjectContext<TModel>();
if (context.IsKeyDefined())
{
var keyProperties = context.GetKeyMembers();
var orderingConfig = new List<OrderingConfiguration<TModel>>();
foreach (var keyProperty in keyProperties)
{
orderingConfig.Add(new OrderingConfiguration<TModel> { Expression = m => keyProperty.GetValue(m) });
}
first.OrderBy(orderingConfig).Should().BeEquivalentTo(second.OrderBy(orderingConfig), options => options.WithStrictOrdering());
}
else
{
first.Should().BeEquivalentTo(second);
}
}