Skip to content

Unexpected behavior expanding a back-navigation that contains derived entities with non-optional complex type where all properties are nullable #37304

@stefanoirace

Description

@stefanoirace

Bug description

Dealing with EF10, with reference to the issue #37162, I have a more complicated use case that is not behaving as expected even after applying the fix #37196.

The non-optional complex type with all properties set as nullable is declared on a derived class that is retrieved through a back-navigation:

public class Project
{
    public int Id { get; set; }
    public List<ProjectProperty> Properties { get; set; }
}

public class ProjectProperty
{
    public int Id { get; set; }
    public int ProjectId { get; set; }
    public Project Project { get; set; }
}

public class ProjectLifetime : ProjectProperty
{
    public Lifetime Lifetime { get; set; }
}

public class Lifetime
{
    public DateTime? Start { get; init; }
    public DateTime? End { get; init; }
}

ProjectProperty has a relationship to Project with both Forward and Back-Navigation properties.
ProjectLifetime extends ProjectProperty and declares a complex property configured as required.

In my scenario ProjectLifetime properties are declared as nullable and the database values are all NULL.

When I perform a query like the following, I unexpectedly get the Lifetime property of the ProjectLifetime class unexpectedly null.

var project = await context.Projects.Include(p => p.Properties).SingleAsync();
Console.WriteLine(((ProjectLifetime)project.Properties.Single()).Lifetime is null);

The same code works fine with EF8.
I suppose that this could be a corner case while accessing complex type through a back navigation declared by a derived class.

Your code

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

context.Projects.Add(new()
{
    Properties = new List<ProjectProperty>
    {
        new ProjectLifetime()
        {
            Lifetime = new Lifetime()
        }
    }
});
await context.SaveChangesAsync();

context.ChangeTracker.Clear();

var project = await context.Projects.Include(p => p.Properties).SingleAsync();
Console.WriteLine(((ProjectLifetime)project.Properties.Single()).Lifetime is null); // Prints true

public class BugDbContext : DbContext
{
    public DbSet<Project> Projects { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer("Server=localhost;Database=test;User=SA;Password=Abcd5678;Connect Timeout=60;ConnectRetryCount=0;Encrypt=false")
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<ProjectProperty>().HasOne(p => p.Project).WithMany(p => p.Properties).HasForeignKey(p => p.ProjectId);
        modelBuilder.Entity<ProjectLifetime>().HasBaseType<ProjectProperty>().ComplexProperty(p => p.Lifetime).IsRequired(true);
    }
}

public class Project
{
    public int Id { get; set; }
    public List<ProjectProperty> Properties { get; set; }
}

public class ProjectProperty
{
    public int Id { get; set; }
    public int ProjectId { get; set; }
    public Project Project { get; set; }
}

public class ProjectLifetime : ProjectProperty
{
    public Lifetime Lifetime { get; set; }
}

public class Lifetime
{
    public DateTime? Start { get; init; }
    public DateTime? End { get; init; }
}

Stack traces


Verbose output

The SQL STATEMENT produced by the repro-scenario:
SELECT [p1].[Id], [p0].[Id], [p0].[Discriminator], [p0].[ProjectId], [p0].[Lifetime_End], [p0].[Lifetime_Start]
      FROM (
          SELECT TOP(2) [p].[Id]
          FROM [Projects] AS [p]
      ) AS [p1]
      LEFT JOIN [ProjectProperty] AS [p0] ON [p1].[Id] = [p0].[ProjectId]
      ORDER BY [p1].[Id]

EF Core version

10.0.0

Database provider

No response

Target framework

.NET 10.0

Operating system

No response

IDE

Visual Studio 2026 18.0.2

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions