diff --git a/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs b/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs index 0d7f5c418..7638bb19a 100644 --- a/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs +++ b/src/EFCore.PG/Query/Expressions/Internal/PgTableValuedFunctionExpression.cs @@ -89,7 +89,9 @@ public override TableExpressionBase Clone(string? alias, ExpressionVisitor cloni arguments[i] = (SqlExpression)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/AdHocJsonQueryNpgsqlTest.cs b/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs index 056aab9d1..379234bc2 100644 --- a/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs +++ b/test/EFCore.PG.FunctionalTests/Query/AdHocJsonQueryNpgsqlTest.cs @@ -246,6 +246,47 @@ LIMIT 2 } } + [ConditionalFact] + public virtual async Task GroupBy_with_json_collection_predicate_and_projecting_group_elements_works() + { + var contextFactory = await InitializeAsync( + seed: async context => + { + context.Entities.AddRange( + new JsonEntity + { + Id = 1, + GroupKey = 10, + SortOrder = 1, + JsonCollection = [new JsonCollectionElement { Value = Guid.Parse("11111111-1111-1111-1111-111111111111") }] + }, + new JsonEntity + { + Id = 2, + GroupKey = 10, + SortOrder = 2, + JsonCollection = [new JsonCollectionElement { Value = Guid.Parse("11111111-1111-1111-1111-111111111111") }] + }); + + await context.SaveChangesAsync(); + }); + + await using var context = contextFactory.CreateContext(); + + var values = new[] { Guid.Parse("11111111-1111-1111-1111-111111111111") }; + + var result = await context.Entities + .Where(entity => entity.JsonCollection.Any(element => values.Contains(element.Value))) + .GroupBy(entity => entity.GroupKey) + .Select(g => new { GroupKey = g.Key, Elements = g.OrderBy(entity => entity.SortOrder).Take(1) }).ToListAsync(); + + var group = Assert.Single(result); + Assert.Equal(10, group.GroupKey); + + var element = Assert.Single(group.Elements); + Assert.Equal(1, element.Id); + } + protected class TypesDbContext(DbContextOptions options) : DbContext(options) { public DbSet Entities { get; set; } @@ -266,6 +307,30 @@ public class TypesJsonEntity public TimeSpan Interval { get; set; } } + protected class JsonEntitiesContext(DbContextOptions options) : DbContext(options) + { + public DbSet Entities { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().Property(x => x.Id).ValueGeneratedNever(); + modelBuilder.Entity().OwnsMany(x => x.JsonCollection).ToJson(); + } + } + + public class JsonEntity + { + public int Id { get; set; } + public int GroupKey { get; set; } + public int SortOrder { get; set; } + public List JsonCollection { get; set; } + } + + public class JsonCollectionElement + { + public Guid Value { get; set; } + } + protected void AssertSql(params string[] expected) => TestSqlLoggerFactory.AssertBaseline(expected); }