diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 97c5c8eb221..56e0ed21910 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -1319,12 +1319,12 @@ public static string AmbiguousForeignKeyPropertyCandidates([CanBeNull] object fi firstDependentToPrincipalNavigationSpecification, firstPrincipalToDependentNavigationSpecification, secondDependentToPrincipalNavigationSpecification, secondPrincipalToDependentNavigationSpecification, foreignKeyProperties); /// - /// The {methodName} property lambda expression '{includeLambdaExpression}' is invalid. The expression should represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, specify an explicitly typed lambda parameter of the target type, E.g. '(Derived d) => d.MyProperty'. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393. + /// The expression '{expression}' is invalid inside Include operation. The expression should represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, specify an explicitly typed lambda parameter of the target type, E.g. '(Derived d) => d.MyProperty'. Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393. /// - public static string InvalidIncludeLambdaExpression([CanBeNull] object methodName, [CanBeNull] object includeLambdaExpression) + public static string InvalidIncludeExpression([CanBeNull] object expression) => string.Format( - GetString("InvalidIncludeLambdaExpression", nameof(methodName), nameof(includeLambdaExpression)), - methodName, includeLambdaExpression); + GetString("InvalidIncludeExpression", nameof(expression)), + expression); /// /// The corresponding CLR type for entity type '{entityType}' is not instantiable and there is no derived entity type in the model that corresponds to a concrete CLR type. @@ -2422,6 +2422,14 @@ public static string InvalidLambdaExpressionInsideInclude public static string IncludeOnNonEntity => GetString("IncludeOnNonEntity"); + /// + /// Different filters '{filter1}' and '{filter2}' have been applied on the same included navigation. Only one unique filter per navigation is allowed. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393. + /// + public static string MultipleFilteredIncludesOnSameNavigation([CanBeNull] object filter1, [CanBeNull] object filter2) + => string.Format( + GetString("MultipleFilteredIncludesOnSameNavigation", nameof(filter1), nameof(filter2)), + filter1, filter2); + /// /// Unable to convert queryable method to enumerable method. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 1848bc401fb..ce4e3f810db 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -747,8 +747,8 @@ Both relationships between '{firstDependentToPrincipalNavigationSpecification}' and '{firstPrincipalToDependentNavigationSpecification}' and between '{secondDependentToPrincipalNavigationSpecification}' and '{secondPrincipalToDependentNavigationSpecification}' could use {foreignKeyProperties} as the foreign key. To resolve this configure the foreign key properties explicitly on at least one of the relationships. - - The {methodName} property lambda expression '{includeLambdaExpression}' is invalid. The expression should represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, specify an explicitly typed lambda parameter of the target type, E.g. '(Derived d) => d.MyProperty'. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393. + + The expression '{expression}' is invalid inside Include operation. The expression should represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, specify an explicitly typed lambda parameter of the target type, E.g. '(Derived d) => d.MyProperty'. Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393. The corresponding CLR type for entity type '{entityType}' is not instantiable and there is no derived entity type in the model that corresponds to a concrete CLR type. @@ -1290,6 +1290,9 @@ Include has been used on non entity queryable. + + Different filters '{filter1}' and '{filter2}' have been applied on the same included navigation. Only one unique filter per navigation is allowed. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393. + Unable to convert queryable method to enumerable method. diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs index c129c668529..fc6ca2aee97 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs @@ -181,9 +181,8 @@ protected Expression ExpandNavigation( var innerSource = (NavigationExpansionExpression)_navigationExpandingExpressionVisitor.Visit(innerQueryable); if (entityReference.IncludePaths.ContainsKey(navigation)) { - var innerIncludeTreeNode = entityReference.IncludePaths[navigation]; var innerEntityReference = (EntityReference)((NavigationTreeExpression)innerSource.PendingSelector).Value; - innerEntityReference.SetIncludePaths(innerIncludeTreeNode); + innerEntityReference.SetIncludePaths(entityReference.IncludePaths[navigation]); } var innerSourceSequenceType = innerSource.Type.GetSequenceType(); @@ -532,6 +531,20 @@ private Expression ExpandIncludesHelper(Expression root, EntityReference entityR included = ExpandIncludesHelper(included, innerEntityReference); } + if (included is MaterializeCollectionNavigationExpression materializeCollectionNavigation) + { + var filterExpression = entityReference.IncludePaths[navigation].FilterExpression; + if (filterExpression != null) + { + var subquery = ReplacingExpressionVisitor.Replace( + filterExpression.Parameters[0], + materializeCollectionNavigation.Subquery, + filterExpression.Body); + + included = materializeCollectionNavigation.Update(subquery); + } + } + result = new IncludeExpression(result, included, navigation); } diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.Expressions.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.Expressions.cs index 8cee5661892..62045b12018 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.Expressions.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.Expressions.cs @@ -84,6 +84,8 @@ protected class IncludeTreeNode : Dictionary { private EntityReference _entityReference; + public virtual LambdaExpression FilterExpression { get; set; } + public IncludeTreeNode(IEntityType entityType, EntityReference entityReference) { EntityType = entityType; diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs index 6d5e6871b16..1297357ea58 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs @@ -32,6 +32,19 @@ private static readonly PropertyInfo _queryContextContextPropertyInfo { QueryableMethods.LastWithPredicate, QueryableMethods.LastWithoutPredicate }, { QueryableMethods.LastOrDefaultWithPredicate, QueryableMethods.LastOrDefaultWithoutPredicate } }; + + private static readonly List _supportedFilteredIncludeOperations = new List + { + QueryableMethods.Where, + QueryableMethods.OrderBy, + QueryableMethods.OrderByDescending, + QueryableMethods.ThenBy, + QueryableMethods.ThenByDescending, + QueryableMethods.Skip, + QueryableMethods.Take, + QueryableMethods.AsQueryable + }; + private readonly QueryTranslationPreprocessor _queryTranslationPreprocessor; private readonly QueryCompilationContext _queryCompilationContext; private readonly PendingSelectorExpandingExpressionVisitor _pendingSelectorExpandingExpressionVisitor; @@ -786,12 +799,28 @@ private NavigationExpansionExpression ProcessInclude(NavigationExpansionExpressi ? entityReference.LastIncludeTreeNode : entityReference.IncludePaths; var includeLambda = expression.UnwrapLambdaFromQuote(); - var lastIncludeTree = PopulateIncludeTree(currentIncludeTreeNode, includeLambda.Body); + + var (result, filterExpression) = ExtractIncludeFilter(includeLambda.Body, includeLambda.Body); + var lastIncludeTree = PopulateIncludeTree(currentIncludeTreeNode, result); if (lastIncludeTree == null) { throw new InvalidOperationException(CoreStrings.InvalidLambdaExpressionInsideInclude); } + if (filterExpression != null) + { + if (lastIncludeTree.FilterExpression != null + && !ExpressionEqualityComparer.Instance.Equals(filterExpression, lastIncludeTree.FilterExpression)) + { + throw new InvalidOperationException( + CoreStrings.MultipleFilteredIncludesOnSameNavigation( + FormatFilter(filterExpression.Body).Print(), + FormatFilter(lastIncludeTree.FilterExpression.Body).Print())); + } + + lastIncludeTree.FilterExpression = filterExpression; + } + entityReference.SetLastInclude(lastIncludeTree); } @@ -799,6 +828,63 @@ private NavigationExpansionExpression ProcessInclude(NavigationExpansionExpressi } throw new InvalidOperationException(CoreStrings.IncludeOnNonEntity); + + (Expression result, LambdaExpression filterExpression) ExtractIncludeFilter(Expression currentExpression, Expression includeExpression) + { + if (currentExpression is MemberExpression) + { + return (currentExpression, default(LambdaExpression)); + } + + if (currentExpression is MethodCallExpression methodCallExpression) + { + if (!methodCallExpression.Method.IsGenericMethod + || !_supportedFilteredIncludeOperations.Contains(methodCallExpression.Method.GetGenericMethodDefinition())) + { + throw new InvalidOperationException(CoreStrings.InvalidIncludeExpression(includeExpression)); + } + + var (result, filterExpression) = ExtractIncludeFilter(methodCallExpression.Arguments[0], includeExpression); + if (filterExpression == null) + { + var prm = Expression.Parameter(result.Type); + filterExpression = Expression.Lambda(prm, prm); + } + + var arguments = new List(); + arguments.Add(filterExpression.Body); + arguments.AddRange(methodCallExpression.Arguments.Skip(1)); + filterExpression = Expression.Lambda( + methodCallExpression.Update(methodCallExpression.Object, arguments), + filterExpression.Parameters); + + return (result, filterExpression); + } + + throw new InvalidOperationException(CoreStrings.InvalidIncludeExpression(includeExpression)); + } + + Expression FormatFilter(Expression expression) + { + if (expression is MethodCallExpression methodCallExpression + && methodCallExpression.Method.IsGenericMethod + && _supportedFilteredIncludeOperations.Contains(methodCallExpression.Method.GetGenericMethodDefinition())) + { + if (methodCallExpression.Method.GetGenericMethodDefinition() == QueryableMethods.AsQueryable) + { + return Expression.Parameter(expression.Type, "navigation"); + } + + var arguments = new List(); + var source = FormatFilter(methodCallExpression.Arguments[0]); + arguments.Add(source); + arguments.AddRange(methodCallExpression.Arguments.Skip(1)); + + return methodCallExpression.Update(methodCallExpression.Object, arguments); + } + + return expression; + } } private NavigationExpansionExpression ProcessJoin( @@ -1474,8 +1560,10 @@ private IncludeTreeNode PopulateIncludeTree(IncludeTreeNode includeTreeNode, Exp if (navigation != null) { var addedNode = innerIncludeTreeNode.AddNavigation(navigation); + // This is to add eager Loaded navigations when owner type is included. PopulateEagerLoadedNavigations(addedNode); + return addedNode; } diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs index 2f9a7699522..9e5788325f7 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs @@ -4980,5 +4980,461 @@ public virtual Task Contains_over_optional_navigation_with_null_entity_reference }), elementSorter: e => (e.Name, e.OptionalName, e.Contains)); } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_basic_Where(bool async) + { + return AssertIncludeQuery( + async, + ss => ss.Set().Include(l1 => l1.OneToMany_Optional1.Where(l2 => l2.Id > 5)), + new List + { + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + "OneToMany_Optional1", + includeFilter: x => x.Where(l2 => l2.Id > 5)) + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_basic_OrderBy_Take(bool async) + { + return AssertIncludeQuery( + async, + ss => ss.Set().Include(l1 => l1.OneToMany_Optional1.OrderBy(x => x.Name).Take(3)), + new List + { + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + "OneToMany_Optional1", + includeFilter: x => x.OrderBy(x => x.Name).Take(3)) + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_basic_OrderBy_Skip(bool async) + { + return AssertIncludeQuery( + async, + ss => ss.Set().Include(l1 => l1.OneToMany_Optional1.OrderBy(x => x.Name).Skip(1)), + new List + { + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + "OneToMany_Optional1", + includeFilter: x => x.OrderBy(x => x.Name).Skip(1)) + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_basic_OrderBy_Skip_Take(bool async) + { + return AssertIncludeQuery( + async, + ss => ss.Set().Include(l1 => l1.OneToMany_Optional1.OrderBy(x => x.Name).Skip(1).Take(3)), + new List + { + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + "OneToMany_Optional1", + includeFilter: x => x.OrderBy(x => x.Name).Skip(1).Take(3)) + }); + } + + [ConditionalFact] + public virtual void Filtered_include_Skip_without_OrderBy() + { + using var ctx = CreateContext(); + var query = ctx.LevelOne.Include(l1 => l1.OneToMany_Optional1.Skip(1)); + var result = query.ToList(); + } + + [ConditionalFact] + public virtual void Filtered_include_Take_without_OrderBy() + { + using var ctx = CreateContext(); + var query = ctx.LevelOne.Include(l1 => l1.OneToMany_Optional1.Skip(1)); + var result = query.ToList(); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_on_ThenInclude(bool async) + { + return AssertIncludeQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToOne_Optional_FK1) + .ThenInclude(l2 => l2.OneToMany_Optional2.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Skip(1).Take(3)), + new List + { + new ExpectedInclude(e => e.OneToOne_Optional_FK1, "OneToOne_Optional_FK1"), + new ExpectedFilteredInclude( + e => e.OneToMany_Optional2, + "OneToMany_Optional2", + "OneToOne_Optional_FK1", + x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Skip(1).Take(3)) + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_after_reference_navigation(bool async) + { + return AssertIncludeQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToOne_Optional_FK1.OneToMany_Optional2.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Skip(1).Take(3)), + new List + { + new ExpectedInclude(e => e.OneToOne_Optional_FK1, "OneToOne_Optional_FK1"), + new ExpectedFilteredInclude( + e => e.OneToMany_Optional2, + "OneToMany_Optional2", + "OneToOne_Optional_FK1", + x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Skip(1).Take(3)) + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_after_different_filtered_include_same_level(bool async) + { + return AssertIncludeQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Take(3)) + .Include(l1 => l1.OneToMany_Required1.Where(x => x.Name != "Bar").OrderByDescending(x => x.Name).Skip(1)), + new List + { + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + "OneToMany_Optional1", + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Take(3)), + new ExpectedFilteredInclude( + e => e.OneToMany_Required1, + "OneToMany_Required1", + includeFilter: x => x.Where(x => x.Name != "Bar").OrderByDescending(x => x.Name).Skip(1)) + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_after_different_filtered_include_different_level(bool async) + { + return AssertIncludeQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Take(3)) + .ThenInclude(l2 => l2.OneToMany_Required2.Where(x => x.Name != "Bar").OrderByDescending(x => x.Name).Skip(1)), + new List + { + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + "OneToMany_Optional1", + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Take(3)), + new ExpectedFilteredInclude( + e => e.OneToMany_Required2, + "OneToMany_Required2", + "OneToMany_Optional1", + includeFilter: x => x.Where(x => x.Name != "Bar").OrderByDescending(x => x.Name).Skip(1)) + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Filtered_include_different_filter_set_on_same_navigation_twice(bool async) + { + var message = (await Assert.ThrowsAsync( + () => AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(3)) + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Bar").OrderByDescending(x => x.Name).Take(3))))).Message; + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Filtered_include_different_filter_set_on_same_navigation_twice_multi_level(bool async) + { + var message = (await Assert.ThrowsAsync( + () => AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo")).ThenInclude(l2 => l2.OneToMany_Optional2) + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Bar")).ThenInclude(l2 => l2.OneToOne_Required_FK2)))).Message; + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_same_filter_set_on_same_navigation_twice(bool async) + { + return AssertIncludeQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderByDescending(x => x.Id).Take(2)) + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderByDescending(x => x.Id).Take(2)), + new List + { + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + "OneToMany_Optional1", + includeFilter: x => x.Where(x => x.Name != "Foo").OrderByDescending(x => x.Id).Take(2)), + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_same_filter_set_on_same_navigation_twice_followed_by_ThenIncludes(bool async) + { + return AssertIncludeQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(2)).ThenInclude(l2 => l2.OneToMany_Optional2) + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(2)).ThenInclude(l2 => l2.OneToOne_Required_FK2), + new List + { + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + "OneToMany_Optional1", + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(2)), + new ExpectedInclude(e => e.OneToMany_Optional2, "OneToMany_Optional1"), + new ExpectedInclude(e => e.OneToOne_Required_FK2, "OneToMany_Optional1"), + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_multiple_multi_level_includes_with_first_level_using_filter_include_on_one_of_the_chains_only(bool async) + { + return AssertIncludeQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(2)).ThenInclude(l2 => l2.OneToMany_Optional2) + .Include(l1 => l1.OneToMany_Optional1).ThenInclude(l2 => l2.OneToOne_Required_FK2), + new List + { + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + "OneToMany_Optional1", + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(2)), + new ExpectedInclude(e => e.OneToMany_Optional2, "OneToMany_Optional2", "OneToMany_Optional1"), + new ExpectedInclude(e => e.OneToOne_Required_FK2, "OneToOne_Required_FK2", "OneToMany_Optional1"), + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_and_non_filtered_include_on_same_navigation1(bool async) + { + return AssertIncludeQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1) + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(3)), + new List + { + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + "OneToMany_Optional1", + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(3)) + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_and_non_filtered_include_on_same_navigation2(bool async) + { + return AssertIncludeQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(3)) + .Include(l1 => l1.OneToMany_Optional1), + new List + { + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + "OneToMany_Optional1", + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(3)) + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_and_non_filtered_include_followed_by_then_include_on_same_navigation(bool async) + { + return AssertIncludeQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(1)) + .Include(l1 => l1.OneToMany_Optional1).ThenInclude(l2 => l2.OneToOne_Optional_PK2.OneToMany_Optional3.Where(x => x.Id > 1)), + new List + { + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + "OneToMany_Optional1", + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(1)), + new ExpectedInclude( + e => e.OneToOne_Optional_PK2, + "OneToOne_Optional_PK2", + "OneToMany_Optional1"), + new ExpectedFilteredInclude( + e => e.OneToMany_Optional3, + "OneToMany_Optional3", + "OneToMany_Optional1.OneToOne_Optional_PK2", + includeFilter: x => x.Where(x => x.Id > 1)), + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_complex_three_level_with_middle_having_filter1(bool async) + { + return AssertIncludeQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1).ThenInclude(l2 => l2.OneToMany_Optional2.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(1)).ThenInclude(l3 => l3.OneToMany_Optional3) + .Include(l1 => l1.OneToMany_Optional1).ThenInclude(l2 => l2.OneToMany_Optional2.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(1)).ThenInclude(l3 => l3.OneToMany_Required3), + new List + { + new ExpectedInclude( + e => e.OneToMany_Optional1, + "OneToMany_Optional1"), + new ExpectedFilteredInclude( + e => e.OneToMany_Optional2, + "OneToMany_Optional2", + "OneToMany_Optional1", + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(1)), + new ExpectedInclude( + e => e.OneToMany_Optional3, + "OneToMany_Optional3", + "OneToMany_Optional1.OneToMany_Optional2"), + new ExpectedInclude( + e => e.OneToMany_Required3, + "OneToMany_Required3", + "OneToMany_Optional1.OneToMany_Optional2"), + }); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_complex_three_level_with_middle_having_filter2(bool async) + { + return AssertIncludeQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1).ThenInclude(l2 => l2.OneToMany_Optional2.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(1)).ThenInclude(l3 => l3.OneToMany_Optional3) + .Include(l1 => l1.OneToMany_Optional1).ThenInclude(l2 => l2.OneToMany_Optional2).ThenInclude(l3 => l3.OneToMany_Required3), + new List + { + new ExpectedInclude( + e => e.OneToMany_Optional1, + "OneToMany_Optional1"), + new ExpectedFilteredInclude( + e => e.OneToMany_Optional2, + "OneToMany_Optional2", + "OneToMany_Optional1", + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(1)), + new ExpectedInclude( + e => e.OneToMany_Optional3, + "OneToMany_Optional3", + "OneToMany_Optional1.OneToMany_Optional2"), + new ExpectedInclude( + e => e.OneToMany_Required3, + "OneToMany_Required3", + "OneToMany_Optional1.OneToMany_Optional2"), + }); + } + + [ConditionalFact] + public virtual void Filtered_include_variable_used_inside_filter() + { + using var ctx = CreateContext(); + var prm = "Foo"; + var query = ctx.LevelOne + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != prm).OrderBy(x => x.Id).Take(3)); + var result = query.ToList(); + } + + [ConditionalFact] + public virtual void Filtered_include_context_accessed_inside_filter() + { + using var ctx = CreateContext(); + var query = ctx.LevelOne + .Include(l1 => l1.OneToMany_Optional1.Where(x => ctx.LevelOne.Count() > 7).OrderBy(x => x.Id).Take(3)); + var result = query.ToList(); + } + + [ConditionalFact] + public virtual void Filtered_include_context_accessed_inside_filter_correlated() + { + using var ctx = CreateContext(); + var query = ctx.LevelOne + .Include(l1 => l1.OneToMany_Optional1.Where(x => ctx.LevelOne.Count(xx => xx.Id != x.Id) > 1).OrderBy(x => x.Id).Take(3)); + var result = query.ToList(); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Filtered_include_include_parameter_used_inside_filter_throws(bool async) + { + await Assert.ThrowsAsync( + () => AssertQuery( + async, + ss => ss.Set() + .Select(l1 => ss.Set().Include(l2 => l2.OneToMany_Optional2.Where(x => x.Id != l2.Id))))); + } + + [ConditionalFact] + public virtual void Filtered_include_outer_parameter_used_inside_filter() + { + // TODO: needs #18191 for result verification + using var ctx = CreateContext(); + var query = ctx.LevelOne.Select(l1 => new + { + l1.Id, + FullInclude = ctx.LevelTwo.Include(l2 => l2.OneToMany_Optional2).ToList(), + FilteredInclude = ctx.LevelTwo.Include(l2 => l2.OneToMany_Optional2.Where(x => x.Id != l1.Id)).ToList() }); + var result = query.ToList(); + } + + [ConditionalFact] + public virtual void Filtered_include_is_considered_loaded() + { + using var ctx = CreateContext(); + var query = ctx.LevelOne.AsTracking().Include(l1 => l1.OneToMany_Optional1.OrderBy(x => x.Id).Take(1)); + var result = query.ToList(); + foreach (var resultElement in result) + { + var entry = ctx.Entry(resultElement); + Assert.True(entry.Navigation("OneToMany_Optional1").IsLoaded); + } + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Filtered_include_with_Distinct_throws(bool async) + { + var message = (await Assert.ThrowsAsync( + () => AssertQuery( + async, + ss => ss.Set().Include(l1 => l1.OneToMany_Optional1.Distinct())))).Message; + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Filtered_include_calling_methods_directly_on_parameter_throws(bool async) + { + var message = (await Assert.ThrowsAsync( + () => AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1) + .ThenInclude(l2 => l2.AsQueryable().Where(xx => xx.Id != 42))))).Message; + } } } diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryTestBase.cs index 9d1212c727e..fb47a9c9812 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsWeakQueryTestBase.cs @@ -169,5 +169,10 @@ public override Task Include_inside_subquery(bool async) { return base.Include_inside_subquery(async); } + + public override void Filtered_include_outer_parameter_used_inside_filter() + { + // TODO: this test can be ran with weak entities once #18191 is fixed and we can use query test infra properly + } } } diff --git a/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs index bce3a83998b..36e562d7af2 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindIncludeQueryTestBase.cs @@ -124,7 +124,7 @@ public virtual void Include_property_expression_invalid() Expression.Parameter(typeof(Order), "o")); Assert.Equal( - CoreStrings.InvalidIncludeLambdaExpression("Include", lambdaExpression.ToString()), + CoreStrings.InvalidIncludeExpression(lambdaExpression.Body.ToString()), Assert.Throws( () => { @@ -196,7 +196,7 @@ public virtual void Then_include_property_expression_invalid() Expression.Parameter(typeof(Order), "o")); Assert.Equal( - CoreStrings.InvalidIncludeLambdaExpression("ThenInclude", lambdaExpression.ToString()), + CoreStrings.InvalidIncludeExpression(lambdaExpression.Body.ToString()), Assert.Throws( () => { diff --git a/test/EFCore.Specification.Tests/TestUtilities/ExpectedFilteredInclude.cs b/test/EFCore.Specification.Tests/TestUtilities/ExpectedFilteredInclude.cs new file mode 100644 index 00000000000..d00bbab0511 --- /dev/null +++ b/test/EFCore.Specification.Tests/TestUtilities/ExpectedFilteredInclude.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.EntityFrameworkCore.TestUtilities +{ + public class ExpectedFilteredInclude : ExpectedInclude + { + public Func, IEnumerable> IncludeFilter { get; } + + public ExpectedFilteredInclude( + Func> include, + string includedName, + string navigationPath = "", + Func, IEnumerable> includeFilter = null) + : base(include, includedName, navigationPath) + { + IncludeFilter = includeFilter; + } + } +} diff --git a/test/EFCore.Specification.Tests/TestUtilities/IncludeQueryResultAsserter.cs b/test/EFCore.Specification.Tests/TestUtilities/IncludeQueryResultAsserter.cs index e675ae80e0a..0402786177e 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/IncludeQueryResultAsserter.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/IncludeQueryResultAsserter.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Reflection; using Xunit; @@ -15,6 +17,7 @@ public class IncludeQueryResultAsserter private readonly MethodInfo _assertCollectionMethodInfo; private readonly Dictionary _entitySorters; private readonly Dictionary _entityAsserters; + private readonly MethodInfo _filterMethodInfo; private List _path; private Stack _fullPath; @@ -28,6 +31,7 @@ public IncludeQueryResultAsserter( _assertElementMethodInfo = typeof(IncludeQueryResultAsserter).GetTypeInfo().GetDeclaredMethod(nameof(AssertElement)); _assertCollectionMethodInfo = typeof(IncludeQueryResultAsserter).GetTypeInfo().GetDeclaredMethod(nameof(AssertCollection)); + _filterMethodInfo = typeof(IncludeQueryResultAsserter).GetTypeInfo().GetDeclaredMethod(nameof(Filter)); } public virtual void AssertResult(object expected, object actual, IEnumerable expectedIncludes) @@ -149,9 +153,22 @@ protected virtual void AssertCollection( protected void ProcessIncludes(TEntity expected, TEntity actual, IEnumerable expectedIncludes) { var currentPath = string.Join(".", _path); + foreach (var expectedInclude in expectedIncludes.OfType>().Where(i => i.NavigationPath == currentPath)) { var expectedIncludedNavigation = expectedInclude.Include(expected); + if (expectedInclude.GetType().BaseType != typeof(object)) + { + var includedType = expectedInclude.GetType().GetGenericArguments()[1]; + var filterTypedMethod = _filterMethodInfo.MakeGenericMethod(typeof(TEntity), includedType); + expectedIncludedNavigation = filterTypedMethod.Invoke( + this, + BindingFlags.NonPublic, + null, + new object[] { expectedIncludedNavigation, expectedInclude }, + CultureInfo.CurrentCulture); + } + var actualIncludedNavigation = expectedInclude.Include(actual); _path.Add(expectedInclude.IncludedName); @@ -164,6 +181,11 @@ protected void ProcessIncludes(TEntity expected, TEntity actual, IEnume } } + private IEnumerable Filter( + IEnumerable expected, + ExpectedFilteredInclude expectedFilteredInclude) + => expectedFilteredInclude.IncludeFilter(expected); + // for debugging purposes protected string FullPath => string.Join(string.Empty, _fullPath.Reverse()); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs index 570051b9f4b..01878802fda 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs @@ -4476,6 +4476,437 @@ FROM [LevelOne] AS [l1] LEFT JOIN [LevelTwo] AS [l3] ON [l1].[Id] = [l3].[OneToOne_Optional_PK_Inverse2Id]"); } + public override async Task Filtered_include_basic_Where(bool async) + { + await base.Filtered_include_basic_Where(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id] +FROM [LevelOne] AS [l] +LEFT JOIN ( + SELECT [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE [l0].[Id] > 5 +) AS [t] ON [l].[Id] = [t].[OneToMany_Optional_Inverse2Id] +ORDER BY [l].[Id], [t].[Id]"); + } + + public override async Task Filtered_include_basic_OrderBy_Take(bool async) + { + await base.Filtered_include_basic_OrderBy_Take(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id] +FROM [LevelOne] AS [l] +OUTER APPLY ( + SELECT TOP(3) [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE [l].[Id] = [l0].[OneToMany_Optional_Inverse2Id] + ORDER BY [l0].[Name] +) AS [t] +ORDER BY [l].[Id], [t].[Name], [t].[Id]"); + } + + public override async Task Filtered_include_basic_OrderBy_Skip(bool async) + { + await base.Filtered_include_basic_OrderBy_Skip(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id] +FROM [LevelOne] AS [l] +OUTER APPLY ( + SELECT [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE [l].[Id] = [l0].[OneToMany_Optional_Inverse2Id] + ORDER BY [l0].[Name] + OFFSET 1 ROWS +) AS [t] +ORDER BY [l].[Id], [t].[Name], [t].[Id]"); + } + + public override async Task Filtered_include_basic_OrderBy_Skip_Take(bool async) + { + await base.Filtered_include_basic_OrderBy_Skip_Take(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id] +FROM [LevelOne] AS [l] +OUTER APPLY ( + SELECT [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE [l].[Id] = [l0].[OneToMany_Optional_Inverse2Id] + ORDER BY [l0].[Name] + OFFSET 1 ROWS FETCH NEXT 3 ROWS ONLY +) AS [t] +ORDER BY [l].[Id], [t].[Name], [t].[Id]"); + } + + public override void Filtered_include_Skip_without_OrderBy() + { + base.Filtered_include_Skip_without_OrderBy(); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id] +FROM [LevelOne] AS [l] +OUTER APPLY ( + SELECT [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE [l].[Id] = [l0].[OneToMany_Optional_Inverse2Id] + ORDER BY (SELECT 1) + OFFSET 1 ROWS +) AS [t] +ORDER BY [l].[Id], [t].[Id]"); + } + + public override void Filtered_include_Take_without_OrderBy() + { + base.Filtered_include_Take_without_OrderBy(); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id] +FROM [LevelOne] AS [l] +OUTER APPLY ( + SELECT [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE [l].[Id] = [l0].[OneToMany_Optional_Inverse2Id] + ORDER BY (SELECT 1) + OFFSET 1 ROWS +) AS [t] +ORDER BY [l].[Id], [t].[Id]"); + } + + public override async Task Filtered_include_on_ThenInclude(bool async) + { + await base.Filtered_include_on_ThenInclude(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id], [t].[Id], [t].[Level2_Optional_Id], [t].[Level2_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse3Id], [t].[OneToMany_Optional_Self_Inverse3Id], [t].[OneToMany_Required_Inverse3Id], [t].[OneToMany_Required_Self_Inverse3Id], [t].[OneToOne_Optional_PK_Inverse3Id], [t].[OneToOne_Optional_Self3Id] +FROM [LevelOne] AS [l] +LEFT JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[Level1_Optional_Id] +OUTER APPLY ( + SELECT [l1].[Id], [l1].[Level2_Optional_Id], [l1].[Level2_Required_Id], [l1].[Name], [l1].[OneToMany_Optional_Inverse3Id], [l1].[OneToMany_Optional_Self_Inverse3Id], [l1].[OneToMany_Required_Inverse3Id], [l1].[OneToMany_Required_Self_Inverse3Id], [l1].[OneToOne_Optional_PK_Inverse3Id], [l1].[OneToOne_Optional_Self3Id] + FROM [LevelThree] AS [l1] + WHERE ([l0].[Id] IS NOT NULL AND ([l0].[Id] = [l1].[OneToMany_Optional_Inverse3Id])) AND (([l1].[Name] <> N'Foo') OR [l1].[Name] IS NULL) + ORDER BY [l1].[Name] + OFFSET 1 ROWS FETCH NEXT 3 ROWS ONLY +) AS [t] +ORDER BY [l].[Id], [t].[Name], [t].[Id]"); + } + + public override async Task Filtered_include_after_reference_navigation(bool async) + { + await base.Filtered_include_after_reference_navigation(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id], [t].[Id], [t].[Level2_Optional_Id], [t].[Level2_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse3Id], [t].[OneToMany_Optional_Self_Inverse3Id], [t].[OneToMany_Required_Inverse3Id], [t].[OneToMany_Required_Self_Inverse3Id], [t].[OneToOne_Optional_PK_Inverse3Id], [t].[OneToOne_Optional_Self3Id] +FROM [LevelOne] AS [l] +LEFT JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[Level1_Optional_Id] +OUTER APPLY ( + SELECT [l1].[Id], [l1].[Level2_Optional_Id], [l1].[Level2_Required_Id], [l1].[Name], [l1].[OneToMany_Optional_Inverse3Id], [l1].[OneToMany_Optional_Self_Inverse3Id], [l1].[OneToMany_Required_Inverse3Id], [l1].[OneToMany_Required_Self_Inverse3Id], [l1].[OneToOne_Optional_PK_Inverse3Id], [l1].[OneToOne_Optional_Self3Id] + FROM [LevelThree] AS [l1] + WHERE ([l0].[Id] IS NOT NULL AND ([l0].[Id] = [l1].[OneToMany_Optional_Inverse3Id])) AND (([l1].[Name] <> N'Foo') OR [l1].[Name] IS NULL) + ORDER BY [l1].[Name] + OFFSET 1 ROWS FETCH NEXT 3 ROWS ONLY +) AS [t] +ORDER BY [l].[Id], [t].[Name], [t].[Id]"); + } + + public override async Task Filtered_include_after_different_filtered_include_same_level(bool async) + { + await base.Filtered_include_after_different_filtered_include_same_level(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id], [t0].[Id], [t0].[Date], [t0].[Level1_Optional_Id], [t0].[Level1_Required_Id], [t0].[Name], [t0].[OneToMany_Optional_Inverse2Id], [t0].[OneToMany_Optional_Self_Inverse2Id], [t0].[OneToMany_Required_Inverse2Id], [t0].[OneToMany_Required_Self_Inverse2Id], [t0].[OneToOne_Optional_PK_Inverse2Id], [t0].[OneToOne_Optional_Self2Id] +FROM [LevelOne] AS [l] +OUTER APPLY ( + SELECT TOP(3) [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE ([l].[Id] = [l0].[OneToMany_Optional_Inverse2Id]) AND (([l0].[Name] <> N'Foo') OR [l0].[Name] IS NULL) + ORDER BY [l0].[Name] +) AS [t] +OUTER APPLY ( + SELECT [l1].[Id], [l1].[Date], [l1].[Level1_Optional_Id], [l1].[Level1_Required_Id], [l1].[Name], [l1].[OneToMany_Optional_Inverse2Id], [l1].[OneToMany_Optional_Self_Inverse2Id], [l1].[OneToMany_Required_Inverse2Id], [l1].[OneToMany_Required_Self_Inverse2Id], [l1].[OneToOne_Optional_PK_Inverse2Id], [l1].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l1] + WHERE ([l].[Id] = [l1].[OneToMany_Required_Inverse2Id]) AND (([l1].[Name] <> N'Bar') OR [l1].[Name] IS NULL) + ORDER BY [l1].[Name] DESC + OFFSET 1 ROWS +) AS [t0] +ORDER BY [l].[Id], [t].[Name], [t].[Id], [t0].[Name] DESC, [t0].[Id]"); + } + + public override async Task Filtered_include_after_different_filtered_include_different_level(bool async) + { + await base.Filtered_include_after_different_filtered_include_different_level(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t1].[Id], [t1].[Date], [t1].[Level1_Optional_Id], [t1].[Level1_Required_Id], [t1].[Name], [t1].[OneToMany_Optional_Inverse2Id], [t1].[OneToMany_Optional_Self_Inverse2Id], [t1].[OneToMany_Required_Inverse2Id], [t1].[OneToMany_Required_Self_Inverse2Id], [t1].[OneToOne_Optional_PK_Inverse2Id], [t1].[OneToOne_Optional_Self2Id], [t1].[Id0], [t1].[Level2_Optional_Id], [t1].[Level2_Required_Id], [t1].[Name0], [t1].[OneToMany_Optional_Inverse3Id], [t1].[OneToMany_Optional_Self_Inverse3Id], [t1].[OneToMany_Required_Inverse3Id], [t1].[OneToMany_Required_Self_Inverse3Id], [t1].[OneToOne_Optional_PK_Inverse3Id], [t1].[OneToOne_Optional_Self3Id] +FROM [LevelOne] AS [l] +OUTER APPLY ( + SELECT [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id], [t0].[Id] AS [Id0], [t0].[Level2_Optional_Id], [t0].[Level2_Required_Id], [t0].[Name] AS [Name0], [t0].[OneToMany_Optional_Inverse3Id], [t0].[OneToMany_Optional_Self_Inverse3Id], [t0].[OneToMany_Required_Inverse3Id], [t0].[OneToMany_Required_Self_Inverse3Id], [t0].[OneToOne_Optional_PK_Inverse3Id], [t0].[OneToOne_Optional_Self3Id] + FROM ( + SELECT TOP(3) [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE ([l].[Id] = [l0].[OneToMany_Optional_Inverse2Id]) AND (([l0].[Name] <> N'Foo') OR [l0].[Name] IS NULL) + ORDER BY [l0].[Name] + ) AS [t] + OUTER APPLY ( + SELECT [l1].[Id], [l1].[Level2_Optional_Id], [l1].[Level2_Required_Id], [l1].[Name], [l1].[OneToMany_Optional_Inverse3Id], [l1].[OneToMany_Optional_Self_Inverse3Id], [l1].[OneToMany_Required_Inverse3Id], [l1].[OneToMany_Required_Self_Inverse3Id], [l1].[OneToOne_Optional_PK_Inverse3Id], [l1].[OneToOne_Optional_Self3Id] + FROM [LevelThree] AS [l1] + WHERE ([t].[Id] = [l1].[OneToMany_Required_Inverse3Id]) AND (([l1].[Name] <> N'Bar') OR [l1].[Name] IS NULL) + ORDER BY [l1].[Name] DESC + OFFSET 1 ROWS + ) AS [t0] +) AS [t1] +ORDER BY [l].[Id], [t1].[Name], [t1].[Id], [t1].[Name0] DESC, [t1].[Id0]"); + } + + public override async Task Filtered_include_same_filter_set_on_same_navigation_twice(bool async) + { + await base.Filtered_include_same_filter_set_on_same_navigation_twice(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id] +FROM [LevelOne] AS [l] +OUTER APPLY ( + SELECT TOP(2) [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE ([l].[Id] = [l0].[OneToMany_Optional_Inverse2Id]) AND (([l0].[Name] <> N'Foo') OR [l0].[Name] IS NULL) + ORDER BY [l0].[Id] DESC +) AS [t] +ORDER BY [l].[Id], [t].[Id] DESC"); + } + + public override async Task Filtered_include_same_filter_set_on_same_navigation_twice_followed_by_ThenIncludes(bool async) + { + await base.Filtered_include_same_filter_set_on_same_navigation_twice_followed_by_ThenIncludes(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t0].[Id], [t0].[Date], [t0].[Level1_Optional_Id], [t0].[Level1_Required_Id], [t0].[Name], [t0].[OneToMany_Optional_Inverse2Id], [t0].[OneToMany_Optional_Self_Inverse2Id], [t0].[OneToMany_Required_Inverse2Id], [t0].[OneToMany_Required_Self_Inverse2Id], [t0].[OneToOne_Optional_PK_Inverse2Id], [t0].[OneToOne_Optional_Self2Id], [t0].[Id0], [t0].[Level2_Optional_Id], [t0].[Level2_Required_Id], [t0].[Name0], [t0].[OneToMany_Optional_Inverse3Id], [t0].[OneToMany_Optional_Self_Inverse3Id], [t0].[OneToMany_Required_Inverse3Id], [t0].[OneToMany_Required_Self_Inverse3Id], [t0].[OneToOne_Optional_PK_Inverse3Id], [t0].[OneToOne_Optional_Self3Id], [t0].[Id1], [t0].[Level2_Optional_Id0], [t0].[Level2_Required_Id0], [t0].[Name1], [t0].[OneToMany_Optional_Inverse3Id0], [t0].[OneToMany_Optional_Self_Inverse3Id0], [t0].[OneToMany_Required_Inverse3Id0], [t0].[OneToMany_Required_Self_Inverse3Id0], [t0].[OneToOne_Optional_PK_Inverse3Id0], [t0].[OneToOne_Optional_Self3Id0] +FROM [LevelOne] AS [l] +OUTER APPLY ( + SELECT [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id], [l1].[Id] AS [Id0], [l1].[Level2_Optional_Id], [l1].[Level2_Required_Id], [l1].[Name] AS [Name0], [l1].[OneToMany_Optional_Inverse3Id], [l1].[OneToMany_Optional_Self_Inverse3Id], [l1].[OneToMany_Required_Inverse3Id], [l1].[OneToMany_Required_Self_Inverse3Id], [l1].[OneToOne_Optional_PK_Inverse3Id], [l1].[OneToOne_Optional_Self3Id], [l2].[Id] AS [Id1], [l2].[Level2_Optional_Id] AS [Level2_Optional_Id0], [l2].[Level2_Required_Id] AS [Level2_Required_Id0], [l2].[Name] AS [Name1], [l2].[OneToMany_Optional_Inverse3Id] AS [OneToMany_Optional_Inverse3Id0], [l2].[OneToMany_Optional_Self_Inverse3Id] AS [OneToMany_Optional_Self_Inverse3Id0], [l2].[OneToMany_Required_Inverse3Id] AS [OneToMany_Required_Inverse3Id0], [l2].[OneToMany_Required_Self_Inverse3Id] AS [OneToMany_Required_Self_Inverse3Id0], [l2].[OneToOne_Optional_PK_Inverse3Id] AS [OneToOne_Optional_PK_Inverse3Id0], [l2].[OneToOne_Optional_Self3Id] AS [OneToOne_Optional_Self3Id0] + FROM ( + SELECT TOP(2) [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE ([l].[Id] = [l0].[OneToMany_Optional_Inverse2Id]) AND (([l0].[Name] <> N'Foo') OR [l0].[Name] IS NULL) + ORDER BY [l0].[Id] + ) AS [t] + LEFT JOIN [LevelThree] AS [l1] ON [t].[Id] = [l1].[Level2_Required_Id] + LEFT JOIN [LevelThree] AS [l2] ON [t].[Id] = [l2].[OneToMany_Optional_Inverse3Id] +) AS [t0] +ORDER BY [l].[Id], [t0].[Id], [t0].[Id1]"); + } + + public override async Task Filtered_include_multiple_multi_level_includes_with_first_level_using_filter_include_on_one_of_the_chains_only(bool async) + { + await base.Filtered_include_multiple_multi_level_includes_with_first_level_using_filter_include_on_one_of_the_chains_only(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t0].[Id], [t0].[Date], [t0].[Level1_Optional_Id], [t0].[Level1_Required_Id], [t0].[Name], [t0].[OneToMany_Optional_Inverse2Id], [t0].[OneToMany_Optional_Self_Inverse2Id], [t0].[OneToMany_Required_Inverse2Id], [t0].[OneToMany_Required_Self_Inverse2Id], [t0].[OneToOne_Optional_PK_Inverse2Id], [t0].[OneToOne_Optional_Self2Id], [t0].[Id0], [t0].[Level2_Optional_Id], [t0].[Level2_Required_Id], [t0].[Name0], [t0].[OneToMany_Optional_Inverse3Id], [t0].[OneToMany_Optional_Self_Inverse3Id], [t0].[OneToMany_Required_Inverse3Id], [t0].[OneToMany_Required_Self_Inverse3Id], [t0].[OneToOne_Optional_PK_Inverse3Id], [t0].[OneToOne_Optional_Self3Id], [t0].[Id1], [t0].[Level2_Optional_Id0], [t0].[Level2_Required_Id0], [t0].[Name1], [t0].[OneToMany_Optional_Inverse3Id0], [t0].[OneToMany_Optional_Self_Inverse3Id0], [t0].[OneToMany_Required_Inverse3Id0], [t0].[OneToMany_Required_Self_Inverse3Id0], [t0].[OneToOne_Optional_PK_Inverse3Id0], [t0].[OneToOne_Optional_Self3Id0] +FROM [LevelOne] AS [l] +OUTER APPLY ( + SELECT [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id], [l1].[Id] AS [Id0], [l1].[Level2_Optional_Id], [l1].[Level2_Required_Id], [l1].[Name] AS [Name0], [l1].[OneToMany_Optional_Inverse3Id], [l1].[OneToMany_Optional_Self_Inverse3Id], [l1].[OneToMany_Required_Inverse3Id], [l1].[OneToMany_Required_Self_Inverse3Id], [l1].[OneToOne_Optional_PK_Inverse3Id], [l1].[OneToOne_Optional_Self3Id], [l2].[Id] AS [Id1], [l2].[Level2_Optional_Id] AS [Level2_Optional_Id0], [l2].[Level2_Required_Id] AS [Level2_Required_Id0], [l2].[Name] AS [Name1], [l2].[OneToMany_Optional_Inverse3Id] AS [OneToMany_Optional_Inverse3Id0], [l2].[OneToMany_Optional_Self_Inverse3Id] AS [OneToMany_Optional_Self_Inverse3Id0], [l2].[OneToMany_Required_Inverse3Id] AS [OneToMany_Required_Inverse3Id0], [l2].[OneToMany_Required_Self_Inverse3Id] AS [OneToMany_Required_Self_Inverse3Id0], [l2].[OneToOne_Optional_PK_Inverse3Id] AS [OneToOne_Optional_PK_Inverse3Id0], [l2].[OneToOne_Optional_Self3Id] AS [OneToOne_Optional_Self3Id0] + FROM ( + SELECT TOP(2) [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE ([l].[Id] = [l0].[OneToMany_Optional_Inverse2Id]) AND (([l0].[Name] <> N'Foo') OR [l0].[Name] IS NULL) + ORDER BY [l0].[Id] + ) AS [t] + LEFT JOIN [LevelThree] AS [l1] ON [t].[Id] = [l1].[Level2_Required_Id] + LEFT JOIN [LevelThree] AS [l2] ON [t].[Id] = [l2].[OneToMany_Optional_Inverse3Id] +) AS [t0] +ORDER BY [l].[Id], [t0].[Id], [t0].[Id1]"); + } + + public override async Task Filtered_include_and_non_filtered_include_on_same_navigation1(bool async) + { + await base.Filtered_include_and_non_filtered_include_on_same_navigation1(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id] +FROM [LevelOne] AS [l] +OUTER APPLY ( + SELECT TOP(3) [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE ([l].[Id] = [l0].[OneToMany_Optional_Inverse2Id]) AND (([l0].[Name] <> N'Foo') OR [l0].[Name] IS NULL) + ORDER BY [l0].[Id] +) AS [t] +ORDER BY [l].[Id], [t].[Id]"); + } + + public override async Task Filtered_include_and_non_filtered_include_on_same_navigation2(bool async) + { + await base.Filtered_include_and_non_filtered_include_on_same_navigation2(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id] +FROM [LevelOne] AS [l] +OUTER APPLY ( + SELECT TOP(3) [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE ([l].[Id] = [l0].[OneToMany_Optional_Inverse2Id]) AND (([l0].[Name] <> N'Foo') OR [l0].[Name] IS NULL) + ORDER BY [l0].[Id] +) AS [t] +ORDER BY [l].[Id], [t].[Id]"); + } + + public override async Task Filtered_include_and_non_filtered_include_followed_by_then_include_on_same_navigation(bool async) + { + await base.Filtered_include_and_non_filtered_include_followed_by_then_include_on_same_navigation(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t1].[Id], [t1].[Date], [t1].[Level1_Optional_Id], [t1].[Level1_Required_Id], [t1].[Name], [t1].[OneToMany_Optional_Inverse2Id], [t1].[OneToMany_Optional_Self_Inverse2Id], [t1].[OneToMany_Required_Inverse2Id], [t1].[OneToMany_Required_Self_Inverse2Id], [t1].[OneToOne_Optional_PK_Inverse2Id], [t1].[OneToOne_Optional_Self2Id], [t1].[Id0], [t1].[Level2_Optional_Id], [t1].[Level2_Required_Id], [t1].[Name0], [t1].[OneToMany_Optional_Inverse3Id], [t1].[OneToMany_Optional_Self_Inverse3Id], [t1].[OneToMany_Required_Inverse3Id], [t1].[OneToMany_Required_Self_Inverse3Id], [t1].[OneToOne_Optional_PK_Inverse3Id], [t1].[OneToOne_Optional_Self3Id], [t1].[Id1], [t1].[Level3_Optional_Id], [t1].[Level3_Required_Id], [t1].[Name1], [t1].[OneToMany_Optional_Inverse4Id], [t1].[OneToMany_Optional_Self_Inverse4Id], [t1].[OneToMany_Required_Inverse4Id], [t1].[OneToMany_Required_Self_Inverse4Id], [t1].[OneToOne_Optional_PK_Inverse4Id], [t1].[OneToOne_Optional_Self4Id] +FROM [LevelOne] AS [l] +OUTER APPLY ( + SELECT [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id], [l1].[Id] AS [Id0], [l1].[Level2_Optional_Id], [l1].[Level2_Required_Id], [l1].[Name] AS [Name0], [l1].[OneToMany_Optional_Inverse3Id], [l1].[OneToMany_Optional_Self_Inverse3Id], [l1].[OneToMany_Required_Inverse3Id], [l1].[OneToMany_Required_Self_Inverse3Id], [l1].[OneToOne_Optional_PK_Inverse3Id], [l1].[OneToOne_Optional_Self3Id], [t0].[Id] AS [Id1], [t0].[Level3_Optional_Id], [t0].[Level3_Required_Id], [t0].[Name] AS [Name1], [t0].[OneToMany_Optional_Inverse4Id], [t0].[OneToMany_Optional_Self_Inverse4Id], [t0].[OneToMany_Required_Inverse4Id], [t0].[OneToMany_Required_Self_Inverse4Id], [t0].[OneToOne_Optional_PK_Inverse4Id], [t0].[OneToOne_Optional_Self4Id] + FROM ( + SELECT TOP(1) [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE ([l].[Id] = [l0].[OneToMany_Optional_Inverse2Id]) AND (([l0].[Name] <> N'Foo') OR [l0].[Name] IS NULL) + ORDER BY [l0].[Id] + ) AS [t] + LEFT JOIN [LevelThree] AS [l1] ON [t].[Id] = [l1].[OneToOne_Optional_PK_Inverse3Id] + LEFT JOIN ( + SELECT [l2].[Id], [l2].[Level3_Optional_Id], [l2].[Level3_Required_Id], [l2].[Name], [l2].[OneToMany_Optional_Inverse4Id], [l2].[OneToMany_Optional_Self_Inverse4Id], [l2].[OneToMany_Required_Inverse4Id], [l2].[OneToMany_Required_Self_Inverse4Id], [l2].[OneToOne_Optional_PK_Inverse4Id], [l2].[OneToOne_Optional_Self4Id] + FROM [LevelFour] AS [l2] + WHERE [l2].[Id] > 1 + ) AS [t0] ON [l1].[Id] = [t0].[OneToMany_Optional_Inverse4Id] +) AS [t1] +ORDER BY [l].[Id], [t1].[Id], [t1].[Id1]"); + } + + public override async Task Filtered_include_complex_three_level_with_middle_having_filter1(bool async) + { + await base.Filtered_include_complex_three_level_with_middle_having_filter1(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t1].[Id], [t1].[Date], [t1].[Level1_Optional_Id], [t1].[Level1_Required_Id], [t1].[Name], [t1].[OneToMany_Optional_Inverse2Id], [t1].[OneToMany_Optional_Self_Inverse2Id], [t1].[OneToMany_Required_Inverse2Id], [t1].[OneToMany_Required_Self_Inverse2Id], [t1].[OneToOne_Optional_PK_Inverse2Id], [t1].[OneToOne_Optional_Self2Id], [t1].[Id0], [t1].[Level2_Optional_Id], [t1].[Level2_Required_Id], [t1].[Name0], [t1].[OneToMany_Optional_Inverse3Id], [t1].[OneToMany_Optional_Self_Inverse3Id], [t1].[OneToMany_Required_Inverse3Id], [t1].[OneToMany_Required_Self_Inverse3Id], [t1].[OneToOne_Optional_PK_Inverse3Id], [t1].[OneToOne_Optional_Self3Id], [t1].[Id00], [t1].[Level3_Optional_Id], [t1].[Level3_Required_Id], [t1].[Name00], [t1].[OneToMany_Optional_Inverse4Id], [t1].[OneToMany_Optional_Self_Inverse4Id], [t1].[OneToMany_Required_Inverse4Id], [t1].[OneToMany_Required_Self_Inverse4Id], [t1].[OneToOne_Optional_PK_Inverse4Id], [t1].[OneToOne_Optional_Self4Id], [t1].[Id1], [t1].[Level3_Optional_Id0], [t1].[Level3_Required_Id0], [t1].[Name1], [t1].[OneToMany_Optional_Inverse4Id0], [t1].[OneToMany_Optional_Self_Inverse4Id0], [t1].[OneToMany_Required_Inverse4Id0], [t1].[OneToMany_Required_Self_Inverse4Id0], [t1].[OneToOne_Optional_PK_Inverse4Id0], [t1].[OneToOne_Optional_Self4Id0] +FROM [LevelOne] AS [l] +LEFT JOIN ( + SELECT [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id], [t0].[Id] AS [Id0], [t0].[Level2_Optional_Id], [t0].[Level2_Required_Id], [t0].[Name] AS [Name0], [t0].[OneToMany_Optional_Inverse3Id], [t0].[OneToMany_Optional_Self_Inverse3Id], [t0].[OneToMany_Required_Inverse3Id], [t0].[OneToMany_Required_Self_Inverse3Id], [t0].[OneToOne_Optional_PK_Inverse3Id], [t0].[OneToOne_Optional_Self3Id], [t0].[Id0] AS [Id00], [t0].[Level3_Optional_Id], [t0].[Level3_Required_Id], [t0].[Name0] AS [Name00], [t0].[OneToMany_Optional_Inverse4Id], [t0].[OneToMany_Optional_Self_Inverse4Id], [t0].[OneToMany_Required_Inverse4Id], [t0].[OneToMany_Required_Self_Inverse4Id], [t0].[OneToOne_Optional_PK_Inverse4Id], [t0].[OneToOne_Optional_Self4Id], [t0].[Id1], [t0].[Level3_Optional_Id0], [t0].[Level3_Required_Id0], [t0].[Name1], [t0].[OneToMany_Optional_Inverse4Id0], [t0].[OneToMany_Optional_Self_Inverse4Id0], [t0].[OneToMany_Required_Inverse4Id0], [t0].[OneToMany_Required_Self_Inverse4Id0], [t0].[OneToOne_Optional_PK_Inverse4Id0], [t0].[OneToOne_Optional_Self4Id0] + FROM [LevelTwo] AS [l0] + OUTER APPLY ( + SELECT [t].[Id], [t].[Level2_Optional_Id], [t].[Level2_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse3Id], [t].[OneToMany_Optional_Self_Inverse3Id], [t].[OneToMany_Required_Inverse3Id], [t].[OneToMany_Required_Self_Inverse3Id], [t].[OneToOne_Optional_PK_Inverse3Id], [t].[OneToOne_Optional_Self3Id], [l2].[Id] AS [Id0], [l2].[Level3_Optional_Id], [l2].[Level3_Required_Id], [l2].[Name] AS [Name0], [l2].[OneToMany_Optional_Inverse4Id], [l2].[OneToMany_Optional_Self_Inverse4Id], [l2].[OneToMany_Required_Inverse4Id], [l2].[OneToMany_Required_Self_Inverse4Id], [l2].[OneToOne_Optional_PK_Inverse4Id], [l2].[OneToOne_Optional_Self4Id], [l3].[Id] AS [Id1], [l3].[Level3_Optional_Id] AS [Level3_Optional_Id0], [l3].[Level3_Required_Id] AS [Level3_Required_Id0], [l3].[Name] AS [Name1], [l3].[OneToMany_Optional_Inverse4Id] AS [OneToMany_Optional_Inverse4Id0], [l3].[OneToMany_Optional_Self_Inverse4Id] AS [OneToMany_Optional_Self_Inverse4Id0], [l3].[OneToMany_Required_Inverse4Id] AS [OneToMany_Required_Inverse4Id0], [l3].[OneToMany_Required_Self_Inverse4Id] AS [OneToMany_Required_Self_Inverse4Id0], [l3].[OneToOne_Optional_PK_Inverse4Id] AS [OneToOne_Optional_PK_Inverse4Id0], [l3].[OneToOne_Optional_Self4Id] AS [OneToOne_Optional_Self4Id0] + FROM ( + SELECT TOP(1) [l1].[Id], [l1].[Level2_Optional_Id], [l1].[Level2_Required_Id], [l1].[Name], [l1].[OneToMany_Optional_Inverse3Id], [l1].[OneToMany_Optional_Self_Inverse3Id], [l1].[OneToMany_Required_Inverse3Id], [l1].[OneToMany_Required_Self_Inverse3Id], [l1].[OneToOne_Optional_PK_Inverse3Id], [l1].[OneToOne_Optional_Self3Id] + FROM [LevelThree] AS [l1] + WHERE ([l0].[Id] = [l1].[OneToMany_Optional_Inverse3Id]) AND (([l1].[Name] <> N'Foo') OR [l1].[Name] IS NULL) + ORDER BY [l1].[Id] + ) AS [t] + LEFT JOIN [LevelFour] AS [l2] ON [t].[Id] = [l2].[OneToMany_Optional_Inverse4Id] + LEFT JOIN [LevelFour] AS [l3] ON [t].[Id] = [l3].[OneToMany_Required_Inverse4Id] + ) AS [t0] +) AS [t1] ON [l].[Id] = [t1].[OneToMany_Optional_Inverse2Id] +ORDER BY [l].[Id], [t1].[Id], [t1].[Id0], [t1].[Id00], [t1].[Id1]"); + } + + public override async Task Filtered_include_complex_three_level_with_middle_having_filter2(bool async) + { + await base.Filtered_include_complex_three_level_with_middle_having_filter2(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t1].[Id], [t1].[Date], [t1].[Level1_Optional_Id], [t1].[Level1_Required_Id], [t1].[Name], [t1].[OneToMany_Optional_Inverse2Id], [t1].[OneToMany_Optional_Self_Inverse2Id], [t1].[OneToMany_Required_Inverse2Id], [t1].[OneToMany_Required_Self_Inverse2Id], [t1].[OneToOne_Optional_PK_Inverse2Id], [t1].[OneToOne_Optional_Self2Id], [t1].[Id0], [t1].[Level2_Optional_Id], [t1].[Level2_Required_Id], [t1].[Name0], [t1].[OneToMany_Optional_Inverse3Id], [t1].[OneToMany_Optional_Self_Inverse3Id], [t1].[OneToMany_Required_Inverse3Id], [t1].[OneToMany_Required_Self_Inverse3Id], [t1].[OneToOne_Optional_PK_Inverse3Id], [t1].[OneToOne_Optional_Self3Id], [t1].[Id00], [t1].[Level3_Optional_Id], [t1].[Level3_Required_Id], [t1].[Name00], [t1].[OneToMany_Optional_Inverse4Id], [t1].[OneToMany_Optional_Self_Inverse4Id], [t1].[OneToMany_Required_Inverse4Id], [t1].[OneToMany_Required_Self_Inverse4Id], [t1].[OneToOne_Optional_PK_Inverse4Id], [t1].[OneToOne_Optional_Self4Id], [t1].[Id1], [t1].[Level3_Optional_Id0], [t1].[Level3_Required_Id0], [t1].[Name1], [t1].[OneToMany_Optional_Inverse4Id0], [t1].[OneToMany_Optional_Self_Inverse4Id0], [t1].[OneToMany_Required_Inverse4Id0], [t1].[OneToMany_Required_Self_Inverse4Id0], [t1].[OneToOne_Optional_PK_Inverse4Id0], [t1].[OneToOne_Optional_Self4Id0] +FROM [LevelOne] AS [l] +LEFT JOIN ( + SELECT [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id], [t0].[Id] AS [Id0], [t0].[Level2_Optional_Id], [t0].[Level2_Required_Id], [t0].[Name] AS [Name0], [t0].[OneToMany_Optional_Inverse3Id], [t0].[OneToMany_Optional_Self_Inverse3Id], [t0].[OneToMany_Required_Inverse3Id], [t0].[OneToMany_Required_Self_Inverse3Id], [t0].[OneToOne_Optional_PK_Inverse3Id], [t0].[OneToOne_Optional_Self3Id], [t0].[Id0] AS [Id00], [t0].[Level3_Optional_Id], [t0].[Level3_Required_Id], [t0].[Name0] AS [Name00], [t0].[OneToMany_Optional_Inverse4Id], [t0].[OneToMany_Optional_Self_Inverse4Id], [t0].[OneToMany_Required_Inverse4Id], [t0].[OneToMany_Required_Self_Inverse4Id], [t0].[OneToOne_Optional_PK_Inverse4Id], [t0].[OneToOne_Optional_Self4Id], [t0].[Id1], [t0].[Level3_Optional_Id0], [t0].[Level3_Required_Id0], [t0].[Name1], [t0].[OneToMany_Optional_Inverse4Id0], [t0].[OneToMany_Optional_Self_Inverse4Id0], [t0].[OneToMany_Required_Inverse4Id0], [t0].[OneToMany_Required_Self_Inverse4Id0], [t0].[OneToOne_Optional_PK_Inverse4Id0], [t0].[OneToOne_Optional_Self4Id0] + FROM [LevelTwo] AS [l0] + OUTER APPLY ( + SELECT [t].[Id], [t].[Level2_Optional_Id], [t].[Level2_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse3Id], [t].[OneToMany_Optional_Self_Inverse3Id], [t].[OneToMany_Required_Inverse3Id], [t].[OneToMany_Required_Self_Inverse3Id], [t].[OneToOne_Optional_PK_Inverse3Id], [t].[OneToOne_Optional_Self3Id], [l2].[Id] AS [Id0], [l2].[Level3_Optional_Id], [l2].[Level3_Required_Id], [l2].[Name] AS [Name0], [l2].[OneToMany_Optional_Inverse4Id], [l2].[OneToMany_Optional_Self_Inverse4Id], [l2].[OneToMany_Required_Inverse4Id], [l2].[OneToMany_Required_Self_Inverse4Id], [l2].[OneToOne_Optional_PK_Inverse4Id], [l2].[OneToOne_Optional_Self4Id], [l3].[Id] AS [Id1], [l3].[Level3_Optional_Id] AS [Level3_Optional_Id0], [l3].[Level3_Required_Id] AS [Level3_Required_Id0], [l3].[Name] AS [Name1], [l3].[OneToMany_Optional_Inverse4Id] AS [OneToMany_Optional_Inverse4Id0], [l3].[OneToMany_Optional_Self_Inverse4Id] AS [OneToMany_Optional_Self_Inverse4Id0], [l3].[OneToMany_Required_Inverse4Id] AS [OneToMany_Required_Inverse4Id0], [l3].[OneToMany_Required_Self_Inverse4Id] AS [OneToMany_Required_Self_Inverse4Id0], [l3].[OneToOne_Optional_PK_Inverse4Id] AS [OneToOne_Optional_PK_Inverse4Id0], [l3].[OneToOne_Optional_Self4Id] AS [OneToOne_Optional_Self4Id0] + FROM ( + SELECT TOP(1) [l1].[Id], [l1].[Level2_Optional_Id], [l1].[Level2_Required_Id], [l1].[Name], [l1].[OneToMany_Optional_Inverse3Id], [l1].[OneToMany_Optional_Self_Inverse3Id], [l1].[OneToMany_Required_Inverse3Id], [l1].[OneToMany_Required_Self_Inverse3Id], [l1].[OneToOne_Optional_PK_Inverse3Id], [l1].[OneToOne_Optional_Self3Id] + FROM [LevelThree] AS [l1] + WHERE ([l0].[Id] = [l1].[OneToMany_Optional_Inverse3Id]) AND (([l1].[Name] <> N'Foo') OR [l1].[Name] IS NULL) + ORDER BY [l1].[Id] + ) AS [t] + LEFT JOIN [LevelFour] AS [l2] ON [t].[Id] = [l2].[OneToMany_Optional_Inverse4Id] + LEFT JOIN [LevelFour] AS [l3] ON [t].[Id] = [l3].[OneToMany_Required_Inverse4Id] + ) AS [t0] +) AS [t1] ON [l].[Id] = [t1].[OneToMany_Optional_Inverse2Id] +ORDER BY [l].[Id], [t1].[Id], [t1].[Id0], [t1].[Id00], [t1].[Id1]"); + } + + public override void Filtered_include_variable_used_inside_filter() + { + base.Filtered_include_variable_used_inside_filter(); + + AssertSql( + @"@__prm_0='Foo' (Size = 4000) + +SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id] +FROM [LevelOne] AS [l] +OUTER APPLY ( + SELECT TOP(3) [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE ([l].[Id] = [l0].[OneToMany_Optional_Inverse2Id]) AND (([l0].[Name] <> @__prm_0) OR [l0].[Name] IS NULL) + ORDER BY [l0].[Id] +) AS [t] +ORDER BY [l].[Id], [t].[Id]"); + } + + public override void Filtered_include_context_accessed_inside_filter() + { + base.Filtered_include_context_accessed_inside_filter(); + + AssertSql( + @"SELECT COUNT(*) +FROM [LevelOne] AS [l]", + // + @"@__p_0='True' + +SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id] +FROM [LevelOne] AS [l] +OUTER APPLY ( + SELECT TOP(3) [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE ([l].[Id] = [l0].[OneToMany_Optional_Inverse2Id]) AND (@__p_0 = CAST(1 AS bit)) + ORDER BY [l0].[Id] +) AS [t] +ORDER BY [l].[Id], [t].[Id]"); + } + + public override void Filtered_include_context_accessed_inside_filter_correlated() + { + base.Filtered_include_context_accessed_inside_filter_correlated(); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id], [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id] +FROM [LevelOne] AS [l] +OUTER APPLY ( + SELECT TOP(3) [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE ([l].[Id] = [l0].[OneToMany_Optional_Inverse2Id]) AND (( + SELECT COUNT(*) + FROM [LevelOne] AS [l1] + WHERE [l1].[Id] <> [l0].[Id]) > 1) + ORDER BY [l0].[Id] +) AS [t] +ORDER BY [l].[Id], [t].[Id]"); + } + + public override void Filtered_include_outer_parameter_used_inside_filter() + { + base.Filtered_include_outer_parameter_used_inside_filter(); + + AssertSql( + @"SELECT [l].[Id], [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id], [t].[Id0], [t].[Level2_Optional_Id], [t].[Level2_Required_Id], [t].[Name0], [t].[OneToMany_Optional_Inverse3Id], [t].[OneToMany_Optional_Self_Inverse3Id], [t].[OneToMany_Required_Inverse3Id], [t].[OneToMany_Required_Self_Inverse3Id], [t].[OneToOne_Optional_PK_Inverse3Id], [t].[OneToOne_Optional_Self3Id], [t1].[Id], [t1].[Date], [t1].[Level1_Optional_Id], [t1].[Level1_Required_Id], [t1].[Name], [t1].[OneToMany_Optional_Inverse2Id], [t1].[OneToMany_Optional_Self_Inverse2Id], [t1].[OneToMany_Required_Inverse2Id], [t1].[OneToMany_Required_Self_Inverse2Id], [t1].[OneToOne_Optional_PK_Inverse2Id], [t1].[OneToOne_Optional_Self2Id], [t1].[Id0], [t1].[Level2_Optional_Id], [t1].[Level2_Required_Id], [t1].[Name0], [t1].[OneToMany_Optional_Inverse3Id], [t1].[OneToMany_Optional_Self_Inverse3Id], [t1].[OneToMany_Required_Inverse3Id], [t1].[OneToMany_Required_Self_Inverse3Id], [t1].[OneToOne_Optional_PK_Inverse3Id], [t1].[OneToOne_Optional_Self3Id] +FROM [LevelOne] AS [l] +OUTER APPLY ( + SELECT [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id], [l1].[Id] AS [Id0], [l1].[Level2_Optional_Id], [l1].[Level2_Required_Id], [l1].[Name] AS [Name0], [l1].[OneToMany_Optional_Inverse3Id], [l1].[OneToMany_Optional_Self_Inverse3Id], [l1].[OneToMany_Required_Inverse3Id], [l1].[OneToMany_Required_Self_Inverse3Id], [l1].[OneToOne_Optional_PK_Inverse3Id], [l1].[OneToOne_Optional_Self3Id] + FROM [LevelTwo] AS [l0] + LEFT JOIN [LevelThree] AS [l1] ON [l0].[Id] = [l1].[OneToMany_Optional_Inverse3Id] +) AS [t] +OUTER APPLY ( + SELECT [l2].[Id], [l2].[Date], [l2].[Level1_Optional_Id], [l2].[Level1_Required_Id], [l2].[Name], [l2].[OneToMany_Optional_Inverse2Id], [l2].[OneToMany_Optional_Self_Inverse2Id], [l2].[OneToMany_Required_Inverse2Id], [l2].[OneToMany_Required_Self_Inverse2Id], [l2].[OneToOne_Optional_PK_Inverse2Id], [l2].[OneToOne_Optional_Self2Id], [t0].[Id] AS [Id0], [t0].[Level2_Optional_Id], [t0].[Level2_Required_Id], [t0].[Name] AS [Name0], [t0].[OneToMany_Optional_Inverse3Id], [t0].[OneToMany_Optional_Self_Inverse3Id], [t0].[OneToMany_Required_Inverse3Id], [t0].[OneToMany_Required_Self_Inverse3Id], [t0].[OneToOne_Optional_PK_Inverse3Id], [t0].[OneToOne_Optional_Self3Id] + FROM [LevelTwo] AS [l2] + LEFT JOIN ( + SELECT [l3].[Id], [l3].[Level2_Optional_Id], [l3].[Level2_Required_Id], [l3].[Name], [l3].[OneToMany_Optional_Inverse3Id], [l3].[OneToMany_Optional_Self_Inverse3Id], [l3].[OneToMany_Required_Inverse3Id], [l3].[OneToMany_Required_Self_Inverse3Id], [l3].[OneToOne_Optional_PK_Inverse3Id], [l3].[OneToOne_Optional_Self3Id] + FROM [LevelThree] AS [l3] + WHERE [l3].[Id] <> [l].[Id] + ) AS [t0] ON [l2].[Id] = [t0].[OneToMany_Optional_Inverse3Id] +) AS [t1] +ORDER BY [l].[Id], [t].[Id], [t].[Id0], [t1].[Id], [t1].[Id0]"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsQuerySqliteTest.cs index f82f06b6a3c..f52478eadec 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsQuerySqliteTest.cs @@ -34,5 +34,27 @@ public override Task Include_inside_subquery(bool async) // Sqlite does not support cross/outer apply public override Task SelectMany_with_outside_reference_to_joined_table_correctly_translated_to_apply(bool async) => null; public override Task Nested_SelectMany_correlated_with_join_table_correctly_translated_to_apply(bool async) => null; + public override void Filtered_include_Skip_without_OrderBy() { } + public override void Filtered_include_Take_without_OrderBy() { } + public override Task Filtered_include_after_different_filtered_include_same_level(bool async) => null; + public override Task Filtered_include_after_different_filtered_include_different_level(bool async) => null; + public override Task Filtered_include_after_reference_navigation(bool async) => null; + public override Task Filtered_include_and_non_filtered_include_on_same_navigation1(bool async) => null; + public override Task Filtered_include_and_non_filtered_include_on_same_navigation2(bool async) => null; + public override Task Filtered_include_basic_OrderBy_Take(bool async) => null; + public override Task Filtered_include_basic_OrderBy_Skip(bool async) => null; + public override Task Filtered_include_basic_OrderBy_Skip_Take(bool async) => null; + public override void Filtered_include_context_accessed_inside_filter() { } + public override void Filtered_include_context_accessed_inside_filter_correlated() { } + public override Task Filtered_include_on_ThenInclude(bool async) => null; + public override void Filtered_include_outer_parameter_used_inside_filter() { } + public override void Filtered_include_variable_used_inside_filter() { } + public override void Filtered_include_is_considered_loaded() { } + public override Task Filtered_include_and_non_filtered_include_followed_by_then_include_on_same_navigation(bool async) => null; + public override Task Filtered_include_complex_three_level_with_middle_having_filter2(bool async) => null; + public override Task Filtered_include_multiple_multi_level_includes_with_first_level_using_filter_include_on_one_of_the_chains_only(bool async) => null; + public override Task Filtered_include_same_filter_set_on_same_navigation_twice(bool async) => null; + public override Task Filtered_include_same_filter_set_on_same_navigation_twice_followed_by_ThenIncludes(bool async) => null; + public override Task Filtered_include_complex_three_level_with_middle_having_filter1(bool async) => null; } } diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsWeakQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsWeakQuerySqliteTest.cs index b33bff236a4..1877628df6f 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsWeakQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/ComplexNavigationsWeakQuerySqliteTest.cs @@ -29,5 +29,26 @@ public override Task Project_collection_navigation_nested_with_take(bool async) // Sqlite does not support cross/outer apply public override Task SelectMany_with_outside_reference_to_joined_table_correctly_translated_to_apply(bool async) => null; public override Task Nested_SelectMany_correlated_with_join_table_correctly_translated_to_apply(bool async) => null; + public override void Filtered_include_Skip_without_OrderBy() { } + public override void Filtered_include_Take_without_OrderBy() { } + public override Task Filtered_include_after_different_filtered_include_same_level(bool async) => null; + public override Task Filtered_include_after_different_filtered_include_different_level(bool async) => null; + public override Task Filtered_include_after_reference_navigation(bool async) => null; + public override Task Filtered_include_and_non_filtered_include_on_same_navigation1(bool async) => null; + public override Task Filtered_include_and_non_filtered_include_on_same_navigation2(bool async) => null; + public override Task Filtered_include_basic_OrderBy_Take(bool async) => null; + public override Task Filtered_include_basic_OrderBy_Skip(bool async) => null; + public override Task Filtered_include_basic_OrderBy_Skip_Take(bool async) => null; + public override void Filtered_include_context_accessed_inside_filter() { } + public override void Filtered_include_context_accessed_inside_filter_correlated() { } + public override Task Filtered_include_on_ThenInclude(bool async) => null; + public override void Filtered_include_variable_used_inside_filter() { } + public override void Filtered_include_is_considered_loaded() { } + public override Task Filtered_include_and_non_filtered_include_followed_by_then_include_on_same_navigation(bool async) => null; + public override Task Filtered_include_complex_three_level_with_middle_having_filter1(bool async) => null; + public override Task Filtered_include_multiple_multi_level_includes_with_first_level_using_filter_include_on_one_of_the_chains_only(bool async) => null; + public override Task Filtered_include_same_filter_set_on_same_navigation_twice(bool async) => null; + public override Task Filtered_include_same_filter_set_on_same_navigation_twice_followed_by_ThenIncludes(bool async) => null; + public override Task Filtered_include_complex_three_level_with_middle_having_filter2(bool async) => null; } }