Skip to content

SqlQuery generates invalid SQL with unreferenced parameter names due to parameter duplication #37409

Description

@bstvn

Bug description

Regression in EF Core 10.x - When using Database.SqlQueryRaw<T>() followed by GroupBy and a Select projection that uses OrderBy().First(), EF Core 10 generates invalid SQL where the parameter is renamed at the outer query level but the inner subqueries still reference the original parameter name.

This is a regression introduced by the parameter handling changes in #35200. The same code works correctly in EF Core 9.0.10.

Reproduction Steps

Minimal reproduction project

Full reproduction available at: [Link to repo]
Or zip: efcore-issue-repro.zip

Expected behavior

The query should execute successfully, properly passing the @space parameter to all subqueries.

Actual behavior

PostgreSQL throws: 42703: column "space" does not exist

Generated SQL (problematic)

SELECT e."Id", e."Name", e."TenantId", (
    SELECT e0."RoleType"
    FROM (
        WITH role_hierarchy AS (
            SELECT
                u."Id" AS "Id",
                u."Name" AS "Name",
                u."TenantId" AS "TenantId",
                ur."RoleType" AS "RoleType",
                ur."SpaceType" AS "SpaceType",
                ur."IsInherited" AS "IsInherited"
            FROM "Users" u
            JOIN "UserRoles" ur ON u."Id" = ur."UserId"
            WHERE ur."SpaceType" = @space  -- ❌ References @space but only @space00 exists!
        )
        SELECT * FROM role_hierarchy
        ORDER BY "Id", "RoleType"
    ) AS e0
    WHERE e."Id" = e0."Id" AND e."Name" = e0."Name" AND e."TenantId" = e0."TenantId"
    ORDER BY e0."RoleType", e0."IsInherited"
    LIMIT 1) AS "RoleType", (
    SELECT e1."IsInherited"
    FROM (
        WITH role_hierarchy AS (
            ...
            WHERE ur."SpaceType" = @space  -- ❌ Same problem
        )
        ...
    ) AS e1
    ...
    LIMIT 1) AS "IsInherited"
FROM (
    WITH role_hierarchy AS (
        ...
        WHERE ur."SpaceType" = @space  -- ❌ Same problem
    )
    ...
) AS e
GROUP BY e."Id", e."Name", e."TenantId"

