-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Description
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