diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs index 0332f6cb0..24462d6dd 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs @@ -76,7 +76,9 @@ public override TableExpressionBase Clone(string? alias, ExpressionVisitor cloni arguments[i] = cloningExpressionVisitor.Visit(Arguments[i]); } - return new PgTableValuedFunctionExpression(Alias, Name, arguments, ColumnInfos, WithOrdinality); + // Without ColumnInfos (e.g. unnest), PostgreSQL ties the output column name to the table alias, so preserve it. + // With explicit ColumnInfos (e.g. jsonb_to_recordset), apply the clone alias to keep FROM references consistent. + return new PgTableValuedFunctionExpression(ColumnInfos is null ? Alias : alias!, Name, arguments, ColumnInfos, WithOrdinality); } /// diff --git a/test/EFCore.PG.FunctionalTests/Query/Associations/OwnedJson/OwnedJsonCollectionNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/Associations/OwnedJson/OwnedJsonCollectionNpgsqlTest.cs index f0ec97362..896cb3897 100644 --- a/test/EFCore.PG.FunctionalTests/Query/Associations/OwnedJson/OwnedJsonCollectionNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/Associations/OwnedJson/OwnedJsonCollectionNpgsqlTest.cs @@ -226,6 +226,60 @@ GROUP BY a0."Key" """); } + [ConditionalFact] + public virtual async Task GroupBy_with_json_collection_predicate_and_projecting_group_elements() + { + await using var context = Fixture.CreateContext(); + + var result = await context.Set() + .Where(root => root.AssociateCollection.Any(element => element.Int > 0)) + .GroupBy(root => root.Name) + .Select(group => new { Elements = group.OrderBy(root => root.Id).Take(1) }) + .ToListAsync(); + + Assert.NotEmpty(result); + Assert.All(result, grouping => Assert.NotNull(grouping.Elements)); + + AssertSql( + """ +SELECT r1."Name", r3."Id", r3."Name", r3.c, r3.c0, r3.c1 +FROM ( + SELECT r."Name" + FROM "RootEntity" AS r + WHERE EXISTS ( + SELECT 1 + FROM ROWS FROM (jsonb_to_recordset(r."AssociateCollection") AS ( + "Id" integer, + "Int" integer, + "Ints" jsonb, + "Name" text, + "String" text + )) WITH ORDINALITY AS a + WHERE a."Int" > 0) + GROUP BY r."Name" +) AS r1 +LEFT JOIN ( + SELECT r2."Id", r2."Name", r2.c, r2.c0, r2.c1 + FROM ( + SELECT r0."Id", r0."Name", r0."AssociateCollection" AS c, r0."OptionalAssociate" AS c0, r0."RequiredAssociate" AS c1, ROW_NUMBER() OVER(PARTITION BY r0."Name" ORDER BY r0."Id" NULLS FIRST) AS row + FROM "RootEntity" AS r0 + WHERE EXISTS ( + SELECT 1 + FROM ROWS FROM (jsonb_to_recordset(r0."AssociateCollection") AS ( + "Id" integer, + "Int" integer, + "Ints" jsonb, + "Name" text, + "String" text + )) WITH ORDINALITY AS a0 + WHERE a0."Int" > 0) + ) AS r2 + WHERE r2.row <= 1 +) AS r3 ON r1."Name" = r3."Name" +ORDER BY r1."Name" NULLS FIRST, r3."Name" NULLS FIRST, r3."Id" NULLS FIRST +"""); + } + #endregion GroupBy public override async Task Select_within_Select_within_Select_with_aggregates()