Skip to content

ComplexCollection (JSON) + QueryFilter on child entity → "Invalid column name '...'" when Parent.Include(Child) and FK nullable #37205

@SpaceOgre

Description

@SpaceOgre

Bug description

When using EF Core 10 relational ComplexCollection (JSON) mapping on a child entity and a DbContext-level query filter on that child, including the child navigation from the parent results in SQL which references an unselected/invalid column for the ComplexCollection. This causes an SQL exception (Invalid column name) when the FK is optional (nullable), so the result is a Left Join. If it is required then it works as it should.

Your code

#:package Microsoft.EntityFrameworkCore@10.0.0
#:package Microsoft.EntityFrameworkCore.SqlServer@10.0.0
#:property PublishAot=false

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

var connectionString = "Server=(localdb)\\MSSQLLocalDB;Database=EfCoreJoinRepro;Trusted_Connection=True;MultipleActiveResultSets=true";
var options = new DbContextOptionsBuilder<TestContext>()
    .UseSqlServer(connectionString)
    .EnableSensitiveDataLogging()
    .Options;

try
{
    using (var context = new TestContext(options))
    {
        context.Database.EnsureDeleted();
        context.Database.EnsureCreated();

        // Seed data
        var child = new Child
        {
            Id = Guid.NewGuid(),
            IsPublic = true,
            CareNeeds =
            [
                new() { Topic = "Physical Care", Answer = "Needs assistance with mobility" }
            ],
        };

        var parent = new Parent
        {
            Id = Guid.NewGuid(),
            Child = child
        };

        context.Childs.Add(child);
        context.Parents.Add(parent);
        context.SaveChanges();

        Console.WriteLine("✓ Data seeded successfully!\n");
    }

    using (var context = new TestContext(options))
    {
        try
        {
            var parent = await context.Parents
                .Include(p => p.Child)
                .FirstOrDefaultAsync();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception Type: {ex.GetType().Name}");
            Console.WriteLine($"Message: {ex.Message}");
            Console.WriteLine("---------------------------------------");

            var queryString = context.Parents
                .Include(p => p.Child)
                .ToQueryString();
            Console.WriteLine("Generated SQL Query:");
            Console.WriteLine(queryString);
        }
    }
}
catch (Exception ex)
{
    Console.WriteLine($"Setup failed: {ex.Message}");
    if (ex.InnerException != null)
        Console.WriteLine($"Inner: {ex.InnerException.Message}");
}

public sealed class TestContext(DbContextOptions<TestContext> options) : DbContext(options)
{
    public DbSet<Parent> Parents => Set<Parent>();
    public DbSet<Child> Childs => Set<Child>();

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        builder.ApplyConfigurationsFromAssembly(typeof(TestContext).Assembly);

        // This query filter is the key to reproducing the bug
        // When applied to Child, EF Core fails to properly generate column names for ComplexCollections
        builder.Entity<Child>().HasQueryFilter("CategoryProtection", child =>
            child.IsPublic
        );
    }
}

public class Parent
{
    public Guid Id { get; set; }
    public Child? Child { get; set; }
    //The bug only occurs when this is nullable, so for Left Joins. Inner Joins work fine.
    public Guid? ChildId { get; set; }
}

public class Child
{
    public Guid Id { get; set; }
    public Parent Parent { get; set; } = null!;
    public bool IsPublic { get; set; }
    public required List<CareNeedAnswer> CareNeeds { get; set; } = [];
}

public record CareNeedAnswer
{
    public string Topic { get; set; } = string.Empty;
    public string? Answer { get; set; }

    public sealed class ChildConfiguration : IEntityTypeConfiguration<Child>
    {
        public void Configure(EntityTypeBuilder<Child> builder)
        {
            builder.ComplexCollection(e => e.CareNeeds, cb => cb.ToJson());
            builder.HasOne<Parent>()
                .WithOne(p => p.Child)
                .HasForeignKey<Parent>(p => p.ChildId)
                .OnDelete(DeleteBehavior.Cascade);
        }
    }
}

Stack traces

Exception Type: SqlException
Message: Invalid column name 'CareNeeds'.
---------------------------------------
Generated SQL Query:
SELECT [p].[Id], [p].[ChildId], [p].[ChildId1], [c0].[Id], [c0].[IsPublic], [c0].[CareNeeds]
FROM [Parents] AS [p]
LEFT JOIN (
    SELECT [c].[Id], [c].[IsPublic]
    FROM [Childs] AS [c]
    WHERE [c].[IsPublic] = CAST(1 AS bit)
) AS [c0] ON [p].[ChildId] = [c0].[Id]

Verbose output


EF Core version

10.0.0

Database provider

Microsoft.EntityFrameworkCore.SqlServer

Target framework

.Net 10

Operating system

Windows 11

IDE

No response

Metadata

Metadata

Assignees

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions