From e201bad656b2c59996d5f4180fd6c35a489e3ecd Mon Sep 17 00:00:00 2001 From: maumar Date: Tue, 16 Jun 2020 21:04:09 -0700 Subject: [PATCH] Add capability to verify order in ordered filtered include queries ExpectedFilteredInclude now detects whether filter contains ordering. Based on that information we either perform default ordering or leave the exisiting order as is. --- .../TestUtilities/ExpectedFilteredInclude.cs | 41 ++++++++++++++++++- .../TestUtilities/QueryAsserter.cs | 20 ++++++--- 2 files changed, 53 insertions(+), 8 deletions(-) diff --git a/test/EFCore.Specification.Tests/TestUtilities/ExpectedFilteredInclude.cs b/test/EFCore.Specification.Tests/TestUtilities/ExpectedFilteredInclude.cs index 17000f5402f..c22250df820 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/ExpectedFilteredInclude.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/ExpectedFilteredInclude.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.Linq.Expressions; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Query; namespace Microsoft.EntityFrameworkCore.TestUtilities { @@ -11,16 +13,51 @@ public class ExpectedFilteredInclude : ExpectedInclude, IEnumerable> IncludeFilter { get; } + public bool AssertOrder { get; } + public ExpectedFilteredInclude( Expression>> include, string navigationPath = "", - Func, IEnumerable> includeFilter = null) + Expression, IEnumerable>> 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> Convert(Expression>> include) => Expression.Lambda>(include.Body, include.Parameters); + + private class OrderFindingExpressionVisitor : ExpressionVisitor + { + public bool ContainsOrderBy { get; private set; } + + private readonly List _orderByMethods = new List + { + 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); + } + } } } diff --git a/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs b/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs index fbd609aad57..0e5b089ffed 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/QueryAsserter.cs @@ -1493,10 +1493,10 @@ public void AssertInclude(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 expectedIncludes) + private void AssertIncludeObject(object expected, object actual, IEnumerable expectedIncludes, bool assertOrder) { if (expected == null && actual == null) @@ -1512,7 +1512,7 @@ private void AssertIncludeObject(object expected, object actual, IEnumerable 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 { @@ -1534,12 +1534,15 @@ private void AssertIncludeEntity(TElement expected, TElement actual, I } private void AssertIncludeCollection( - IEnumerable expected, IEnumerable actual, IEnumerable expectedIncludes) + IEnumerable expected, + IEnumerable actual, + IEnumerable 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)sorter; expectedList = expectedList.OrderBy(actualSorter).ToList(); @@ -1563,6 +1566,7 @@ private void ProcessIncludes(TEntity expected, TEntity actual, IEnumera foreach (var expectedInclude in expectedIncludes.OfType>().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]; @@ -1573,13 +1577,17 @@ private void ProcessIncludes(TEntity expected, TEntity actual, IEnumera null, new object[] { expectedIncludedNavigation, expectedInclude }, CultureInfo.CurrentCulture); + + assertOrder = (bool)expectedInclude.GetType() + .GetProperty(nameof(ExpectedFilteredInclude.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); }