The issue: EF Core declares @space00 as the parameter (visible in logging: [Parameters=[@space00='?' (DbType = Int32)]), but the raw SQL inside the subqueries still references @space.

Working behavior in EF Core 9.0.10

With EF Core 9.0.10 + Npgsql 9.0.4, the same query works correctly:

-- EF Core 9: Parameters=[@space='?' (DbType = Int32)]
SELECT e."Id", e."Name", e."TenantId", (
    SELECT e0."RoleType"
    FROM (
        WITH role_hierarchy AS (
            SELECT
                u."Id" AS "Id",
                u."Name" AS "Name",
                u."TenantId" AS "TenantId",
                ur."RoleType" AS "RoleType",
                ur."SpaceType" AS "SpaceType",
                ur."IsInherited" AS "IsInherited"
            FROM "Users" u
            JOIN "UserRoles" ur ON u."Id" = ur."UserId"
            WHERE ur."SpaceType" = @space  -- ✅ @space matches the declared parameter
        )
        SELECT * FROM role_hierarchy
        ORDER BY "Id", "RoleType"
    ) AS e0
    WHERE e."Id" = e0."Id" AND e."Name" = e0."Name" AND e."TenantId" = e0."TenantId"
    ORDER BY e0."RoleType", e0."IsInherited"
    LIMIT 1) AS "RoleType", (
    ...
) AS e
GROUP BY e."Id", e."Name", e."TenantId"

Key difference:

Version Declared Parameter Reference in SQL
EF Core 9.0.10 @space @space ✅
EF Core 10.0.1 @space00 @space ❌

Query results with EF Core 9 (working):

User: Alice (ID: 1), TenantId: 100, RoleType: 2, IsInherited: False
User: Bob (ID: 2), TenantId: 100, RoleType: 2, IsInherited: False
User: Charlie (ID: 3), TenantId: 200, RoleType: 5, IsInherited: True

Important observation

Before applying the projection, the query works correctly because @space appears only once:

// This works fine - no duplication, parameter is not renamed
var rawQuery = _dbContext.Database.SqlQueryRaw<UserRoleInheritance>(
    UserRoleInheritanceQuery,
    new NpgsqlParameter("@space", 2));

var results = await rawQuery.ToListAsync(); // ✅ Works - @space appears once

The problem only occurs when GroupBy + Select with First() is applied, which causes EF Core to duplicate the raw SQL subquery multiple times and rename the parameter.

Root cause analysis

When EF Core processes the GroupBy + Select with First():

  1. Subquery duplication: The raw SQL query is duplicated 3 times (once for the main query, once for each First() property projection)

  2. Parameter renaming: EF Core renames the parameter from @space to @space00 at the outer query level (likely to handle uniquification across duplicated subqueries)

  3. Missing parameter propagation: The parameter references inside the duplicated raw SQL subqueries are NOT updated to use the new parameter name

This causes PostgreSQL to fail because @space is not a declared parameter - only @space00 exists.

Environment

Failing configuration (EF Core 10):

  • EF Core version: 10.0.1
  • Database provider: Npgsql.EntityFrameworkCore.PostgreSQL 10.0.0
  • Target framework: .NET 10.0

Working configuration (EF Core 9):

  • EF Core version: 9.0.10
  • Database provider: Npgsql.EntityFrameworkCore.PostgreSQL 9.0.4
  • Target framework: .NET 9.0

Common:

  • Operating system: Windows 11
  • Database: PostgreSQL 17

Related Issues/PRs

Workarounds

Option 1: Materialize first (not ideal for large datasets)

var rawResults = await rawQuery.ToListAsync();
var projected = rawResults
    .GroupBy(x => new { x.Id, x.Name, x.TenantId })
    .Select(x => new UserData { ... })
    .ToList();

Option 2: Move GroupBy and projection into raw SQL (recommended)

Instead of applying LINQ operators after SqlQueryRaw, include the GROUP BY and aggregation directly in the SQL query:

private const string UserRoleInheritanceQueryWithGroupBy =
    """
    WITH role_hierarchy AS (
        SELECT
            u."Id" AS "Id",
            u."Name" AS "Name",
            u."TenantId" AS "TenantId",
            ur."RoleType" AS "RoleType",
            ur."SpaceType" AS "SpaceType",
            ur."IsInherited" AS "IsInherited",
            ROW_NUMBER() OVER (
                PARTITION BY u."Id", u."Name", u."TenantId"
                ORDER BY ur."RoleType", ur."IsInherited"
            ) AS rn
        FROM "Users" u
        JOIN "UserRoles" ur ON u."Id" = ur."UserId"
        WHERE ur."SpaceType" = @space
    )
    SELECT "Id", "Name", "TenantId", "RoleType", "IsInherited"
    FROM role_hierarchy
    WHERE rn = 1
    ORDER BY "Id"
    """;

// No LINQ GroupBy/Select needed - projection is done in SQL
var results = await _dbContext.Database
    .SqlQueryRaw<UserData>(UserRoleInheritanceQueryWithGroupBy, new NpgsqlParameter("@space", 2))
    .ToListAsync();

This approach:

  • Avoids the parameter renaming bug entirely
  • Is more efficient (single query, no subquery duplication)
  • Keeps all logic server-side

Your code

public class User
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public int TenantId { get; set; }
}

public class UserRole
{
    public int Id { get; set; }
    public int UserId { get; set; }
    public int RoleType { get; set; }
    public int SpaceType { get; set; }
    public bool IsInherited { get; set; }
}

public class UserRoleInheritance
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public int TenantId { get; set; }
    public int RoleType { get; set; }
    public int SpaceType { get; set; }
    public bool IsInherited { get; set; }
}

public class UserData
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public int TenantId { get; set; }
    public int RoleType { get; set; }
    public bool IsInherited { get; set; }
}

private const string UserRoleInheritanceQuery =
    """
    WITH role_hierarchy AS (
        SELECT
            u."Id" AS "Id",
            u."Name" AS "Name",
            u."TenantId" AS "TenantId",
            ur."RoleType" AS "RoleType",
            ur."SpaceType" AS "SpaceType",
            ur."IsInherited" AS "IsInherited"
        FROM "Users" u
        JOIN "UserRoles" ur ON u."Id" = ur."UserId"
        WHERE ur."SpaceType" = @space
    )
    SELECT * FROM role_hierarchy
    ORDER BY "Id", "RoleType"
    """;

// Execute SqlQueryRaw
var rawQuery = _dbContext.Database.SqlQueryRaw<UserRoleInheritance>(
    UserRoleInheritanceQuery,
    new NpgsqlParameter("@space", 2));

// Apply GroupBy + projection with First()
var projectedQuery = rawQuery
    .GroupBy(x => new { x.Id, x.Name, x.TenantId })
    .Select(x => new UserData
    {
        Id = x.Key.Id,
        Name = x.Key.Name,
        TenantId = x.Key.TenantId,
        RoleType = x.OrderBy(r => r.RoleType).ThenBy(r => r.IsInherited).First().RoleType,
        IsInherited = x.OrderBy(r => r.RoleType).ThenBy(r => r.IsInherited).First().IsInherited
    });

var results = await projectedQuery.ToListAsync(); // Throws exception

Stack traces

=== Executing query ===
dbug: 12/20/2025 16:55:03.423 CoreEventId.QueryCompilationStarting[10111] (Microsoft.EntityFrameworkCore.Query) 
      Compiling query expression: 
      'DbSet<UserRoleInheritance>().FromSql(WITH role_hierarchy AS (
          -- Simple CTE that joins User and UserRole
          SELECT
              u."Id" AS "Id",
              u."Name" AS "Name",
              u."TenantId" AS "TenantId",
              ur."RoleType" AS "RoleType",
              ur."SpaceType" AS "SpaceType",
              ur."IsInherited" AS "IsInherited"
          FROM "Users" u
          JOIN "UserRoles" ur ON u."Id" = ur."UserId"
          WHERE ur."SpaceType" >= @space
      )
      SELECT * FROM role_hierarchy
      ORDER BY "Id", "RoleType", @p)
          .GroupBy(x => new { 
              Id = x.Id, 
              Name = x.Name, 
              TenantId = x.TenantId
           })
          .Select(x => new UserData{ 
              Id = x.Key.Id, 
              Name = x.Key.Name, 
              TenantId = x.Key.TenantId, 
              RoleType = x
                  .OrderBy(r => r.RoleType)
                  .ThenBy(r => r.IsInherited)
                  .First().RoleType, 
              IsInherited = x
                  .OrderBy(r => r.RoleType)
                  .ThenBy(r => r.IsInherited)
                  .First().IsInherited 
          }
          )'
dbug: 12/20/2025 16:55:03.425 CoreEventId.QueryExecutionPlanned[10107] (Microsoft.EntityFrameworkCore.Query) 
      Generated query execution expression: 
      'queryContext => SingleQueryingEnumerable.Create<UserData>(
          relationalQueryContext: (RelationalQueryContext)queryContext, 
          relationalCommandResolver: parameters => [LIFTABLE Constant: RelationalCommandCache.QueryExpression(
              Projection Mapping:
                  Id -> 0
                  Name -> 1
                  TenantId -> 2
                  RoleType -> 3
                  IsInherited -> 4
              SELECT e.Id, e.Name, e.TenantId, (
                  SELECT TOP(1) e0.RoleType
                  FROM WITH role_hierarchy AS (
          -- Simple CTE that joins User and UserRole
          SELECT
              u."Id" AS "Id",
              u."Name" AS "Name",
              u."TenantId" AS "TenantId",
              ur."RoleType" AS "RoleType",
              ur."SpaceType" AS "SpaceType",
              ur."IsInherited" AS "IsInherited"
          FROM "Users" u
          JOIN "UserRoles" ur ON u."Id" = ur."UserId"
          WHERE ur."SpaceType" >= @space
      )
      SELECT * FROM role_hierarchy
      ORDER BY "Id", "RoleType"
                  WHERE ((e.Id == e0.Id) && (e.Name == e0.Name)) && (e.TenantId == e0.TenantId)
                  ORDER BY e0.RoleType ASC, e0.IsInherited ASC) AS RoleType, (
                  SELECT TOP(1) e1.IsInherited
                  FROM WITH role_hierarchy AS (
          -- Simple CTE that joins User and UserRole
          SELECT
              u."Id" AS "Id",
              u."Name" AS "Name",
              u."TenantId" AS "TenantId",
              ur."RoleType" AS "RoleType",
              ur."SpaceType" AS "SpaceType",
              ur."IsInherited" AS "IsInherited"
          FROM "Users" u
          JOIN "UserRoles" ur ON u."Id" = ur."UserId"
          WHERE ur."SpaceType" >= @space
      )
      SELECT * FROM role_hierarchy
      ORDER BY "Id", "RoleType"
                  WHERE ((e.Id == e1.Id) && (e.Name == e1.Name)) && (e.TenantId == e1.TenantId)
                  ORDER BY e1.RoleType ASC, e1.IsInherited ASC) AS IsInherited
              FROM WITH role_hierarchy AS (
          -- Simple CTE that joins User and UserRole
          SELECT
              u."Id" AS "Id",
              u."Name" AS "Name",
              u."TenantId" AS "TenantId",
              ur."RoleType" AS "RoleType",
              ur."SpaceType" AS "SpaceType",
              ur."IsInherited" AS "IsInherited"
          FROM "Users" u
          JOIN "UserRoles" ur ON u."Id" = ur."UserId"
          WHERE ur."SpaceType" >= @space
      )
      SELECT * FROM role_hierarchy
      ORDER BY "Id", "RoleType"
              GROUP BY e.Id, e.Name, e.TenantId) | Resolver: c => new RelationalCommandCache(
              c.Dependencies.MemoryCache, 
              c.RelationalDependencies.QuerySqlGeneratorFactory, 
              c.RelationalDependencies.RelationalParameterBasedSqlProcessorFactory, 
              Projection Mapping:
                  Id -> 0
                  Name -> 1
                  TenantId -> 2
                  RoleType -> 3
                  IsInherited -> 4
              SELECT e.Id, e.Name, e.TenantId, (
                  SELECT TOP(1) e0.RoleType
                  FROM WITH role_hierarchy AS (
          -- Simple CTE that joins User and UserRole
          SELECT
              u."Id" AS "Id",
              u."Name" AS "Name",
              u."TenantId" AS "TenantId",
              ur."RoleType" AS "RoleType",
              ur."SpaceType" AS "SpaceType",
              ur."IsInherited" AS "IsInherited"
          FROM "Users" u
          JOIN "UserRoles" ur ON u."Id" = ur."UserId"
          WHERE ur."SpaceType" >= @space
      )
      SELECT * FROM role_hierarchy
      ORDER BY "Id", "RoleType"
                  WHERE ((e.Id == e0.Id) && (e.Name == e0.Name)) && (e.TenantId == e0.TenantId)
                  ORDER BY e0.RoleType ASC, e0.IsInherited ASC) AS RoleType, (
                  SELECT TOP(1) e1.IsInherited
                  FROM WITH role_hierarchy AS (
          -- Simple CTE that joins User and UserRole
          SELECT
              u."Id" AS "Id",
              u."Name" AS "Name",
              u."TenantId" AS "TenantId",
              ur."RoleType" AS "RoleType",
              ur."SpaceType" AS "SpaceType",
              ur."IsInherited" AS "IsInherited"
          FROM "Users" u
          JOIN "UserRoles" ur ON u."Id" = ur."UserId"
          WHERE ur."SpaceType" >= @space
      )
      SELECT * FROM role_hierarchy
      ORDER BY "Id", "RoleType"
                  WHERE ((e.Id == e1.Id) && (e.Name == e1.Name)) && (e.TenantId == e1.TenantId)
                  ORDER BY e1.RoleType ASC, e1.IsInherited ASC) AS IsInherited
              FROM WITH role_hierarchy AS (
          -- Simple CTE that joins User and UserRole
          SELECT
              u."Id" AS "Id",
              u."Name" AS "Name",
              u."TenantId" AS "TenantId",
              ur."RoleType" AS "RoleType",
              ur."SpaceType" AS "SpaceType",
              ur."IsInherited" AS "IsInherited"
          FROM "Users" u
          JOIN "UserRoles" ur ON u."Id" = ur."UserId"
          WHERE ur."SpaceType" >= @space
      )
      SELECT * FROM role_hierarchy
      ORDER BY "Id", "RoleType"
              GROUP BY e.Id, e.Name, e.TenantId, 
              False, 
              Parameter
          )].GetRelationalCommandTemplate(parameters), 
          readerColumns: null, 
          shaper: (queryContext, dataReader, resultContext, resultCoordinator) => 
          {
              int? value1;
              string value2;
              int? value3;
              int? value4;
              bool? value5;
              value1 = (int?)dataReader.GetInt32(0);
              value2 = dataReader.GetString(1);
              value3 = (int?)dataReader.GetInt32(2);
              value4 = dataReader.IsDBNull(3) ? default(int?) : (int?)dataReader.GetInt32(3);
              value5 = dataReader.IsDBNull(4) ? default(bool?) : (bool?)dataReader.GetBoolean(4);
              return new UserData{ 
                  Id = (int)value1, 
                  Name = value2, 
                  TenantId = (int)value3, 
                  RoleType = (int)value4, 
                  IsInherited = (bool)value5 
              }
              ;
          }, 
          contextType: EFCoreBugRepro.AppDbContext, 
          standAloneStateManager: False, 
          detailedErrorsEnabled: False, 
          threadSafetyChecksEnabled: True)'
dbug: 12/20/2025 16:55:03.427 RelationalEventId.ConnectionOpening[20000] (Microsoft.EntityFrameworkCore.Database.Connection) 
      Opening connection to database 'efcorerepro' on server 'tcp://localhost:5432'.
dbug: 12/20/2025 16:55:03.428 RelationalEventId.ConnectionOpened[20001] (Microsoft.EntityFrameworkCore.Database.Connection) 
      Opened connection to database 'efcorerepro' on server 'tcp://localhost:5432'.
dbug: 12/20/2025 16:55:03.428 RelationalEventId.CommandCreating[20103] (Microsoft.EntityFrameworkCore.Database.Command) 
      Creating DbCommand for 'ExecuteReader'.
dbug: 12/20/2025 16:55:03.428 RelationalEventId.CommandCreated[20104] (Microsoft.EntityFrameworkCore.Database.Command) 
      Created DbCommand for 'ExecuteReader' (0ms).
dbug: 12/20/2025 16:55:03.428 RelationalEventId.CommandInitialized[20106] (Microsoft.EntityFrameworkCore.Database.Command) 
      Initialized DbCommand for 'ExecuteReader' (0ms).
dbug: 12/20/2025 16:55:03.428 RelationalEventId.CommandExecuting[20100] (Microsoft.EntityFrameworkCore.Database.Command) 
      Executing DbCommand [Parameters=[@space00='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
      SELECT e."Id", e."Name", e."TenantId", (
          SELECT e0."RoleType"
          FROM (
              WITH role_hierarchy AS (
                  -- Simple CTE that joins User and UserRole
                  SELECT
                      u."Id" AS "Id",
                      u."Name" AS "Name",
                      u."TenantId" AS "TenantId",
                      ur."RoleType" AS "RoleType",
                      ur."SpaceType" AS "SpaceType",
                      ur."IsInherited" AS "IsInherited"
                  FROM "Users" u
                  JOIN "UserRoles" ur ON u."Id" = ur."UserId"
                  WHERE ur."SpaceType" >= @space
              )
              SELECT * FROM role_hierarchy
              ORDER BY "Id", "RoleType"
          ) AS e0
          WHERE e."Id" = e0."Id" AND e."Name" = e0."Name" AND e."TenantId" = e0."TenantId"
          ORDER BY e0."RoleType", e0."IsInherited"
          LIMIT 1) AS "RoleType", (
          SELECT e1."IsInherited"
          FROM (
              WITH role_hierarchy AS (
                  -- Simple CTE that joins User and UserRole
                  SELECT
                      u."Id" AS "Id",
                      u."Name" AS "Name",
                      u."TenantId" AS "TenantId",
                      ur."RoleType" AS "RoleType",
                      ur."SpaceType" AS "SpaceType",
                      ur."IsInherited" AS "IsInherited"
                  FROM "Users" u
                  JOIN "UserRoles" ur ON u."Id" = ur."UserId"
                  WHERE ur."SpaceType" >= @space
              )
              SELECT * FROM role_hierarchy
              ORDER BY "Id", "RoleType"
          ) AS e1
          WHERE e."Id" = e1."Id" AND e."Name" = e1."Name" AND e."TenantId" = e1."TenantId"
          ORDER BY e1."RoleType", e1."IsInherited"
          LIMIT 1) AS "IsInherited"
      FROM (
          WITH role_hierarchy AS (
              -- Simple CTE that joins User and UserRole
              SELECT
                  u."Id" AS "Id",
                  u."Name" AS "Name",
                  u."TenantId" AS "TenantId",
                  ur."RoleType" AS "RoleType",
                  ur."SpaceType" AS "SpaceType",
                  ur."IsInherited" AS "IsInherited"
              FROM "Users" u
              JOIN "UserRoles" ur ON u."Id" = ur."UserId"
              WHERE ur."SpaceType" >= @space
          )
          SELECT * FROM role_hierarchy
          ORDER BY "Id", "RoleType"
      ) AS e
      GROUP BY e."Id", e."Name", e."TenantId"
fail: 12/20/2025 16:55:03.430 RelationalEventId.CommandError[20102] (Microsoft.EntityFrameworkCore.Database.Command) 
      Failed executing DbCommand (2ms) [Parameters=[@space00='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
      SELECT e."Id", e."Name", e."TenantId", (
          SELECT e0."RoleType"
          FROM (
              WITH role_hierarchy AS (
                  -- Simple CTE that joins User and UserRole
                  SELECT
                      u."Id" AS "Id",
                      u."Name" AS "Name",
                      u."TenantId" AS "TenantId",
                      ur."RoleType" AS "RoleType",
                      ur."SpaceType" AS "SpaceType",
                      ur."IsInherited" AS "IsInherited"
                  FROM "Users" u
                  JOIN "UserRoles" ur ON u."Id" = ur."UserId"
                  WHERE ur."SpaceType" >= @space
              )
              SELECT * FROM role_hierarchy
              ORDER BY "Id", "RoleType"
          ) AS e0
          WHERE e."Id" = e0."Id" AND e."Name" = e0."Name" AND e."TenantId" = e0."TenantId"
          ORDER BY e0."RoleType", e0."IsInherited"
          LIMIT 1) AS "RoleType", (
          SELECT e1."IsInherited"
          FROM (
              WITH role_hierarchy AS (
                  -- Simple CTE that joins User and UserRole
                  SELECT
                      u."Id" AS "Id",
                      u."Name" AS "Name",
                      u."TenantId" AS "TenantId",
                      ur."RoleType" AS "RoleType",
                      ur."SpaceType" AS "SpaceType",
                      ur."IsInherited" AS "IsInherited"
                  FROM "Users" u
                  JOIN "UserRoles" ur ON u."Id" = ur."UserId"
                  WHERE ur."SpaceType" >= @space
              )
              SELECT * FROM role_hierarchy
              ORDER BY "Id", "RoleType"
          ) AS e1
          WHERE e."Id" = e1."Id" AND e."Name" = e1."Name" AND e."TenantId" = e1."TenantId"
          ORDER BY e1."RoleType", e1."IsInherited"
          LIMIT 1) AS "IsInherited"
      FROM (
          WITH role_hierarchy AS (
              -- Simple CTE that joins User and UserRole
              SELECT
                  u."Id" AS "Id",
                  u."Name" AS "Name",
                  u."TenantId" AS "TenantId",
                  ur."RoleType" AS "RoleType",
                  ur."SpaceType" AS "SpaceType",
                  ur."IsInherited" AS "IsInherited"
              FROM "Users" u
              JOIN "UserRoles" ur ON u."Id" = ur."UserId"
              WHERE ur."SpaceType" >= @space
          )
          SELECT * FROM role_hierarchy
          ORDER BY "Id", "RoleType"
      ) AS e
      GROUP BY e."Id", e."Name", e."TenantId"
dbug: 12/20/2025 16:55:03.435 RelationalEventId.ConnectionClosing[20002] (Microsoft.EntityFrameworkCore.Database.Connection) 
      Closing connection to database 'efcorerepro' on server 'tcp://localhost:5432'.
dbug: 12/20/2025 16:55:03.436 RelationalEventId.ConnectionClosed[20003] (Microsoft.EntityFrameworkCore.Database.Connection) 
      Closed connection to database 'efcorerepro' on server 'tcp://localhost:5432' (0ms).
fail: 12/20/2025 16:55:03.436 CoreEventId.QueryIterationFailed[10100] (Microsoft.EntityFrameworkCore.Query) 
      An exception occurred while iterating over the results of a query for context type 'EFCoreBugRepro.AppDbContext'.
      Npgsql.PostgresException (0x80004005): 42703: column "space" does not exist
      
      POSITION: 2092
         at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage)
         at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
         at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
         at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming, CancellationToken cancellationToken)
         at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
         at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
         at Npgsql.NpgsqlCommand.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 Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
        Exception data:
          Severity: ERROR
          SqlState: 42703
          MessageText: column "space" does not exist
          Position: 2092
          File: parse_relation.c
          Line: 3716
          Routine: errorMissingColumn

Verbose output


EF Core version

10.0.1

Database provider

Npgsql.EntityFrameworkCore.PostgreSQL

Target framework

.NET 10

Operating system

Windows 11

IDE

Rider 2025.3.1

Activity

  1. self-assigned this
    on Dec 20, 2025
  2. roji commented on Dec 30, 2025

    @roji
    Member

    Confirmed regression in 10.0, still reproduces on latest main (3cb5690).

    Minimal repro
    await using var context = new AppDbContext();
    await context.Database.EnsureDeletedAsync();
    await context.Database.EnsureCreatedAsync();
    
    var query = await context.Database.SqlQueryRaw<UserRoleInheritance>(
        "SELECT 1 AS Id, 2 AS RoleType WHERE 1 = @space",
        new SqlParameter("@space", 2))
        .GroupBy(x => x.Id)
        .Select(x => new
        {
            Id = x.Key,
            RoleType = x.OrderBy(r => r.RoleType).First().RoleType
        })
        .ToListAsync();
    
    public class AppDbContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            => optionsBuilder
                .UseSqlServer(Environment.GetEnvironmentVariable("Test__SqlServer__DefaultConnection"))
                .LogTo(Console.WriteLine, LogLevel.Information)
                .EnableSensitiveDataLogging();
    }
    
    public class UserRoleInheritance
    {
        public int Id { get; set; }
        public int RoleType { get; set; }
    }
  3. added theissue type on Dec 30, 2025
  4. added 2 commits that reference this issue on Dec 31, 2025
    e499a8e
    a719a29
  5. roji commented on Dec 31, 2025

    @roji
    Member

    This happens because our translation of the GroupBy - followed by a non-reducing Select - duplicate the GroupBy source, which in this case is the FromSqlExpression. At that point we have two duplicate FromSqlExpression referencing the same DbParameter, and we uniquify its name twice. The fix is to track processed DbParameters to ensure we only uniquify them once.

  6. roji commented on Dec 31, 2025

    @roji
    Member

    This bug requires a relatively rare combination of factors to manifest (FromSql, with a DbParameter, followed by a GroupBy, followed by a non-reducing Select). It can also probably be worked around, by e.g. not using DbParameter in the problematic query, or fetching the results and doing the grouping on the client (even if both of these aren't perfect workarounds). As such I'll hold off backporting the fix for now, but if other people run into this we can reconsider.

  7. added a commit that references this issue on Dec 31, 2025
    6af1d56
  8. added a commit that references this issue on Jan 3, 2026
    afc2150
  9. changed the title [-]SqlQueryRaw with GroupBy and First() projection generates invalid SQL with unreferenced parameter names[/-] [+]SqlQuery generates invalid SQL with unreferenced parameter names due to parameter duplication[/+] on Jan 20, 2026
  10. 1 remaining item

  11. roji commented on Jan 20, 2026

    @roji
    Member

    Another simpler scenario has been reported in npgsql/efcore.pg#3703 (thanks @BladeWise, @dmancini-lmt), which shows that this affects more common scenarios. I'll take a look at backporting the fix in #37430 for 10.0.x.

    Simpler minimal repro
    await using var context = new BlogContext();
    await context.Database.EnsureDeletedAsync();
    await context.Database.EnsureCreatedAsync();
    
    var v = new NpgsqlParameter<int>("value", 1);
    var values = await context.Database.SqlQuery<int>($"""
        SELECT 1
        WHERE {v} >= 0 OR {v} <= 0
        """).ToListAsync();
    
    Console.WriteLine($"Result count: {values.Count}");
    
    public class BlogContext : DbContext
    {
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            => optionsBuilder
                .UseNpgsql(Environment.GetEnvironmentVariable("Test__Npgsql__DefaultConnection"))
                .LogTo(Console.WriteLine, LogLevel.Information)
                .EnableSensitiveDataLogging();
    }
  12. reopened this on Jan 20, 2026
  13. roji commented on Jan 20, 2026

    @roji
    Member

    A workaround (until this is fixed) for the above is to simply have two local variables referencing the same thing, and use this in the SQL query, rather than referencing the same local variable twice.

  14. added 2 commits that reference this issue on Jan 20, 2026
    309229d
    1a48235
  15. BladeWise commented on Jan 20, 2026

    @BladeWise
    Contributor

    I tried your suggestion, but it seems it does not work for the simple case where the same parameter reference is provided multiple times using a FormattableString.
    This is the modified test:

        [TestMethod]
        public async Task QueryWithMultipleReferencesToNpgsqlParameterWorks()
        {
            using var scope = _serviceProvider.CreateScope();
            var sp = scope.ServiceProvider;
            var dbContext = sp.GetRequiredService<SampleDbContext>();
    
            var v = new NpgsqlParameter<int>("value", 1);
            var v2 = v;
            var values = await dbContext.Database.SqlQuery<int>($"""
                                                                 SELECT 1
                                                                 WHERE {v} >= 0 OR {v2} <= 0
                                                                 """)
                                        .ToListAsync();
            Assert.IsNotEmpty(values);
        }

    which produces the following log

    Microsoft.EntityFrameworkCore.Database.Command: Error: Failed executing DbCommand (47,430ms) [Parameters=[value0='1'], CommandType='Text', CommandTimeout='30']
    SELECT 1
    WHERE @value >= 0 OR @value0 <= 0
    Microsoft.EntityFrameworkCore.Query: Error: An exception occurred while iterating over the results of a query for context type 'EFCore.PostgreSQL.Tests.SampleDbContext'.
    Npgsql.PostgresException (0x80004005): 42703: column "value" does not exist

    For the record, the only reliable workaround I found in that case was to explicitly build the FormattableString using a format that uses a single placeholder (which is the same as using SqlQueryRaw with the same parameter assocaited to a single positional placeholder):

        [TestMethod]
        public async Task QueryWithMultipleReferencesToNpgsqlParameterAsExplicitFormattableStringWorks()
        {
            using var scope = _serviceProvider.CreateScope();
            var sp = scope.ServiceProvider;
            var dbContext = sp.GetRequiredService<SampleDbContext>();
    
            var v = new NpgsqlParameter<int>("value", 1);
            var values = await dbContext.Database.SqlQuery<int>(FormattableStringFactory.Create("""
                                                                                                SELECT 1
                                                                                                WHERE {0} >= 0 OR {0} <= 0
                                                                                                """,
                                                                                                v))
                                        .ToListAsync();
            Assert.IsNotEmpty(values);
        }

    which results in the following log

    Microsoft.EntityFrameworkCore.Database.Command: Information: Executed DbCommand (20ms) [Parameters=[value='1'], CommandType='Text', CommandTimeout='30']
    SELECT 1
    WHERE @value >= 0 OR @value <= 0
  16. roji commented on Jan 20, 2026

    @roji
    Member

    Right:

    var v = new NpgsqlParameter<int>("value", 1);
    var v2 = v;

    You most likely need two NpgsqlParameter with different names but the same value:

    var v = new NpgsqlParameter<int>("value", 1);
    var v2 = new NpgsqlParameter<int>("value", 1);
  17. BladeWise commented on Jan 20, 2026

    @BladeWise
    Contributor

    True, but in such a case it would be exactly the same as not using the DbParameter and provide 2 distinct parameters.
    What I meant to say is that if we want to avoid parameter duplication and not incur in the issue with SqlQuery, the only option is creating a FormattableString where parameters are not duplicated.

  18. added a commit that references this issue on Feb 2, 2026
    7b2471b
  19. added this to the 10.0.4 milestone on Feb 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions