Skip to content

LEFT JOIN with GroupBy subquery throws InvalidOperationException on NULL values from non-matching rows #38055

@roji

Description

@roji

LEFT JOIN with GroupBy subquery throws InvalidOperationException on NULL values from non-matching rows

Moved from npgsql/efcore.pg#3801, reported by @lttruc1402.

When performing a LEFT JOIN against a GroupBy subquery containing aggregate functions (like Count()), EF Core's materializer attempts to read NULL values (from non-matching LEFT JOIN rows) into non-nullable int properties, crashing with InvalidOperationException: Nullable object must have a before the user's null-check can be evaluated.value

This is not provider- it reproduces on SQLite, SQL Server, and PostgreSQL alike.specific

Minimal repro (SQLite)

using Microsoft.EntityFrameworkCore;

await using var context = new TestContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();

context.PickupStatuses.AddRange(
    new PickupStatus { PickupStatusId = 1, Name = "Active" },
    new PickupStatus { PickupStatusId = 2, Name = "NoRequests" }
);
context.PickupRequests.Add(new PickupRequest { PickupStatusId = 1 });
await context.SaveChangesAsync();

var statusWithCount = context.PickupRequests
    .GroupBy(
        o => o.PickupStatusId,
        (pickupStatusId, el) => new { pickupStatusId, Count = el.Count() });

// Fails with both LeftJoin and GroupJoin+DefaultIfEmpty
var result = await context.PickupStatuses
    .LeftJoin(
        statusWithCount,
        e => e.PickupStatusId,
        e => e.pickupStatusId,
        (status, countInfo) => new { status, countInfo })
    .Select(e => new
    {
        e.status.PickupStatusId,
        Count = e.countInfo == null ? 0 : e.countInfo.Count,
    })
    .ToListAsync();

public class PickupStatus
{
    public int PickupStatusId { get; set; }
    public string Name { get; set; } = "";
}

public class PickupRequest
{
    public int Id { get; set; }
    public int PickupStatusId { get; set; }
}

public class TestContext : DbContext
{
    public DbSet<PickupStatus> PickupStatuses { get; set; }
    public DbSet<PickupRequest> PickupRequests { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder.UseSqlite("Data Source=repro.db");
}

Generated SQL

SELECT "p"."PickupStatusId", "p1"."pickupStatusId", "p1"."Count"
FROM "PickupStatuses" AS "p"
LEFT JOIN (
    SELECT "p0"."PickupStatusId" AS "pickupStatusId", COUNT(*) AS "Count"
    FROM "PickupRequests" AS "p0"
    GROUP BY "p0"."PickupStatusId"
) AS "p1" ON "p"."PickupStatusId" = "p1"."pickupStatusId"

Exception

System.InvalidOperationException: Nullable object must have a value.
   at lambda_method62(Closure, QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator)
   at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](...)

Notes

Metadata

Metadata

Assignees

No one assigned

    Type

    No fields configured for Bug.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions