Skip to content

NullReferenceException in shaper for tuple query when a query-filtered entity is reached via both a scalar and collection navigation #3933

Closed

Description

@stevendarby

We found this while migrating a production application from SQL Server to PostgreSQL -- a query that had worked correctly on SQL Server started throwing this exception against efcore.pg.

Include your code

using Microsoft.EntityFrameworkCore;
using Npgsql;
using Testcontainers.MsSql;
using Testcontainers.PostgreSql;

var sqlServerContainer = new MsSqlBuilder().Build();
var postgresContainer = new PostgreSqlBuilder().Build();

await sqlServerContainer.StartAsync();
await postgresContainer.StartAsync();

await Run("sqlserver", new SqlServerReproContext(sqlServerContainer.GetConnectionString()));
await Run("postgres", new PostgresReproContext(postgresContainer.GetConnectionString()));

await sqlServerContainer.DisposeAsync();
await postgresContainer.DisposeAsync();

static async Task Run(string label, ReproContext context)
{
    await using (context)
    {
        await context.Database.EnsureCreatedAsync();

        const short OrgId = 1;
        var orderIdentifier = Guid.NewGuid();

        var representative = new Representative { Id = 1, OrgId = OrgId, IsActive = true };
        var vendor = new Vendor { Id = 1, OrgId = OrgId, IsActive = true, Representatives = [representative] };
        var vendorLink = new VendorLink { Id = 1, OrgId = OrgId, Vendor = vendor };
        var order = new Order { Id = 1, OrgId = OrgId, OrderIdentifier = orderIdentifier, VendorLinks = [vendorLink] };

        context.Orders.Add(order);
        await context.SaveChangesAsync();

        Console.WriteLine($"--- {label} ---");

        try
        {
            var result = await context.Orders
                .Where(x => x.OrderIdentifier == orderIdentifier)
                .Select(x => ValueTuple.Create(
                    x.Id,
                    x.VendorLinks.Any(vl => vl.Vendor.IsActive && vl.Vendor.Representatives.Any(r => r.IsActive))))
                .FirstOrDefaultAsync();

            Console.WriteLine($"SUCCESS: ({result.Item1}, {result.Item2})");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"FAILED: {ex.GetType().Name}: {ex.Message}");
            Console.WriteLine(ex.StackTrace);
        }

        Console.WriteLine();
    }
}

public class Representative
{
    public int Id { get; set; }
    public short OrgId { get; set; }
    public Guid RepresentativeIdentifier { get; set; }
    public int VendorId { get; set; }
    public bool IsActive { get; set; }
}

public class Vendor
{
    public int Id { get; set; }
    public short OrgId { get; set; }
    public Guid VendorIdentifier { get; set; }
    public bool IsActive { get; set; }
    public List<Representative> Representatives { get; set; } = [];
}

public class VendorLink
{
    public int Id { get; set; }
    public short OrgId { get; set; }
    public Guid VendorLinkIdentifier { get; set; }
    public int OrderId { get; set; }
    public int? VendorId { get; set; }
    public Vendor? Vendor { get; set; }
}

public class Order
{
    public int Id { get; set; }
    public short OrgId { get; set; }
    public Guid OrderIdentifier { get; set; }
    public List<VendorLink> VendorLinks { get; set; } = [];
}

public abstract class ReproContext : DbContext
{
    public short OrgId { get; set; } = 1;

    public DbSet<Order> Orders => Set<Order>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Representative>().HasKey(e => new { e.OrgId, e.Id });

        modelBuilder.Entity<Vendor>(builder =>
        {
            builder.HasKey(e => new { e.OrgId, e.Id });
            builder
                .HasMany(v => v.Representatives)
                .WithOne()
                .HasForeignKey(r => new { r.OrgId, r.VendorId })
                .IsRequired();

            modelBuilder.Entity<Vendor>().HasQueryFilter(x => x.OrgId == OrgId);
        });

        modelBuilder.Entity<VendorLink>(builder =>
        {
            builder.HasKey(e => new { e.OrgId, e.Id });
            builder
                .HasOne(vl => vl.Vendor)
                .WithMany()
                .HasForeignKey(vl => new { vl.OrgId, vl.VendorId })
                .IsRequired(false);
        });

        modelBuilder.Entity<Order>(builder =>
        {
            builder.HasKey(e => new { e.OrgId, e.Id });
            builder
                .HasMany(o => o.VendorLinks)
                .WithOne()
                .HasForeignKey(vl => new { vl.OrgId, vl.OrderId })
                .IsRequired();
        });
    }
}

public sealed class SqlServerReproContext(string connectionString) : ReproContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseSqlServer(connectionString);
}

public sealed class PostgresReproContext(string connectionString) : ReproContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var dataSourceBuilder = new NpgsqlDataSourceBuilder(connectionString);
        dataSourceBuilder.EnableRecordsAsTuples();
        optionsBuilder.UseNpgsql(dataSourceBuilder.Build());
    }
}

Include the output of your code

Against SQL Server, the query returns (1, True) as expected.

Against Npgsql, it throws:

