diff --git a/src/EFCore/Query/Internal/StructuralTypeMaterializerSource.cs b/src/EFCore/Query/Internal/StructuralTypeMaterializerSource.cs index d0b55bfe469..2ecb217415c 100644 --- a/src/EFCore/Query/Internal/StructuralTypeMaterializerSource.cs +++ b/src/EFCore/Query/Internal/StructuralTypeMaterializerSource.cs @@ -131,6 +131,7 @@ public Expression CreateMaterializeExpression( // Creates a conditional expression that handles materialization of nullable complex types. // For nullable complex types, the method checks if all scalar properties are null // and returns default if they are, otherwise materializes the complex type instance. + // For required complex types, always materializes the instance even if all properties are null. // If there's a required (non-nullable) property, only that property is checked for efficiency. Expression HandleNullableComplexTypeMaterialization( IComplexType complexType, @@ -138,6 +139,14 @@ Expression HandleNullableComplexTypeMaterialization( Expression materializeExpression, MethodCallExpression getValueBufferExpression) { + // If the complex property is required, don't apply null-checking wrapper + if (!complexType.ComplexProperty.IsNullable) + { + return clrType.IsNullableType() + ? Convert(materializeExpression, clrType) + : materializeExpression; + } + // Get all scalar properties of the complex type (including nested ones). var allScalarProperties = complexType.GetFlattenedProperties().ToList(); diff --git a/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs index e336dd34498..b2ab876d43b 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs @@ -282,6 +282,81 @@ public class OptionalComplexProperty #endregion Issue37337 + #region NonOptionalComplexTypeViaLeftJoin + + [ConditionalFact] + public virtual async Task Non_optional_complex_type_with_all_nullable_properties_via_left_join() + { + var contextFactory = await InitializeAsync( + seed: context => + { + context.Projects.Add(new ContextNonOptionalComplexTypeViaLeftJoin.Project + { + Properties = new List + { + new ContextNonOptionalComplexTypeViaLeftJoin.ProjectLifetime + { + Lifetime = new ContextNonOptionalComplexTypeViaLeftJoin.Lifetime() + } + } + }); + return context.SaveChangesAsync(); + }); + + await using var context = contextFactory.CreateContext(); + + var project = await context.Projects.Include(p => p.Properties).SingleAsync(); + var projectLifetime = (ContextNonOptionalComplexTypeViaLeftJoin.ProjectLifetime)project.Properties.Single(); + + Assert.NotNull(projectLifetime.Lifetime); + Assert.Null(projectLifetime.Lifetime.Start); + Assert.Null(projectLifetime.Lifetime.End); + } + + private class ContextNonOptionalComplexTypeViaLeftJoin(DbContextOptions options) : DbContext(options) + { + public DbSet Projects { get; set; } = null!; + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasOne(p => p.Project) + .WithMany(p => p.Properties) + .HasForeignKey(p => p.ProjectId); + + modelBuilder.Entity() + .HasBaseType() + .ComplexProperty(p => p.Lifetime) + .IsRequired(true); + } + + public class Project + { + public int Id { get; set; } + public List Properties { get; set; } = null!; + } + + public class ProjectProperty + { + public int Id { get; set; } + public int ProjectId { get; set; } + public Project Project { get; set; } = null!; + } + + public class ProjectLifetime : ProjectProperty + { + public Lifetime Lifetime { get; set; } = null!; + } + + public class Lifetime + { + public DateTime? Start { get; init; } + public DateTime? End { get; init; } + } + } + + #endregion NonOptionalComplexTypeViaLeftJoin + protected override string StoreName => "AdHocComplexTypeQueryTest"; }