Skip to content

Commit

Permalink
Add capability to verify order in ordered filtered include queries
Browse files Browse the repository at this point in the history
ExpectedFilteredInclude now detects whether filter contains ordering. Based on that information we either perform default ordering or leave the exisiting order as is.
  • Loading branch information
maumar committed Jun 17, 2020
1 parent bc6038c commit e201bad
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,60 @@
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore.Query;

namespace Microsoft.EntityFrameworkCore.TestUtilities
{
public class ExpectedFilteredInclude<TEntity, TIncluded> : ExpectedInclude<TEntity>
{
public Func<IEnumerable<TIncluded>, IEnumerable<TIncluded>> IncludeFilter { get; }

public bool AssertOrder { get; }

public ExpectedFilteredInclude(
Expression<Func<TEntity, IEnumerable<TIncluded>>> include,
string navigationPath = "",
Func<IEnumerable<TIncluded>, IEnumerable<TIncluded>> includeFilter = null)
Expression<Func<IEnumerable<TIncluded>, IEnumerable<TIncluded>>> includeFilter = null)
: base(Convert(include), navigationPath)
{
IncludeFilter = includeFilter;
var orderFinder = new OrderFindingExpressionVisitor();
orderFinder.Visit(includeFilter.Body);
AssertOrder = orderFinder.ContainsOrderBy;
IncludeFilter = includeFilter.Compile();
}

private static Expression<Func<TEntity, object>> Convert(Expression<Func<TEntity, IEnumerable<TIncluded>>> include)
=> Expression.Lambda<Func<TEntity, object>>(include.Body, include.Parameters);

private class OrderFindingExpressionVisitor : ExpressionVisitor
{
public bool ContainsOrderBy { get; private set; }

private readonly List<MethodInfo> _orderByMethods = new List<MethodInfo>
{
QueryableMethods.OrderBy,
QueryableMethods.OrderByDescending,
QueryableMethods.ThenBy,
QueryableMethods.ThenByDescending,
EnumerableMethods.OrderBy,
EnumerableMethods.OrderByDescending,
EnumerableMethods.ThenBy,
EnumerableMethods.ThenByDescending
};

protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
{
if (methodCallExpression.Method.IsGenericMethod
&& _orderByMethods.Contains(methodCallExpression.Method.GetGenericMethodDefinition()))
{
ContainsOrderBy = true;

return methodCallExpression;
}

return base.VisitMethodCall(methodCallExpression);
}
}
}
}
20 changes: 14 additions & 6 deletions test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1493,10 +1493,10 @@ public void AssertInclude<TEntity>(TEntity expected, TEntity actual, IExpectedIn
{
_includePath.Clear();

AssertIncludeObject(expected, actual, expectedIncludes);
AssertIncludeObject(expected, actual, expectedIncludes, assertOrder: false);
}

private void AssertIncludeObject(object expected, object actual, IEnumerable<IExpectedInclude> expectedIncludes)
private void AssertIncludeObject(object expected, object actual, IEnumerable<IExpectedInclude> expectedIncludes, bool assertOrder)
{
if (expected == null
&& actual == null)
Expand All @@ -1512,7 +1512,7 @@ private void AssertIncludeObject(object expected, object actual, IEnumerable<IEx
i => i.IsConstructedGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
{
_assertIncludeCollectionMethodInfo.MakeGenericMethod(expectedType.GenericTypeArguments[0])
.Invoke(this, new[] { expected, actual, expectedIncludes });
.Invoke(this, new[] { expected, actual, expectedIncludes, assertOrder });
}
else
{
Expand All @@ -1534,12 +1534,15 @@ private void AssertIncludeEntity<TElement>(TElement expected, TElement actual, I
}

private void AssertIncludeCollection<TElement>(
IEnumerable<TElement> expected, IEnumerable<TElement> actual, IEnumerable<IExpectedInclude> expectedIncludes)
IEnumerable<TElement> expected,
IEnumerable<TElement> actual,
IEnumerable<IExpectedInclude> expectedIncludes,
bool assertOrder)
{
var expectedList = expected.ToList();
var actualList = actual.ToList();

if (_entitySorters.TryGetValue(typeof(TElement), out var sorter))
if (!assertOrder && _entitySorters.TryGetValue(typeof(TElement), out var sorter))
{
var actualSorter = (Func<TElement, object>)sorter;
expectedList = expectedList.OrderBy(actualSorter).ToList();
Expand All @@ -1563,6 +1566,7 @@ private void ProcessIncludes<TEntity>(TEntity expected, TEntity actual, IEnumera
foreach (var expectedInclude in expectedIncludes.OfType<ExpectedInclude<TEntity>>().Where(i => i.NavigationPath == currentPath))
{
var expectedIncludedNavigation = GetIncluded(expected, expectedInclude.IncludeMember);
bool assertOrder = false;
if (expectedInclude.GetType().BaseType != typeof(object))
{
var includedType = expectedInclude.GetType().GetGenericArguments()[1];
Expand All @@ -1573,13 +1577,17 @@ private void ProcessIncludes<TEntity>(TEntity expected, TEntity actual, IEnumera
null,
new object[] { expectedIncludedNavigation, expectedInclude },
CultureInfo.CurrentCulture);

assertOrder = (bool)expectedInclude.GetType()
.GetProperty(nameof(ExpectedFilteredInclude<object, object>.AssertOrder))
.GetValue(expectedInclude);
}

var actualIncludedNavigation = GetIncluded(actual, expectedInclude.IncludeMember);

_includePath.Add(expectedInclude.IncludeMember.Name);

AssertIncludeObject(expectedIncludedNavigation, actualIncludedNavigation, expectedIncludes);
AssertIncludeObject(expectedIncludedNavigation, actualIncludedNavigation, expectedIncludes, assertOrder);

_includePath.RemoveAt(_includePath.Count - 1);
}
Expand Down

0 comments on commit e201bad

Please sign in to comment.