System.NullReferenceException: Object reference not set to an instance of an object.
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.CreateGetValueExpression(ParameterExpression dbDataReader, Int32 index, Boolean nullable, RelationalTypeMapping typeMapping, Type type, IPropertyBase property)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.VisitExtension(Expression extensionExpression)
   at System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.ProcessShaper(Expression shaperExpression, Expression& relationalCommandResolver, IReadOnlyList`1& readerColumns, LambdaExpression& relatedDataLoaders, Int32& collectionId)
   at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.VisitShapedQuery(ShapedQueryExpression shapedQueryExpression)
   ...

Include provider and version information

EF Core version: 10.0.12
Npgsql.EntityFrameworkCore.PostgreSQL version: 10.0.3
Database server version: PostgreSQL (tested via Testcontainers' default image); also reproduces against Aurora PostgreSQL
Operating system: Windows (also seen in Linux-hosted production)
IDE: n/a (dotnet run)

Further technical details

Removing the query filter on Vendor, or splitting the tuple-projecting query into two separate non-tuple queries, both make it work again.

Activity

  1. added theissue type on Oct 14, 2026
  2. roji commented on Oct 14, 2026

    @roji
    Member

    AI Triage

    The below is an AI-generated analysis and may contain inaccuracies.

    Confirmed bug, specific to the PostgreSQL provider in the tested comparisons. The supplied query reproduces the reported NullReferenceException in CreateGetValueExpression with Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3 and EF Core 10.0.12, against PostgreSQL 18.4. The original seeded repro returns (1, True) with SQLite on EF Core 10.0.12. SQL Server was not independently rerun; SQLite was used for the cross-provider check.

    Confirmed regression between 8.x and 9.x, rather than a new 10.0.12 regression. The reduced repro gives the following results:

    Npgsql.EntityFrameworkCore.PostgreSQL EF Core Result
    8.0.0 8.0.0 SQL compilation succeeds
    8.0.11 8.0.32 SQL compilation succeeds
    9.0.0 9.0.0 Reported NullReferenceException
    9.0.4 9.0.21 Reported NullReferenceException
    10.0.0 10.0.0 Reported NullReferenceException
    10.0.3 10.0.12 Reported NullReferenceException
    10.0.3 10.0.13 Reported NullReferenceException

    The reduced repro also compiles successfully with SQLite on EF Core 10.0.13. No working 9.x or 10.x combination was found among the versions tested.

    Reduction and likely cause

    The failure happens during query compilation: ToQueryString() is sufficient, with no database connection, schema creation, or seed data. The outer Where, FirstOrDefaultAsync, composite keys, and EnableRecordsAsTuples() are unnecessary. However, the otherwise-unused VendorIdentifier property matters: removing it from the original model makes the reduced query compile successfully.

    A likely provider-side cause is that PgRowValueExpression.VisitChildren and Update construct replacement row-value expressions without preserving TypeMapping. A projection rewrite can therefore leave the tuple projection without a mapping, while EF's shaper expects one. This is a source-based diagnosis, not a validated fix.

    An equivalent anonymous-object projection compiles successfully on Npgsql 10.0.3 / EF Core 10.0.12, with the query filter retained. Projecting Id and the boolean into an anonymous object and constructing the tuple after materialization is therefore a workaround candidate that avoids splitting the query.

    Duplicate search

    No convincing duplicate was found in the open or closed EF Core issues searched. dotnet/efcore#39255 has a similar missing-type-mapping/shaper exception, but involves SQL Server JSON primitive collections, not PostgreSQL row values; it is not established as the same bug. #2356 is related historical work on reading row values, rather than a duplicate of this regression.

    The issue type has been set to Bug. The tested provider difference and the row-expression mapping code point to keeping this issue in this repository, rather than closing it in favor of an EF Core issue.

    minimal repro

    Use a .NET 10 console project with implicit usings enabled and these package references:

    <ItemGroup>
      <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.12" />
      <PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" />
    </ItemGroup>

    No running PostgreSQL server is needed: the exception occurs before opening a connection.

    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Logging;
    
    using var context = new ReproContext();
    Console.WriteLine(context.Orders
        .Select(x => ValueTuple.Create(
            x.Id,
            x.VendorLinks.Any(vl => vl.Vendor!.IsActive && vl.Vendor.Representatives.Any(r => r.IsActive))))
        .ToQueryString());
    
    public class Representative
    {
        public int Id { get; set; }
        public int VendorId { get; set; }
        public bool IsActive { get; set; }
    }
    
    public class Vendor
    {
        public int Id { get; set; }
        public short OrgId { get; set; }
        public Guid VendorIdentifier { get; set; }
        public bool IsActive { get; set; }
        public List<Representative> Representatives { get; set; } = [];
    }
    
    public class VendorLink
    {
        public int Id { get; set; }
        public int OrderId { get; set; }
        public int? VendorId { get; set; }
        public Vendor? Vendor { get; set; }
    }
    
    public class Order
    {
        public int Id { get; set; }
        public List<VendorLink> VendorLinks { get; set; } = [];
    }
    
    public class ReproContext : DbContext
    {
        public short OrgId { get; set; } = 1;
        public DbSet<Order> Orders => Set<Order>();
    
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            => optionsBuilder
                .UseNpgsql("Host=localhost;Database=issue3933")
                .LogTo(Console.WriteLine, LogLevel.Information)
                .EnableSensitiveDataLogging();
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
            => modelBuilder.Entity<Vendor>().HasQueryFilter(x => x.OrgId == OrgId);
    }
  3. self-assigned this
    on Oct 14, 2026
  4. added this to the 10.0.4 milestone on Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions