Skip to content

[Regression 9.0 → 10.0] Query filter with inline array of navigation columns generates SQL referencing pruned columns #38700

Description

@yvesleguennec

Bug description

On EF Core 10, a global query filter that reads a navigation column via an inline array +
.Any(...) produces invalid SQL: the join-derived table for that navigation projects only the join
key (Id), while the filter's EXISTS clause still references another column from it (ServiceId).

Regression: same code works on EF Core 9.x (tested with 9.0.18 on net10.0), fails on EF Core
10.0.0–10.0.10. Also reproduced on PostgreSQL and MySQL (same error shape). Not caused by named
query filters — classic HasQueryFilter(lambda) reproduces it.

Expected: query returns ["ok"].

Workaround (EF10): avoid the inline collection — Scope.AuthorizedServiceIds.Contains(c.Parent.ServiceId) works.

Likely root cause (source reading on v10.0.10, SqlTreePruner.PruneValues): the inline array
becomes a ValuesExpression whose row value is a ColumnExpression (p0.ServiceId). EF10's
PruneValues (#36159) copies row values without
Visit(), so embedded column refs are never registered → join subquery pruned to Id only.
Related: #34954 (same territory, different failure).

Runnable projects (dotnet run, logs SQL):

Your code

public class Parent
{
    public int Id { get; set; }
    public int ServiceId { get; set; }
}

public class Child
{
    public int Id { get; set; }
    public int ParentId { get; set; }
    public Parent Parent { get; set; } = null!;
    public string Label { get; set; } = "";
}

public static class Scope
{
    public static List<int> AuthorizedServiceIds { get; set; } = [];
}

public class ReproContext(DbContextOptions<ReproContext> options) : DbContext(options)
{
    public DbSet<Parent> Parents => Set<Parent>();
    public DbSet<Child> Children => Set<Child>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Parent>().HasQueryFilter(
            p => Scope.AuthorizedServiceIds.Contains(p.ServiceId));

        // Inline array of a navigation column — this is the trigger.
        modelBuilder.Entity<Child>().HasQueryFilter(c =>
            new int?[] { c.Parent.ServiceId }
                .Any(id => id.HasValue && Scope.AuthorizedServiceIds.Contains(id.Value)));
    }
}

Scope.AuthorizedServiceIds = [10];
// Seed: Parent { Id = 1, ServiceId = 10 }, Child { ParentId = 1, Label = "ok" }
var results = await context.Children.AsNoTracking().Select(c => c.Label).ToListAsync(); // throws

Stack traces

Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 1: 'no such column: p0.ServiceId'.
   at Microsoft.Data.Sqlite.SqliteException.ThrowExceptionForRC(Int32 rc, sqlite3 db)
   at Microsoft.Data.Sqlite.SqliteCommand.PrepareAndEnumerateStatements()+MoveNext()
   at Microsoft.Data.Sqlite.SqliteCommand.GetStatements()+MoveNext()
   at Microsoft.Data.Sqlite.SqliteDataReader.NextResult()
   at Microsoft.Data.Sqlite.SqliteCommand.ExecuteReader(CommandBehavior behavior)
   at Microsoft.Data.Sqlite.SqliteCommand.ExecuteReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
   at Microsoft.Data.Sqlite.SqliteCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(AsyncEnumerator enumerator, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()

Same shape on PostgreSQL (column p0.ServiceId does not exist) and MySQL (Unknown column 'p0.ServiceId').

Verbose output

EF Core 10 — failing SQL (SQLite):

SELECT "c"."Label"
FROM "Children" AS "c"
INNER JOIN (
    SELECT "p"."Id"
    FROM "Parents" AS "p"
    WHERE "p"."ServiceId" = 10
) AS "p0" ON "c"."ParentId" = "p0"."Id"
WHERE EXISTS (
    SELECT 1
    FROM (SELECT "p0"."ServiceId" AS "Value") AS "v"
    WHERE "v"."Value" = 10)

p0 projects only Id, but the EXISTS still reads p0.ServiceId.

EF Core 9.0.18 — same code, working SQL (SQLite):

SELECT "c"."Label"
FROM "Children" AS "c"
INNER JOIN (
    SELECT "p"."Id", "p"."ServiceId"
    FROM "Parents" AS "p"
    WHERE "p"."ServiceId" = 10
) AS "p0" ON "c"."ParentId" = "p0"."Id"
WHERE EXISTS (
    SELECT 1
    FROM (SELECT "p0"."ServiceId" AS "Value") AS "v"
    WHERE "v"."Value" = 10)

Console output (EF10): FAIL — SqliteException: SQLite Error 1: 'no such column: p0.ServiceId'.
Console output (EF9): OK — ok

EF Core version

10.0.10 (appeared in 10.0.0)

Database provider

Microsoft.EntityFrameworkCore.Sqlite 10.0.10 (primary repro above).

Also reproduced on Npgsql 10.0.0 (PostgreSQL) and Microting.EntityFrameworkCore.MySql 10.0.10 (MySQL).

Target framework

net10.0

Operating system

Linux (Ubuntu)

IDE

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions