diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index ab0fae02254..91caabab0b4 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -2817,6 +2817,18 @@ private SqlRemappingVisitor PushdownIntoSubqueryInternal() _tables.Add(subquery); _tableReferences.Add(subqueryTableReferenceExpression); + var useOldBehavior = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue26587", out var enabled) + && enabled; + + if (!useOldBehavior) + { + // Remap tableReferences in inner so that all components follow referential integrity. + foreach (var tableReference in subquery._tableReferences) + { + tableReference.UpdateTableReference(this, subquery); + } + } + var projectionMap = new Dictionary(ReferenceEqualityComparer.Instance); if (_projection.Count > 0) @@ -2953,10 +2965,13 @@ private SqlRemappingVisitor PushdownIntoSubqueryInternal() subquery.ClearOrdering(); } - // Remap tableReferences in inner - foreach (var tableReference in subquery._tableReferences) + if (useOldBehavior) { - tableReference.UpdateTableReference(this, subquery); + // Remap tableReferences in inner + foreach (var tableReference in subquery._tableReferences) + { + tableReference.UpdateTableReference(this, subquery); + } } var tableReferenceUpdatingExpressionVisitor = new TableReferenceUpdatingExpressionVisitor(this, subquery); diff --git a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs index 6f89bffc442..9cd3343a89c 100644 --- a/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/NorthwindGroupByQueryTestBase.cs @@ -1810,6 +1810,20 @@ public virtual Task GroupBy_aggregate_Pushdown(bool async) .Skip(4)); } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GroupBy_aggregate_using_grouping_key_Pushdown(bool async) + { + return AssertQuery( + async, + ss => ss.Set().GroupBy(e => e.CustomerID) + .Where(g => g.Count() > 10) + .Select(g => new { g.Key, Max = g.Max(e => g.Key) }) + .OrderBy(t => t.Key) + .Take(20) + .Skip(4)); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual Task GroupBy_aggregate_Pushdown_followed_by_projecting_Length(bool async) diff --git a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs index 1d10ea74f0c..013cd5922a4 100644 --- a/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/SimpleQueryTestBase.cs @@ -551,5 +551,52 @@ protected class Membership public Group Group { get; set; } public int GroupId { get; set; } } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task GroupBy_aggregate_on_right_side_of_join(bool async) + { + var contextFactory = await InitializeAsync(); + using var context = contextFactory.CreateContext(); + + var orderId = 123456; + + var orderItems = context.OrderItems.Where(o => o.OrderId == orderId); + var items = orderItems + .GroupBy(o => o.OrderId, (o, g) => new + { + Key = o, + IsPending = g.Max(y => y.ShippingDate == null && y.CancellationDate == null ? o : (o - 10000000)) + }) + .OrderBy(e => e.Key); + + + var query = orderItems + .Join(items, x => x.OrderId, x => x.Key, (x, y) => x) + .OrderBy(x => x.OrderId); + + + var users = async + ? await query.ToListAsync() + : query.ToList(); + } + + protected class Context26587 : DbContext + { + public Context26587(DbContextOptions options) + : base(options) + { + } + + public DbSet OrderItems { get; set; } + } + + protected class OrderItem + { + public int Id { get; set; } + public int OrderId { get; set; } + public DateTime? ShippingDate { get; set; } + public DateTime? CancellationDate { get; set; } + } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs index 6cf9201655c..ac22f5a8418 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindGroupByQuerySqlServerTest.cs @@ -1316,6 +1316,26 @@ ORDER BY [t].[CustomerID] OFFSET @__p_1 ROWS"); } + public override async Task GroupBy_aggregate_using_grouping_key_Pushdown(bool async) + { + await base.GroupBy_aggregate_using_grouping_key_Pushdown(async); + + AssertSql( + @"@__p_0='20' +@__p_1='4' + +SELECT [t].[Key], [t].[Max] +FROM ( + SELECT TOP(@__p_0) [o].[CustomerID] AS [Key], MAX([o].[CustomerID]) AS [Max] + FROM [Orders] AS [o] + GROUP BY [o].[CustomerID] + HAVING COUNT(*) > 10 + ORDER BY [o].[CustomerID] +) AS [t] +ORDER BY [t].[Key] +OFFSET @__p_1 ROWS"); + } + public override async Task GroupBy_aggregate_Pushdown_followed_by_projecting_Length(bool async) { await base.GroupBy_aggregate_Pushdown_followed_by_projecting_Length(async); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs index 821ba1ab853..6c327967dc4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/SimpleQuerySqlServerTest.cs @@ -128,5 +128,24 @@ ELSE CAST(0 AS bit) END AS [HasAccess] FROM [Users] AS [u]"); } + + public override async Task GroupBy_aggregate_on_right_side_of_join(bool async) + { + await base.GroupBy_aggregate_on_right_side_of_join(async); + + AssertSql( + @"@__orderId_0='123456' + +SELECT [o].[Id], [o].[CancellationDate], [o].[OrderId], [o].[ShippingDate] +FROM [OrderItems] AS [o] +INNER JOIN ( + SELECT [o0].[OrderId] AS [Key] + FROM [OrderItems] AS [o0] + WHERE [o0].[OrderId] = @__orderId_0 + GROUP BY [o0].[OrderId] +) AS [t] ON [o].[OrderId] = [t].[Key] +WHERE [o].[OrderId] = @__orderId_0 +ORDER BY [o].[OrderId]"); + } } }