diff --git a/src/EFCore.Relational/EFCore.Relational.baseline.json b/src/EFCore.Relational/EFCore.Relational.baseline.json index f41a789b4e2..86e54eddedc 100644 --- a/src/EFCore.Relational/EFCore.Relational.baseline.json +++ b/src/EFCore.Relational/EFCore.Relational.baseline.json @@ -14397,6 +14397,14 @@ } ] }, + { + "Type": "class Microsoft.EntityFrameworkCore.Query.RelationalNavigationExpansionExtensibilityHelper : Microsoft.EntityFrameworkCore.Query.NavigationExpansionExtensibilityHelper, Microsoft.EntityFrameworkCore.Query.INavigationExpansionExtensibilityHelper", + "Methods": [ + { + "Member": "RelationalNavigationExpansionExtensibilityHelper(Microsoft.EntityFrameworkCore.Query.NavigationExpansionExtensibilityHelperDependencies dependencies);" + } + ] + }, { "Type": "class Microsoft.EntityFrameworkCore.Metadata.Conventions.RelationalNavigationJsonPropertyNameAttributeConvention : Microsoft.EntityFrameworkCore.Metadata.Conventions.NavigationAttributeConventionBase, Microsoft.EntityFrameworkCore.Metadata.Conventions.INavigationAddedConvention, Microsoft.EntityFrameworkCore.Metadata.Conventions.IConvention", "Methods": [ diff --git a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs index 6c7154eb2a1..b50c32ac06d 100644 --- a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs +++ b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs @@ -186,6 +186,7 @@ public override EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(); TryAdd(); TryAdd(); + TryAdd(); TryAdd(); TryAdd(); TryAdd(); diff --git a/src/EFCore.Relational/Query/RelationalNavigationExpansionExtensibilityHelper.cs b/src/EFCore.Relational/Query/RelationalNavigationExpansionExtensibilityHelper.cs new file mode 100644 index 00000000000..12ea5bfd253 --- /dev/null +++ b/src/EFCore.Relational/Query/RelationalNavigationExpansionExtensibilityHelper.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Query; + +/// +/// Relational-specific service which helps with various aspects of navigation expansion extensibility. +/// +/// +/// +/// The service lifetime is . This means a single instance +/// is used by many instances. The implementation must be thread-safe. +/// This service cannot depend on services registered as . +/// +/// +/// See Implementation of database providers and extensions +/// and How EF Core queries work for more information and examples. +/// +/// +public class RelationalNavigationExpansionExtensibilityHelper : NavigationExpansionExtensibilityHelper, INavigationExpansionExtensibilityHelper +{ + /// + /// Creates a new instance of the class. + /// + /// The dependencies to use. + public RelationalNavigationExpansionExtensibilityHelper(NavigationExpansionExtensibilityHelperDependencies dependencies) + : base(dependencies) + { + } + + /// + bool INavigationExpansionExtensibilityHelper.SupportsNavigationExpansionJoins + => true; +} diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerNavigationExpansionExtensibilityHelper.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerNavigationExpansionExtensibilityHelper.cs index d8399f76891..084f4d32ace 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerNavigationExpansionExtensibilityHelper.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerNavigationExpansionExtensibilityHelper.cs @@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class SqlServerNavigationExpansionExtensibilityHelper : NavigationExpansionExtensibilityHelper +public class SqlServerNavigationExpansionExtensibilityHelper : RelationalNavigationExpansionExtensibilityHelper { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/EFCore.baseline.json b/src/EFCore/EFCore.baseline.json index 08c06423d20..84ad8b3832f 100644 --- a/src/EFCore/EFCore.baseline.json +++ b/src/EFCore/EFCore.baseline.json @@ -14697,6 +14697,11 @@ { "Member": "void ValidateQueryRootCreation(Microsoft.EntityFrameworkCore.Metadata.IEntityType entityType, Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression? source);" } + ], + "Properties": [ + { + "Member": "bool SupportsNavigationExpansionJoins { get; }" + } ] }, { diff --git a/src/EFCore/Query/INavigationExpansionExtensibilityHelper.cs b/src/EFCore/Query/INavigationExpansionExtensibilityHelper.cs index d3e6fe1d189..a10903c0e17 100644 --- a/src/EFCore/Query/INavigationExpansionExtensibilityHelper.cs +++ b/src/EFCore/Query/INavigationExpansionExtensibilityHelper.cs @@ -39,4 +39,13 @@ public interface INavigationExpansionExtensibilityHelper /// The first query root. /// The second query root. bool AreQueryRootsCompatible(EntityQueryRootExpression? first, EntityQueryRootExpression? second); + + /// + /// Whether navigation expansion may add joins to a source to translate aggregates over + /// reference navigations inside a GroupBy result selector into the grouped query, instead + /// of a correlated subquery per group. Providers that do not support joins must return + /// . + /// + bool SupportsNavigationExpansionJoins + => false; } diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.Expressions.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.Expressions.cs index 6daddc1d039..baef2c022da 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.Expressions.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.Expressions.cs @@ -302,11 +302,15 @@ public GroupByNavigationExpansionExpression( ParameterExpression groupingParameter, NavigationTreeNode currentTree, Expression pendingSelector, - string innerParameterName) + string innerParameterName, + NavigationExpansionExpression? parent = null, + LambdaExpression? originalKeySelector = null) { Source = source; CurrentParameter = groupingParameter; Type = source.Type; + Parent = parent; + OriginalKeySelector = originalKeySelector; GroupingEnumerable = new NavigationExpansionExpression( Call(QueryableMethods.AsQueryable.MakeGenericMethod(CurrentParameter.Type.GetGenericArguments()[1]), CurrentParameter), currentTree, @@ -320,11 +324,28 @@ public GroupByNavigationExpansionExpression( public NavigationExpansionExpression GroupingEnumerable { get; } + /// + /// The pre-GroupBy source, stashed so the aggregate lift can widen it with joins and rebuild + /// the GroupBy. Cleared as soon as an operator is composed over the grouping sequence. + /// + public NavigationExpansionExpression? Parent { get; private set; } + + /// + /// The unprocessed key selector, re-derivable over the widened parent because navigation + /// expansion is idempotent (re-expansion reuses existing joins). + /// + public LambdaExpression? OriginalKeySelector { get; } + public Type SourceElementType => CurrentParameter.Type; public void UpdateSource(Expression source) - => Source = source; + { + Source = source; + + // Operators over the grouping sequence cannot be carried through the aggregate lift. + Parent = null; + } public override ExpressionType NodeType => ExpressionType.Extension; diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs index 5a24034c60e..f9f33b74391 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs @@ -1161,6 +1161,7 @@ private Expression ProcessGroupBy( LambdaExpression? elementSelector, LambdaExpression? resultSelector) { + var originalKeySelector = keySelector; var keySelectorBody = ExpandNavigationsForSource(source, RemapLambdaExpression(source, keySelector)); // Need to generate lambda after processing element/result selector @@ -1183,7 +1184,10 @@ private Expression ProcessGroupBy( Expression.Quote(keySelector)); return new GroupByNavigationExpansionExpression( - innerSource, groupingParameter, source.CurrentTree, source.PendingSelector, innerParameterName); + innerSource, groupingParameter, source.CurrentTree, source.PendingSelector, innerParameterName, + // Lift state — an element selector reshapes the source, so no lift in that case. + elementSelector == null ? source : null, + elementSelector == null ? originalKeySelector : null); } var enumerableParameter = Expression.Parameter( @@ -1662,6 +1666,18 @@ private GroupByNavigationExpansionExpression ProcessOrderByThenBy( private NavigationExpansionExpression? ProcessSelect(GroupByNavigationExpansionExpression groupBySource, LambdaExpression selector) { + // Lift aggregates over reference navigations into the grouped query instead of a + // correlated subquery per group (#27933). + if (_extensibilityHelper.SupportsNavigationExpansionJoins + && groupBySource is { Parent: NavigationExpansionExpression parent, OriginalKeySelector: LambdaExpression originalKeySelector }) + { + var lifted = TryLiftAggregatesOverNavigations(parent, originalKeySelector, selector); + if (lifted != null) + { + return lifted; + } + } + var groupingElementReplacingExpressionVisitor = new GroupingElementReplacingExpressionVisitor(selector.Parameters[0], groupBySource); var selectorBody = groupingElementReplacingExpressionVisitor.Visit(selector.Body); @@ -1687,6 +1703,276 @@ private GroupByNavigationExpansionExpression ProcessOrderByThenBy( return new NavigationExpansionExpression(newSource, navigationTree, navigationTree, parameterName); } + /// + /// Rewrites GroupBy(k).Select(g => ...aggregates...) so reference navigations used in aggregate + /// selectors expand as joins on the pre-GroupBy source and the aggregates translate into the + /// grouped query (#27933). Returns null when the shape does not qualify. + /// + private NavigationExpansionExpression? TryLiftAggregatesOverNavigations( + NavigationExpansionExpression parent, + LambdaExpression originalKeySelector, + LambdaExpression selector) + { + var scanner = new GroupingAggregateScanner(selector.Parameters[0]); + scanner.Visit(selector.Body); + if (scanner.HasUnsupportedUsage + || scanner.Aggregates.Count == 0) + { + return null; + } + + // Flat-aggregate queries keep the existing translation unchanged. + var traversesNavigation = false; + foreach (var aggregate in scanner.Aggregates) + { + if (aggregate.Selector != null + && ContainsReferenceNavigationAccess(RemapLambdaExpression(parent, aggregate.Selector))) + { + traversesNavigation = true; + break; + } + } + + if (!traversesNavigation) + { + return null; + } + + // Expand every body on the parent first (this applies the joins and may widen the parent's + // element shape), then generate all lambdas over the final parameter. Re-expanding the key + // selector is idempotent with respect to joins already applied by ProcessGroupBy. + var keyBody = ExpandNavigationsForSource(parent, RemapLambdaExpression(parent, originalKeySelector)); + var aggregateBodies = new Expression?[scanner.Aggregates.Count]; + for (var i = 0; i < scanner.Aggregates.Count; i++) + { + var aggregateSelector = scanner.Aggregates[i].Selector; + aggregateBodies[i] = aggregateSelector == null + ? null + : ExpandNavigationsForSource(parent, RemapLambdaExpression(parent, aggregateSelector)); + } + + var keySelector = GenerateLambda(keyBody, parent.CurrentParameter); + var elementType = parent.CurrentParameter.Type; + var groupByCall = Expression.Call( + QueryableMethods.GroupByWithKeySelector.MakeGenericMethod(elementType, keySelector.ReturnType), + parent.Source, + Expression.Quote(keySelector)); + + var groupingParameter = Expression.Parameter(groupByCall.Type.GetSequenceType(), GetParameterName("g")); + var originalElementType = selector.Parameters[0].Type.GetGenericArguments()[1]; + + var aggregateReplacements = new Dictionary(ReferenceEqualityComparer.Instance); + for (var i = 0; i < scanner.Aggregates.Count; i++) + { + var aggregate = scanner.Aggregates[i]; + var newSelector = aggregateBodies[i] == null + ? null + : GenerateLambda(aggregateBodies[i]!, parent.CurrentParameter); + aggregateReplacements[aggregate.Call] = RebuildLiftedAggregate( + aggregate, groupingParameter, originalElementType, elementType, newSelector); + } + + var resultBody = new LiftedResultSelectorRebinder(selector.Parameters[0], groupingParameter, aggregateReplacements) + .Visit(selector.Body); + var result = Expression.Call( + QueryableMethods.Select.MakeGenericMethod(groupingParameter.Type, resultBody.Type), + groupByCall, + Expression.Quote(Expression.Lambda(resultBody, groupingParameter))); + + var navigationTree = new NavigationTreeExpression(Expression.Default(result.Type.GetSequenceType())); + + return new NavigationExpansionExpression(result, navigationTree, navigationTree, GetParameterName("e")); + } + + /// + /// Detects member chains rooted at an entity navigation tree node whose first member is a + /// non-collection navigation — accesses that would otherwise translate as a correlated subquery. + /// + private static bool ContainsReferenceNavigationAccess(Expression body) + { + var detector = new ReferenceNavigationAccessDetector(); + detector.Visit(body); + return detector.Found; + } + + private sealed class ReferenceNavigationAccessDetector : ExpressionVisitor + { + public bool Found { get; private set; } + + protected override Expression VisitMember(MemberExpression memberExpression) + { + var chain = new List(); + var current = (Expression?)memberExpression; + while (current is MemberExpression member) + { + chain.Insert(0, member.Member); + current = member.Expression; + } + + if (current is NavigationTreeExpression { Value: EntityReference entityReference } + && chain.Count > 1 + && entityReference.EntityType.FindNavigation(chain[0].Name) is INavigation { IsCollection: false }) + { + Found = true; + return memberExpression; + } + + return base.VisitMember(memberExpression); + } + } + + private sealed record GroupingAggregateCall(MethodCallExpression Call, LambdaExpression? Selector, bool SourceAsQueryable); + + private static MethodCallExpression RebuildLiftedAggregate( + GroupingAggregateCall aggregate, + ParameterExpression groupingParameter, + Type originalElementType, + Type newElementType, + LambdaExpression? newSelector) + { + var method = aggregate.Call.Method; + var newMethod = method.GetGenericMethodDefinition().MakeGenericMethod( + method.GetGenericArguments().Select(t => t == originalElementType ? newElementType : t).ToArray()); + + Expression newSource = aggregate.SourceAsQueryable + ? Expression.Call(QueryableMethods.AsQueryable.MakeGenericMethod(newElementType), groupingParameter) + : groupingParameter; + + return newSelector == null + ? Expression.Call(newMethod, newSource) + : Expression.Call( + newMethod, + newSource, + method.DeclaringType == typeof(Queryable) ? Expression.Quote(newSelector) : newSelector); + } + + /// + /// Verifies the grouping parameter is only used as g.Key or as the source of a whitelisted + /// aggregate, collecting the aggregates. + /// + private sealed class GroupingAggregateScanner(ParameterExpression groupingParameter) : ExpressionVisitor + { + private static readonly string[] SelectorAggregateMethodNames = + [nameof(Enumerable.Sum), nameof(Enumerable.Min), nameof(Enumerable.Max), nameof(Enumerable.Average)]; + + private static readonly string[] PredicateAggregateMethodNames = + [nameof(Enumerable.Count), nameof(Enumerable.LongCount)]; + + public List Aggregates { get; } = []; + public bool HasUnsupportedUsage { get; private set; } + + [return: NotNullIfNotNull(nameof(expression))] + public override Expression? Visit(Expression? expression) + { + if (expression == groupingParameter) + { + HasUnsupportedUsage = true; + } + + return base.Visit(expression); + } + + protected override Expression VisitMember(MemberExpression memberExpression) + => memberExpression.Expression == groupingParameter + && memberExpression.Member.Name == nameof(IGrouping.Key) + ? memberExpression + : base.VisitMember(memberExpression); + + protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) + { + if (TryMatchAggregate(methodCallExpression, out var aggregate)) + { + Aggregates.Add(aggregate); + if (aggregate.Selector != null) + { + base.Visit(aggregate.Selector.Body); + } + + return methodCallExpression; + } + + return base.VisitMethodCall(methodCallExpression); + } + + private bool TryMatchAggregate( + MethodCallExpression methodCallExpression, + [NotNullWhen(true)] out GroupingAggregateCall? aggregate) + { + aggregate = null; + var method = methodCallExpression.Method; + if (!method.IsStatic + || (method.DeclaringType != typeof(Enumerable) && method.DeclaringType != typeof(Queryable)) + || !method.IsGenericMethod + || methodCallExpression.Arguments.Count is < 1 or > 2) + { + return false; + } + + var sourceAsQueryable = false; + var sourceArgument = methodCallExpression.Arguments[0]; + if (sourceArgument is MethodCallExpression asQueryableCall + && asQueryableCall.Method.IsGenericMethod + && asQueryableCall.Method.GetGenericMethodDefinition() == QueryableMethods.AsQueryable + && asQueryableCall.Arguments[0] == groupingParameter) + { + sourceAsQueryable = true; + } + else if (sourceArgument != groupingParameter) + { + return false; + } + + var isSelectorAggregate = SelectorAggregateMethodNames.Contains(method.Name); + var isPredicateAggregate = PredicateAggregateMethodNames.Contains(method.Name); + if (!isSelectorAggregate && !isPredicateAggregate) + { + return false; + } + + LambdaExpression? selector = null; + if (methodCallExpression.Arguments.Count == 2) + { + selector = methodCallExpression.Arguments[1] switch + { + UnaryExpression { NodeType: ExpressionType.Quote, Operand: LambdaExpression quoted } => quoted, + LambdaExpression lambda => lambda, + _ => null + }; + if (selector == null) + { + return false; + } + } + else if (isSelectorAggregate) + { + return false; + } + + aggregate = new GroupingAggregateCall(methodCallExpression, selector, sourceAsQueryable); + return true; + } + } + + private sealed class LiftedResultSelectorRebinder( + ParameterExpression originalParameter, + ParameterExpression newParameter, + Dictionary aggregateReplacements) + : ExpressionVisitor + { + [return: NotNullIfNotNull(nameof(expression))] + public override Expression? Visit(Expression? expression) + => expression != null && aggregateReplacements.TryGetValue(expression, out var replacement) + ? replacement + : base.Visit(expression); + + protected override Expression VisitMember(MemberExpression memberExpression) + => memberExpression.Expression == originalParameter + && memberExpression.Member.Name == nameof(IGrouping.Key) + ? Expression.MakeMemberAccess( + newParameter, newParameter.Type.GetProperty(nameof(IGrouping.Key))!) + : base.VisitMember(memberExpression); + } + private GroupByNavigationExpansionExpression ProcessSkipTake( GroupByNavigationExpansionExpression groupBySource, MethodInfo genericMethod, diff --git a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs index b7200ec3976..eca7630c5e8 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs @@ -354,6 +354,60 @@ public virtual Task GroupBy_with_aggregate_through_navigation_property(bool asyn ss => ss.Set().GroupBy(c => c.EmployeeID).Select(g => new { max = g.Max(i => i.Customer.Region) }), elementSorter: e => e.max); + [Theory, MemberData(nameof(IsAsyncData))] + public virtual Task GroupBy_multiple_aggregates_sharing_same_navigation(bool async) + => AssertQuery( + async, + ss => ss.Set() + .GroupBy(o => o.EmployeeID) + .Select( + g => new + { + g.Key, + Londons = g.Sum(o => o.Customer.City == "London" ? 1 : 0), + Berlins = g.Sum(o => o.Customer.City == "Berlin" ? 1 : 0), + Total = g.Sum(o => o.OrderID), + Count = g.Count() + }), + elementSorter: e => e.Key); + + [Theory, MemberData(nameof(IsAsyncData))] + public virtual Task GroupBy_aggregate_through_two_level_navigation(bool async) + => AssertQuery( + async, + ss => ss.Set() + .GroupBy(od => od.ProductID) + .Select(g => new { g.Key, Londons = g.Sum(od => od.Order.Customer.City == "London" ? 1 : 0) }), + elementSorter: e => e.Key); + + [Theory, MemberData(nameof(IsAsyncData))] + public virtual Task GroupBy_Count_with_predicate_through_navigation_property(bool async) + => AssertQuery( + async, + ss => ss.Set() + .GroupBy(o => o.EmployeeID) + .Select(g => new { g.Key, Londons = g.Count(o => o.Customer.City == "London") }), + elementSorter: e => e.Key); + + [Theory, MemberData(nameof(IsAsyncData))] + public virtual Task GroupBy_key_and_aggregate_through_same_navigation(bool async) + => AssertQuery( + async, + ss => ss.Set() + .GroupBy(o => o.Customer.City) + .Select(g => new { g.Key, Londons = g.Count(o => o.Customer.City == "London") }), + elementSorter: e => e.Key); + + [Theory, MemberData(nameof(IsAsyncData))] + public virtual Task GroupBy_aggregate_through_navigation_in_intermediate_projection(bool async) + => AssertQuery( + async, + ss => ss.Set() + .Select(o => new { o.EmployeeID, City = o.Customer.City }) + .GroupBy(x => x.EmployeeID) + .Select(g => new { g.Key, Londons = g.Sum(x => x.City == "London" ? 1 : 0) }), + elementSorter: e => e.Key); + [Theory, MemberData(nameof(IsAsyncData))] public virtual Task GroupBy_with_aggregate_containing_complex_where(bool async) => AssertQuery( diff --git a/test/EFCore.Specification.Tests/Query/NorthwindQueryFiltersQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindQueryFiltersQueryTestBase.cs index ad2bdb7262c..9495e131e33 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindQueryFiltersQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindQueryFiltersQueryTestBase.cs @@ -138,6 +138,34 @@ public virtual Task Include_query_opt_out(bool async) elementAsserter: (e, a) => AssertInclude(e, a, new ExpectedInclude(x => x.Orders))); } + [Theory, MemberData(nameof(IsAsyncData))] + public virtual Task GroupBy_aggregate_through_filtered_navigation(bool async) + => AssertFilteredQuery( + async, + ss => ss.Set() + .GroupBy(o => o.EmployeeID) + .Select(g => new { g.Key, Londons = g.Count(o => o.Customer.City == "London") }), + elementSorter: e => e.Key.GetValueOrDefault()); + + [Theory, MemberData(nameof(IsAsyncData))] + public virtual Task GroupBy_aggregate_through_filtered_navigation_with_total(bool async) + => AssertFilteredQuery( + async, + ss => ss.Set() + .GroupBy(o => o.EmployeeID) + .Select(g => new { g.Key, Total = g.Count(), Londons = g.Count(o => o.Customer.City == "London") }), + elementSorter: e => e.Key.GetValueOrDefault()); + + [Theory, MemberData(nameof(IsAsyncData))] + public virtual Task GroupBy_aggregate_through_filtered_navigation_ignore_query_filters(bool async) + => AssertQuery( + async, + ss => ss.Set() + .IgnoreQueryFilters() + .GroupBy(o => o.EmployeeID) + .Select(g => new { g.Key, Londons = g.Count(o => o.Customer.City == "London") }), + elementSorter: e => e.Key.GetValueOrDefault()); + [Theory, MemberData(nameof(IsAsyncData))] public virtual Task Included_many_to_one_query2(bool async) { diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs index 971b4bcc411..c73139740d5 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/AdHocMiscellaneousQuerySqlServerTest.cs @@ -2399,22 +2399,11 @@ public override async Task GroupBy_Aggregate_over_navigations_repeated(bool asyn AssertSql( """ -SELECT ( - SELECT MIN([o].[HourlyRate]) - FROM [TimeSheets] AS [t0] - LEFT JOIN [Order] AS [o] ON [t0].[OrderId] = [o].[Id] - WHERE [t0].[OrderId] IS NOT NULL AND [t].[OrderId] = [t0].[OrderId]) AS [HourlyRate], ( - SELECT MIN([c].[Id]) - FROM [TimeSheets] AS [t1] - INNER JOIN [Project] AS [p] ON [t1].[ProjectId] = [p].[Id] - INNER JOIN [Customers] AS [c] ON [p].[CustomerId] = [c].[Id] - WHERE [t1].[OrderId] IS NOT NULL AND [t].[OrderId] = [t1].[OrderId]) AS [CustomerId], ( - SELECT MIN([c0].[Name]) - FROM [TimeSheets] AS [t2] - INNER JOIN [Project] AS [p0] ON [t2].[ProjectId] = [p0].[Id] - INNER JOIN [Customers] AS [c0] ON [p0].[CustomerId] = [c0].[Id] - WHERE [t2].[OrderId] IS NOT NULL AND [t].[OrderId] = [t2].[OrderId]) AS [CustomerName] +SELECT MIN([o].[HourlyRate]) AS [HourlyRate], MIN([c].[Id]) AS [CustomerId], MIN([c].[Name]) AS [CustomerName] FROM [TimeSheets] AS [t] +LEFT JOIN [Order] AS [o] ON [t].[OrderId] = [o].[Id] +INNER JOIN [Project] AS [p] ON [t].[ProjectId] = [p].[Id] +INNER JOIN [Customers] AS [c] ON [p].[CustomerId] = [c].[Id] WHERE [t].[OrderId] IS NOT NULL GROUP BY [t].[OrderId] """); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs index 264317760ff..a729ed7baa3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsSharedTypeQuerySqlServerTest.cs @@ -876,37 +876,14 @@ public override async Task Composite_key_join_on_groupby_aggregate_projecting_on AssertSql( """ -SELECT [s1].[Key] +SELECT [s0].[Key] FROM [Level1] AS [l] INNER JOIN ( - SELECT [s].[Key], ( - SELECT ISNULL(SUM(CASE - WHEN [l7].[OneToOne_Required_PK_Date] IS NOT NULL AND [l7].[Level1_Required_Id] IS NOT NULL AND [l7].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l7].[Id] - END), 0) - FROM ( - SELECT [l3].[Id], CASE - WHEN [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l4].[Id] - END % 3 AS [Key] - FROM [Level1] AS [l3] - LEFT JOIN ( - SELECT [l5].[Id], [l5].[OneToOne_Required_PK_Date], [l5].[Level1_Required_Id], [l5].[OneToMany_Required_Inverse2Id] - FROM [Level1] AS [l5] - WHERE [l5].[OneToOne_Required_PK_Date] IS NOT NULL AND [l5].[Level1_Required_Id] IS NOT NULL AND [l5].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [l4] ON [l3].[Id] = CASE - WHEN [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l4].[Id] - END - WHERE [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [s0] - LEFT JOIN ( - SELECT [l6].[Id], [l6].[OneToOne_Required_PK_Date], [l6].[Level1_Required_Id], [l6].[OneToMany_Required_Inverse2Id] - FROM [Level1] AS [l6] - WHERE [l6].[OneToOne_Required_PK_Date] IS NOT NULL AND [l6].[Level1_Required_Id] IS NOT NULL AND [l6].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [l7] ON [s0].[Id] = CASE - WHEN [l7].[OneToOne_Required_PK_Date] IS NOT NULL AND [l7].[Level1_Required_Id] IS NOT NULL AND [l7].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l7].[Id] - END - WHERE [s].[Key] = [s0].[Key] OR ([s].[Key] IS NULL AND [s0].[Key] IS NULL)) AS [Sum] + SELECT [s].[Key], ISNULL(SUM(CASE + WHEN [s].[OneToOne_Required_PK_Date] IS NOT NULL AND [s].[Level1_Required_Id] IS NOT NULL AND [s].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [s].[Id0] + END), 0) AS [Sum] FROM ( - SELECT CASE + SELECT [l2].[Id] AS [Id0], [l2].[OneToOne_Required_PK_Date], [l2].[Level1_Required_Id], [l2].[OneToMany_Required_Inverse2Id], CASE WHEN [l2].[OneToOne_Required_PK_Date] IS NOT NULL AND [l2].[Level1_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l2].[Id] END % 3 AS [Key] FROM [Level1] AS [l0] @@ -920,7 +897,7 @@ WHEN [l2].[OneToOne_Required_PK_Date] IS NOT NULL AND [l2].[Level1_Required_Id] WHERE [l2].[OneToOne_Required_PK_Date] IS NOT NULL AND [l2].[Level1_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [s] GROUP BY [s].[Key] -) AS [s1] ON [l].[Id] = [s1].[Key] AND [s1].[Sum] > 10 +) AS [s0] ON [l].[Id] = [s0].[Key] AND [s0].[Sum] > 10 """); } @@ -930,37 +907,14 @@ public override async Task Composite_key_join_on_groupby_aggregate_projecting_on AssertSql( """ -SELECT [s1].[Key] +SELECT [s0].[Key] FROM [Level1] AS [l] INNER JOIN ( - SELECT [s].[Key], ( - SELECT ISNULL(SUM(CASE - WHEN [l7].[OneToOne_Required_PK_Date] IS NOT NULL AND [l7].[Level1_Required_Id] IS NOT NULL AND [l7].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l7].[Id] - END), 0) - FROM ( - SELECT [l3].[Id], CASE - WHEN [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l4].[Id] - END % 3 AS [Key] - FROM [Level1] AS [l3] - LEFT JOIN ( - SELECT [l5].[Id], [l5].[OneToOne_Required_PK_Date], [l5].[Level1_Required_Id], [l5].[OneToMany_Required_Inverse2Id] - FROM [Level1] AS [l5] - WHERE [l5].[OneToOne_Required_PK_Date] IS NOT NULL AND [l5].[Level1_Required_Id] IS NOT NULL AND [l5].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [l4] ON [l3].[Id] = CASE - WHEN [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l4].[Id] - END - WHERE [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [s0] - LEFT JOIN ( - SELECT [l6].[Id], [l6].[OneToOne_Required_PK_Date], [l6].[Level1_Required_Id], [l6].[OneToMany_Required_Inverse2Id] - FROM [Level1] AS [l6] - WHERE [l6].[OneToOne_Required_PK_Date] IS NOT NULL AND [l6].[Level1_Required_Id] IS NOT NULL AND [l6].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [l7] ON [s0].[Id] = CASE - WHEN [l7].[OneToOne_Required_PK_Date] IS NOT NULL AND [l7].[Level1_Required_Id] IS NOT NULL AND [l7].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l7].[Id] - END - WHERE [s].[Key] = [s0].[Key] OR ([s].[Key] IS NULL AND [s0].[Key] IS NULL)) AS [Sum] + SELECT [s].[Key], ISNULL(SUM(CASE + WHEN [s].[OneToOne_Required_PK_Date] IS NOT NULL AND [s].[Level1_Required_Id] IS NOT NULL AND [s].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [s].[Id0] + END), 0) AS [Sum] FROM ( - SELECT CASE + SELECT [l2].[Id] AS [Id0], [l2].[OneToOne_Required_PK_Date], [l2].[Level1_Required_Id], [l2].[OneToMany_Required_Inverse2Id], CASE WHEN [l2].[OneToOne_Required_PK_Date] IS NOT NULL AND [l2].[Level1_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l2].[Id] END % 3 AS [Key] FROM [Level1] AS [l0] @@ -974,8 +928,8 @@ WHEN [l2].[OneToOne_Required_PK_Date] IS NOT NULL AND [l2].[Level1_Required_Id] WHERE [l2].[OneToOne_Required_PK_Date] IS NOT NULL AND [l2].[Level1_Required_Id] IS NOT NULL AND [l2].[OneToMany_Required_Inverse2Id] IS NOT NULL ) AS [s] GROUP BY [s].[Key] -) AS [s1] ON [l].[Id] = [s1].[Key] AND CASE - WHEN [s1].[Sum] <= 10 THEN CAST(0 AS bit) +) AS [s0] ON [l].[Id] = [s0].[Key] AND CASE + WHEN [s0].[Sum] <= 10 THEN CAST(0 AS bit) ELSE CAST(1 AS bit) END = CAST(1 AS bit) """); @@ -1028,27 +982,9 @@ public override async Task GroupBy_aggregate_where_required_relationship_2(bool AssertSql( """ -SELECT [l2].[Id] AS [Key], ( - SELECT MAX(CASE - WHEN [l8].[OneToOne_Required_PK_Date] IS NOT NULL AND [l8].[Level1_Required_Id] IS NOT NULL AND [l8].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l8].[Id] - END) - FROM [Level1] AS [l3] - LEFT JOIN ( - SELECT [l5].[Id], [l5].[OneToOne_Required_PK_Date], [l5].[Level1_Required_Id], [l5].[OneToMany_Required_Inverse2Id] - FROM [Level1] AS [l5] - WHERE [l5].[OneToOne_Required_PK_Date] IS NOT NULL AND [l5].[Level1_Required_Id] IS NOT NULL AND [l5].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [l4] ON [l3].[Id] = CASE - WHEN [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l4].[Id] - END - LEFT JOIN [Level1] AS [l6] ON [l4].[OneToMany_Required_Inverse2Id] = [l6].[Id] - LEFT JOIN ( - SELECT [l7].[Id], [l7].[OneToOne_Required_PK_Date], [l7].[Level1_Required_Id], [l7].[OneToMany_Required_Inverse2Id] - FROM [Level1] AS [l7] - WHERE [l7].[OneToOne_Required_PK_Date] IS NOT NULL AND [l7].[Level1_Required_Id] IS NOT NULL AND [l7].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [l8] ON [l3].[Id] = CASE - WHEN [l8].[OneToOne_Required_PK_Date] IS NOT NULL AND [l8].[Level1_Required_Id] IS NOT NULL AND [l8].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l8].[Id] - END - WHERE [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l2].[Id] = [l6].[Id] OR ([l2].[Id] IS NULL AND [l6].[Id] IS NULL))) AS [Max] +SELECT [l2].[Id] AS [Key], MAX(CASE + WHEN [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l1].[Id] +END) AS [Max] FROM [Level1] AS [l] LEFT JOIN ( SELECT [l0].[Id], [l0].[OneToOne_Required_PK_Date], [l0].[Level1_Required_Id], [l0].[OneToMany_Required_Inverse2Id] @@ -1060,47 +996,11 @@ WHEN [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] LEFT JOIN [Level1] AS [l2] ON [l1].[OneToMany_Required_Inverse2Id] = [l2].[Id] WHERE [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL GROUP BY [l2].[Id] -HAVING ( - SELECT MAX(CASE - WHEN [l8].[OneToOne_Required_PK_Date] IS NOT NULL AND [l8].[Level1_Required_Id] IS NOT NULL AND [l8].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l8].[Id] - END) - FROM [Level1] AS [l3] - LEFT JOIN ( - SELECT [l5].[Id], [l5].[OneToOne_Required_PK_Date], [l5].[Level1_Required_Id], [l5].[OneToMany_Required_Inverse2Id] - FROM [Level1] AS [l5] - WHERE [l5].[OneToOne_Required_PK_Date] IS NOT NULL AND [l5].[Level1_Required_Id] IS NOT NULL AND [l5].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [l4] ON [l3].[Id] = CASE - WHEN [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l4].[Id] - END - LEFT JOIN [Level1] AS [l6] ON [l4].[OneToMany_Required_Inverse2Id] = [l6].[Id] - LEFT JOIN ( - SELECT [l7].[Id], [l7].[OneToOne_Required_PK_Date], [l7].[Level1_Required_Id], [l7].[OneToMany_Required_Inverse2Id] - FROM [Level1] AS [l7] - WHERE [l7].[OneToOne_Required_PK_Date] IS NOT NULL AND [l7].[Level1_Required_Id] IS NOT NULL AND [l7].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [l8] ON [l3].[Id] = CASE - WHEN [l8].[OneToOne_Required_PK_Date] IS NOT NULL AND [l8].[Level1_Required_Id] IS NOT NULL AND [l8].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l8].[Id] - END - WHERE [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l2].[Id] = [l6].[Id] OR ([l2].[Id] IS NULL AND [l6].[Id] IS NULL))) < 2 OR ( - SELECT MAX(CASE - WHEN [l8].[OneToOne_Required_PK_Date] IS NOT NULL AND [l8].[Level1_Required_Id] IS NOT NULL AND [l8].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l8].[Id] - END) - FROM [Level1] AS [l3] - LEFT JOIN ( - SELECT [l5].[Id], [l5].[OneToOne_Required_PK_Date], [l5].[Level1_Required_Id], [l5].[OneToMany_Required_Inverse2Id] - FROM [Level1] AS [l5] - WHERE [l5].[OneToOne_Required_PK_Date] IS NOT NULL AND [l5].[Level1_Required_Id] IS NOT NULL AND [l5].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [l4] ON [l3].[Id] = CASE - WHEN [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l4].[Id] - END - LEFT JOIN [Level1] AS [l6] ON [l4].[OneToMany_Required_Inverse2Id] = [l6].[Id] - LEFT JOIN ( - SELECT [l7].[Id], [l7].[OneToOne_Required_PK_Date], [l7].[Level1_Required_Id], [l7].[OneToMany_Required_Inverse2Id] - FROM [Level1] AS [l7] - WHERE [l7].[OneToOne_Required_PK_Date] IS NOT NULL AND [l7].[Level1_Required_Id] IS NOT NULL AND [l7].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [l8] ON [l3].[Id] = CASE - WHEN [l8].[OneToOne_Required_PK_Date] IS NOT NULL AND [l8].[Level1_Required_Id] IS NOT NULL AND [l8].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l8].[Id] - END - WHERE [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l2].[Id] = [l6].[Id] OR ([l2].[Id] IS NULL AND [l6].[Id] IS NULL))) > 2 +HAVING MAX(CASE + WHEN [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l1].[Id] +END) < 2 OR MAX(CASE + WHEN [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l1].[Id] +END) > 2 """); } @@ -1179,27 +1079,9 @@ public override async Task GroupBy_aggregate_where_required_relationship(bool as AssertSql( """ -SELECT [l2].[Id] AS [Key], ( - SELECT MAX(CASE - WHEN [l8].[OneToOne_Required_PK_Date] IS NOT NULL AND [l8].[Level1_Required_Id] IS NOT NULL AND [l8].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l8].[Id] - END) - FROM [Level1] AS [l3] - LEFT JOIN ( - SELECT [l5].[Id], [l5].[OneToOne_Required_PK_Date], [l5].[Level1_Required_Id], [l5].[OneToMany_Required_Inverse2Id] - FROM [Level1] AS [l5] - WHERE [l5].[OneToOne_Required_PK_Date] IS NOT NULL AND [l5].[Level1_Required_Id] IS NOT NULL AND [l5].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [l4] ON [l3].[Id] = CASE - WHEN [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l4].[Id] - END - LEFT JOIN [Level1] AS [l6] ON [l4].[OneToMany_Required_Inverse2Id] = [l6].[Id] - LEFT JOIN ( - SELECT [l7].[Id], [l7].[OneToOne_Required_PK_Date], [l7].[Level1_Required_Id], [l7].[OneToMany_Required_Inverse2Id] - FROM [Level1] AS [l7] - WHERE [l7].[OneToOne_Required_PK_Date] IS NOT NULL AND [l7].[Level1_Required_Id] IS NOT NULL AND [l7].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [l8] ON [l3].[Id] = CASE - WHEN [l8].[OneToOne_Required_PK_Date] IS NOT NULL AND [l8].[Level1_Required_Id] IS NOT NULL AND [l8].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l8].[Id] - END - WHERE [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l2].[Id] = [l6].[Id] OR ([l2].[Id] IS NULL AND [l6].[Id] IS NULL))) AS [Max] +SELECT [l2].[Id] AS [Key], MAX(CASE + WHEN [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l1].[Id] +END) AS [Max] FROM [Level1] AS [l] LEFT JOIN ( SELECT [l0].[Id], [l0].[OneToOne_Required_PK_Date], [l0].[Level1_Required_Id], [l0].[OneToMany_Required_Inverse2Id] @@ -1211,47 +1093,11 @@ WHEN [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] LEFT JOIN [Level1] AS [l2] ON [l1].[OneToMany_Required_Inverse2Id] = [l2].[Id] WHERE [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL GROUP BY [l2].[Id] -HAVING ( - SELECT MAX(CASE - WHEN [l8].[OneToOne_Required_PK_Date] IS NOT NULL AND [l8].[Level1_Required_Id] IS NOT NULL AND [l8].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l8].[Id] - END) - FROM [Level1] AS [l3] - LEFT JOIN ( - SELECT [l5].[Id], [l5].[OneToOne_Required_PK_Date], [l5].[Level1_Required_Id], [l5].[OneToMany_Required_Inverse2Id] - FROM [Level1] AS [l5] - WHERE [l5].[OneToOne_Required_PK_Date] IS NOT NULL AND [l5].[Level1_Required_Id] IS NOT NULL AND [l5].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [l4] ON [l3].[Id] = CASE - WHEN [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l4].[Id] - END - LEFT JOIN [Level1] AS [l6] ON [l4].[OneToMany_Required_Inverse2Id] = [l6].[Id] - LEFT JOIN ( - SELECT [l7].[Id], [l7].[OneToOne_Required_PK_Date], [l7].[Level1_Required_Id], [l7].[OneToMany_Required_Inverse2Id] - FROM [Level1] AS [l7] - WHERE [l7].[OneToOne_Required_PK_Date] IS NOT NULL AND [l7].[Level1_Required_Id] IS NOT NULL AND [l7].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [l8] ON [l3].[Id] = CASE - WHEN [l8].[OneToOne_Required_PK_Date] IS NOT NULL AND [l8].[Level1_Required_Id] IS NOT NULL AND [l8].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l8].[Id] - END - WHERE [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l2].[Id] = [l6].[Id] OR ([l2].[Id] IS NULL AND [l6].[Id] IS NULL))) <> 2 OR ( - SELECT MAX(CASE - WHEN [l8].[OneToOne_Required_PK_Date] IS NOT NULL AND [l8].[Level1_Required_Id] IS NOT NULL AND [l8].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l8].[Id] - END) - FROM [Level1] AS [l3] - LEFT JOIN ( - SELECT [l5].[Id], [l5].[OneToOne_Required_PK_Date], [l5].[Level1_Required_Id], [l5].[OneToMany_Required_Inverse2Id] - FROM [Level1] AS [l5] - WHERE [l5].[OneToOne_Required_PK_Date] IS NOT NULL AND [l5].[Level1_Required_Id] IS NOT NULL AND [l5].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [l4] ON [l3].[Id] = CASE - WHEN [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l4].[Id] - END - LEFT JOIN [Level1] AS [l6] ON [l4].[OneToMany_Required_Inverse2Id] = [l6].[Id] - LEFT JOIN ( - SELECT [l7].[Id], [l7].[OneToOne_Required_PK_Date], [l7].[Level1_Required_Id], [l7].[OneToMany_Required_Inverse2Id] - FROM [Level1] AS [l7] - WHERE [l7].[OneToOne_Required_PK_Date] IS NOT NULL AND [l7].[Level1_Required_Id] IS NOT NULL AND [l7].[OneToMany_Required_Inverse2Id] IS NOT NULL - ) AS [l8] ON [l3].[Id] = CASE - WHEN [l8].[OneToOne_Required_PK_Date] IS NOT NULL AND [l8].[Level1_Required_Id] IS NOT NULL AND [l8].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l8].[Id] - END - WHERE [l4].[OneToOne_Required_PK_Date] IS NOT NULL AND [l4].[Level1_Required_Id] IS NOT NULL AND [l4].[OneToMany_Required_Inverse2Id] IS NOT NULL AND ([l2].[Id] = [l6].[Id] OR ([l2].[Id] IS NULL AND [l6].[Id] IS NULL))) IS NULL +HAVING MAX(CASE + WHEN [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l1].[Id] +END) <> 2 OR MAX(CASE + WHEN [l1].[OneToOne_Required_PK_Date] IS NOT NULL AND [l1].[Level1_Required_Id] IS NOT NULL AND [l1].[OneToMany_Required_Inverse2Id] IS NOT NULL THEN [l1].[Id] +END) IS NULL """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Ef6GroupBySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Ef6GroupBySqlServerTest.cs index 76eb64e182b..a101841fff4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Ef6GroupBySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Ef6GroupBySqlServerTest.cs @@ -764,13 +764,7 @@ public override async Task Whats_new_2021_sample_7(bool async) """ @size='11' -SELECT [p0].[LastName], [f].[Size], ( - SELECT MIN([f1].[Size]) - FROM [Person] AS [p1] - LEFT JOIN [Feet] AS [f0] ON [p1].[Id] = [f0].[Id] - LEFT JOIN [Person] AS [p2] ON [f0].[Id] = [p2].[Id] - LEFT JOIN [Feet] AS [f1] ON [p1].[Id] = [f1].[Id] - WHERE [f0].[Size] = @size AND [p1].[MiddleInitial] IS NOT NULL AND ([f0].[Id] <> 1 OR [f0].[Id] IS NULL) AND ([f].[Size] = [f0].[Size] OR ([f].[Size] IS NULL AND [f0].[Size] IS NULL)) AND ([p0].[LastName] = [p2].[LastName] OR ([p0].[LastName] IS NULL AND [p2].[LastName] IS NULL))) AS [Min] +SELECT [p0].[LastName], [f].[Size], MIN([f].[Size]) AS [Min] FROM [Person] AS [p] LEFT JOIN [Feet] AS [f] ON [p].[Id] = [f].[Id] LEFT JOIN [Person] AS [p0] ON [f].[Id] = [p0].[Id] @@ -809,12 +803,9 @@ public override async Task Whats_new_2021_sample_9(bool async) AssertSql( """ -SELECT [p].[FirstName] AS [Feet], ( - SELECT ISNULL(SUM([f].[Size]), 0) - FROM [Person] AS [p0] - LEFT JOIN [Feet] AS [f] ON [p0].[Id] = [f].[Id] - WHERE [p].[FirstName] = [p0].[FirstName] OR ([p].[FirstName] IS NULL AND [p0].[FirstName] IS NULL)) AS [Total] +SELECT [p].[FirstName] AS [Feet], ISNULL(SUM([f].[Size]), 0) AS [Total] FROM [Person] AS [p] +LEFT JOIN [Feet] AS [f] ON [p].[Id] = [f].[Id] GROUP BY [p].[FirstName] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index 7bbf3f1b085..0d364ed54ae 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -9062,20 +9062,10 @@ public override async Task Set_operator_with_navigation_in_projection_groupby_ag AssertSql( """ -SELECT [s].[Name], ( - SELECT ISNULL(SUM(CAST(LEN([c].[Location]) AS int)), 0) - FROM [Gears] AS [g2] - INNER JOIN [Squads] AS [s0] ON [g2].[SquadId] = [s0].[Id] - INNER JOIN [Cities] AS [c] ON [g2].[CityOfBirthName] = [c].[Name] - WHERE N'Marcus' IN ( - SELECT [g3].[Nickname] - FROM [Gears] AS [g3] - UNION ALL - SELECT [g4].[Nickname] - FROM [Gears] AS [g4] - ) AND ([s].[Name] = [s0].[Name] OR ([s].[Name] IS NULL AND [s0].[Name] IS NULL))) AS [SumOfLengths] +SELECT [s].[Name], ISNULL(SUM(CAST(LEN([c].[Location]) AS int)), 0) AS [SumOfLengths] FROM [Gears] AS [g] INNER JOIN [Squads] AS [s] ON [g].[SquadId] = [s].[Id] +INNER JOIN [Cities] AS [c] ON [g].[CityOfBirthName] = [c].[Name] WHERE N'Marcus' IN ( SELECT [g0].[Nickname] FROM [Gears] AS [g0] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Inheritance/TPCGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Inheritance/TPCGearsOfWarQuerySqlServerTest.cs index 3a3c4ee4ed1..5ab04293fd9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Inheritance/TPCGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Inheritance/TPCGearsOfWarQuerySqlServerTest.cs @@ -12168,38 +12168,16 @@ public override async Task Set_operator_with_navigation_in_projection_groupby_ag AssertSql( """ -SELECT [s].[Name], ( - SELECT ISNULL(SUM(CAST(LEN([c].[Location]) AS int)), 0) - FROM ( - SELECT [g2].[SquadId], [g2].[CityOfBirthName] - FROM [Gears] AS [g2] - UNION ALL - SELECT [o2].[SquadId], [o2].[CityOfBirthName] - FROM [Officers] AS [o2] - ) AS [u3] - INNER JOIN [Squads] AS [s0] ON [u3].[SquadId] = [s0].[Id] - INNER JOIN [Cities] AS [c] ON [u3].[CityOfBirthName] = [c].[Name] - WHERE N'Marcus' IN ( - SELECT [g3].[Nickname] - FROM [Gears] AS [g3] - UNION ALL - SELECT [o3].[Nickname] - FROM [Officers] AS [o3] - UNION ALL - SELECT [g4].[Nickname] - FROM [Gears] AS [g4] - UNION ALL - SELECT [o4].[Nickname] - FROM [Officers] AS [o4] - ) AND ([s].[Name] = [s0].[Name] OR ([s].[Name] IS NULL AND [s0].[Name] IS NULL))) AS [SumOfLengths] +SELECT [s].[Name], ISNULL(SUM(CAST(LEN([c].[Location]) AS int)), 0) AS [SumOfLengths] FROM ( - SELECT [g].[SquadId] + SELECT [g].[SquadId], [g].[CityOfBirthName] FROM [Gears] AS [g] UNION ALL - SELECT [o].[SquadId] + SELECT [o].[SquadId], [o].[CityOfBirthName] FROM [Officers] AS [o] ) AS [u] INNER JOIN [Squads] AS [s] ON [u].[SquadId] = [s].[Id] +INNER JOIN [Cities] AS [c] ON [u].[CityOfBirthName] = [c].[Name] WHERE N'Marcus' IN ( SELECT [g0].[Nickname] FROM [Gears] AS [g0] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Inheritance/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Inheritance/TPTGearsOfWarQuerySqlServerTest.cs index 565dd038fef..f0b5870f8cc 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/Inheritance/TPTGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/Inheritance/TPTGearsOfWarQuerySqlServerTest.cs @@ -10348,20 +10348,10 @@ public override async Task Set_operator_with_navigation_in_projection_groupby_ag AssertSql( """ -SELECT [s].[Name], ( - SELECT ISNULL(SUM(CAST(LEN([c].[Location]) AS int)), 0) - FROM [Gears] AS [g2] - INNER JOIN [Squads] AS [s0] ON [g2].[SquadId] = [s0].[Id] - INNER JOIN [Cities] AS [c] ON [g2].[CityOfBirthName] = [c].[Name] - WHERE N'Marcus' IN ( - SELECT [g3].[Nickname] - FROM [Gears] AS [g3] - UNION ALL - SELECT [g4].[Nickname] - FROM [Gears] AS [g4] - ) AND ([s].[Name] = [s0].[Name] OR ([s].[Name] IS NULL AND [s0].[Name] IS NULL))) AS [SumOfLengths] +SELECT [s].[Name], ISNULL(SUM(CAST(LEN([c].[Location]) AS int)), 0) AS [SumOfLengths] FROM [Gears] AS [g] INNER JOIN [Squads] AS [s] ON [g].[SquadId] = [s].[Id] +INNER JOIN [Cities] AS [c] ON [g].[CityOfBirthName] = [c].[Name] WHERE N'Marcus' IN ( SELECT [g0].[Nickname] FROM [Gears] AS [g0] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs index 047f653d4bd..5147d7bea91 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs @@ -2366,12 +2366,91 @@ public override async Task GroupBy_with_aggregate_through_navigation_property(bo AssertSql( """ -SELECT ( - SELECT MAX([c].[Region]) - FROM [Orders] AS [o0] - LEFT JOIN [Customers] AS [c] ON [o0].[CustomerID] = [c].[CustomerID] - WHERE [o].[EmployeeID] = [o0].[EmployeeID] OR ([o].[EmployeeID] IS NULL AND [o0].[EmployeeID] IS NULL)) AS [max] +SELECT MAX([c].[Region]) AS [max] +FROM [Orders] AS [o] +LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] +GROUP BY [o].[EmployeeID] +"""); + } + + public override async Task GroupBy_multiple_aggregates_sharing_same_navigation(bool async) + { + await base.GroupBy_multiple_aggregates_sharing_same_navigation(async); + + AssertSql( + """ +SELECT [o].[EmployeeID] AS [Key], ISNULL(SUM(CASE + WHEN [c].[City] = N'London' THEN 1 + ELSE 0 +END), 0) AS [Londons], ISNULL(SUM(CASE + WHEN [c].[City] = N'Berlin' THEN 1 + ELSE 0 +END), 0) AS [Berlins], ISNULL(SUM([o].[OrderID]), 0) AS [Total], COUNT(*) AS [Count] FROM [Orders] AS [o] +LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] +GROUP BY [o].[EmployeeID] +"""); + } + + public override async Task GroupBy_aggregate_through_two_level_navigation(bool async) + { + await base.GroupBy_aggregate_through_two_level_navigation(async); + + AssertSql( + """ +SELECT [o].[ProductID] AS [Key], ISNULL(SUM(CASE + WHEN [c].[City] = N'London' THEN 1 + ELSE 0 +END), 0) AS [Londons] +FROM [Order Details] AS [o] +INNER JOIN [Orders] AS [o0] ON [o].[OrderID] = [o0].[OrderID] +LEFT JOIN [Customers] AS [c] ON [o0].[CustomerID] = [c].[CustomerID] +GROUP BY [o].[ProductID] +"""); + } + + public override async Task GroupBy_Count_with_predicate_through_navigation_property(bool async) + { + await base.GroupBy_Count_with_predicate_through_navigation_property(async); + + AssertSql( + """ +SELECT [o].[EmployeeID] AS [Key], COUNT(CASE + WHEN [c].[City] = N'London' THEN 1 +END) AS [Londons] +FROM [Orders] AS [o] +LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] +GROUP BY [o].[EmployeeID] +"""); + } + + public override async Task GroupBy_key_and_aggregate_through_same_navigation(bool async) + { + await base.GroupBy_key_and_aggregate_through_same_navigation(async); + + AssertSql( + """ +SELECT [c].[City] AS [Key], COUNT(CASE + WHEN [c].[City] = N'London' THEN 1 +END) AS [Londons] +FROM [Orders] AS [o] +LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] +GROUP BY [c].[City] +"""); + } + + public override async Task GroupBy_aggregate_through_navigation_in_intermediate_projection(bool async) + { + await base.GroupBy_aggregate_through_navigation_in_intermediate_projection(async); + + AssertSql( + """ +SELECT [o].[EmployeeID] AS [Key], ISNULL(SUM(CASE + WHEN [c].[City] = N'London' THEN 1 + ELSE 0 +END), 0) AS [Londons] +FROM [Orders] AS [o] +LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] GROUP BY [o].[EmployeeID] """); } @@ -3831,27 +3910,20 @@ public override async Task Complex_query_with_groupBy_in_subquery4(bool async) AssertSql( """ -SELECT [c].[CustomerID], [s1].[Sum], [s1].[Count], [s1].[Key] +SELECT [c].[CustomerID], [s0].[Sum], [s0].[Count], [s0].[Key] FROM [Customers] AS [c] OUTER APPLY ( - SELECT ISNULL(SUM([s].[OrderID]), 0) AS [Sum], ( - SELECT COUNT(*) - FROM ( - SELECT [o0].[CustomerID], COALESCE([c1].[City], N'') + COALESCE([o0].[CustomerID], N'') AS [Key] - FROM [Orders] AS [o0] - LEFT JOIN [Customers] AS [c1] ON [o0].[CustomerID] = [c1].[CustomerID] - WHERE [c].[CustomerID] = [o0].[CustomerID] - ) AS [s0] - LEFT JOIN [Customers] AS [c2] ON [s0].[CustomerID] = [c2].[CustomerID] - WHERE ([s].[Key] = [s0].[Key] OR ([s].[Key] IS NULL AND [s0].[Key] IS NULL)) AND COALESCE([c2].[City], N'') + COALESCE([s0].[CustomerID], N'') LIKE N'Lon%') AS [Count], [s].[Key] + SELECT ISNULL(SUM([s].[OrderID]), 0) AS [Sum], COUNT(CASE + WHEN COALESCE([s].[City], N'') + COALESCE([s].[CustomerID], N'') LIKE N'Lon%' THEN 1 + END) AS [Count], [s].[Key] FROM ( - SELECT [o].[OrderID], COALESCE([c0].[City], N'') + COALESCE([o].[CustomerID], N'') AS [Key] + SELECT [o].[OrderID], [o].[CustomerID], [c0].[City], COALESCE([c0].[City], N'') + COALESCE([o].[CustomerID], N'') AS [Key] FROM [Orders] AS [o] LEFT JOIN [Customers] AS [c0] ON [o].[CustomerID] = [c0].[CustomerID] WHERE [c].[CustomerID] = [o].[CustomerID] ) AS [s] GROUP BY [s].[Key] -) AS [s1] +) AS [s0] ORDER BY [c].[CustomerID] """); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindQueryFiltersQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindQueryFiltersQuerySqlServerTest.cs index f6cdd9bd9f8..afc4dd0068d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindQueryFiltersQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindQueryFiltersQuerySqlServerTest.cs @@ -351,6 +351,65 @@ public override async Task Client_eval(bool async) AssertSql(); } + public override async Task GroupBy_aggregate_through_filtered_navigation(bool async) + { + await base.GroupBy_aggregate_through_filtered_navigation(async); + + AssertSql( + """ +@ef_filter__TenantPrefix_startswith='B%' (Size = 40) + +SELECT [o].[EmployeeID] AS [Key], COUNT(CASE + WHEN [c0].[City] = N'London' THEN 1 +END) AS [Londons] +FROM [Orders] AS [o] +LEFT JOIN ( + SELECT [c].[CustomerID], [c].[City], [c].[CompanyName] + FROM [Customers] AS [c] + WHERE [c].[CompanyName] LIKE @ef_filter__TenantPrefix_startswith ESCAPE N'\' +) AS [c0] ON [o].[CustomerID] = [c0].[CustomerID] +WHERE [c0].[CustomerID] IS NOT NULL AND [c0].[CompanyName] IS NOT NULL +GROUP BY [o].[EmployeeID] +"""); + } + + public override async Task GroupBy_aggregate_through_filtered_navigation_with_total(bool async) + { + await base.GroupBy_aggregate_through_filtered_navigation_with_total(async); + + AssertSql( + """ +@ef_filter__TenantPrefix_startswith='B%' (Size = 40) + +SELECT [o].[EmployeeID] AS [Key], COUNT(*) AS [Total], COUNT(CASE + WHEN [c0].[City] = N'London' THEN 1 +END) AS [Londons] +FROM [Orders] AS [o] +LEFT JOIN ( + SELECT [c].[CustomerID], [c].[City], [c].[CompanyName] + FROM [Customers] AS [c] + WHERE [c].[CompanyName] LIKE @ef_filter__TenantPrefix_startswith ESCAPE N'\' +) AS [c0] ON [o].[CustomerID] = [c0].[CustomerID] +WHERE [c0].[CustomerID] IS NOT NULL AND [c0].[CompanyName] IS NOT NULL +GROUP BY [o].[EmployeeID] +"""); + } + + public override async Task GroupBy_aggregate_through_filtered_navigation_ignore_query_filters(bool async) + { + await base.GroupBy_aggregate_through_filtered_navigation_ignore_query_filters(async); + + AssertSql( + """ +SELECT [o].[EmployeeID] AS [Key], COUNT(CASE + WHEN [c].[City] = N'London' THEN 1 +END) AS [Londons] +FROM [Orders] AS [o] +LEFT JOIN [Customers] AS [c] ON [o].[CustomerID] = [c].[CustomerID] +GROUP BY [o].[EmployeeID] +"""); + } + public override async Task Included_many_to_one_query2(bool async) { await base.Included_many_to_one_query2(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs index a64e37aa521..0c90381e68b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs @@ -9025,20 +9025,10 @@ public override async Task Set_operator_with_navigation_in_projection_groupby_ag AssertSql( """ -SELECT [s].[Name], ( - SELECT ISNULL(SUM(CAST(LEN([c].[Location]) AS int)), 0) - FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g2] - INNER JOIN [Squads] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s0] ON [g2].[SquadId] = [s0].[Id] - INNER JOIN [Cities] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [c] ON [g2].[CityOfBirthName] = [c].[Name] - WHERE N'Marcus' IN ( - SELECT [g3].[Nickname] - FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g3] - UNION ALL - SELECT [g4].[Nickname] - FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g4] - ) AND ([s].[Name] = [s0].[Name] OR ([s].[Name] IS NULL AND [s0].[Name] IS NULL))) AS [SumOfLengths] +SELECT [s].[Name], ISNULL(SUM(CAST(LEN([c].[Location]) AS int)), 0) AS [SumOfLengths] FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] INNER JOIN [Squads] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [s] ON [g].[SquadId] = [s].[Id] +INNER JOIN [Cities] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [c] ON [g].[CityOfBirthName] = [c].[Name] WHERE N'Marcus' IN ( SELECT [g0].[Nickname] FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g0] diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs b/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs index 449bc445e10..ee54b8b591a 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs @@ -907,15 +907,7 @@ public override void TVF_with_navigation_in_projection_groupby_aggregate() AssertSql( """ -SELECT [c].[LastName], ( - SELECT ISNULL(SUM(CAST(LEN([c1].[FirstName]) AS int)), 0) - FROM [Orders] AS [o0] - INNER JOIN [Customers] AS [c0] ON [o0].[CustomerId] = [c0].[Id] - INNER JOIN [Customers] AS [c1] ON [o0].[CustomerId] = [c1].[Id] - WHERE NOT EXISTS ( - SELECT 1 - FROM [dbo].[GetTopTwoSellingProducts]() AS [g0] - WHERE [g0].[ProductId] = 25) AND ([c].[LastName] = [c0].[LastName] OR ([c].[LastName] IS NULL AND [c0].[LastName] IS NULL))) AS [SumOfLengths] +SELECT [c].[LastName], ISNULL(SUM(CAST(LEN([c].[FirstName]) AS int)), 0) AS [SumOfLengths] FROM [Orders] AS [o] INNER JOIN [Customers] AS [c] ON [o].[CustomerId] = [c].[Id] WHERE NOT EXISTS ( @@ -932,18 +924,7 @@ public override void TVF_with_argument_being_a_subquery_with_navigation_in_proje AssertSql( """ -SELECT [c0].[LastName], ( - SELECT ISNULL(SUM(CAST(LEN([c3].[FirstName]) AS int)), 0) - FROM [Orders] AS [o0] - INNER JOIN [Customers] AS [c1] ON [o0].[CustomerId] = [c1].[Id] - INNER JOIN [Customers] AS [c3] ON [o0].[CustomerId] = [c3].[Id] - WHERE 25 NOT IN ( - SELECT [g0].[CustomerId] - FROM [dbo].[GetOrdersWithMultipleProducts](( - SELECT TOP(1) [c2].[Id] - FROM [Customers] AS [c2] - ORDER BY [c2].[Id])) AS [g0] - ) AND ([c0].[LastName] = [c1].[LastName] OR ([c0].[LastName] IS NULL AND [c1].[LastName] IS NULL))) AS [SumOfLengths] +SELECT [c0].[LastName], ISNULL(SUM(CAST(LEN([c0].[FirstName]) AS int)), 0) AS [SumOfLengths] FROM [Orders] AS [o] INNER JOIN [Customers] AS [c0] ON [o].[CustomerId] = [c0].[Id] WHERE 25 NOT IN ( diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index c590930011d..a4e93dad4f4 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -8693,20 +8693,10 @@ public override async Task Set_operator_with_navigation_in_projection_groupby_ag AssertSql( """ -SELECT "s"."Name", ( - SELECT COALESCE(SUM(length("c"."Location")), 0) - FROM "Gears" AS "g2" - INNER JOIN "Squads" AS "s0" ON "g2"."SquadId" = "s0"."Id" - INNER JOIN "Cities" AS "c" ON "g2"."CityOfBirthName" = "c"."Name" - WHERE 'Marcus' IN ( - SELECT "g3"."Nickname" - FROM "Gears" AS "g3" - UNION ALL - SELECT "g4"."Nickname" - FROM "Gears" AS "g4" - ) AND ("s"."Name" = "s0"."Name" OR ("s"."Name" IS NULL AND "s0"."Name" IS NULL))) AS "SumOfLengths" +SELECT "s"."Name", COALESCE(SUM(length("c"."Location")), 0) AS "SumOfLengths" FROM "Gears" AS "g" INNER JOIN "Squads" AS "s" ON "g"."SquadId" = "s"."Id" +INNER JOIN "Cities" AS "c" ON "g"."CityOfBirthName" = "c"."Name" WHERE 'Marcus' IN ( SELECT "g0"."Nickname" FROM "Gears" AS "g0"