diff --git a/src/EFCore/Query/Internal/StructuralTypeMaterializerSource.cs b/src/EFCore/Query/Internal/StructuralTypeMaterializerSource.cs index d0b55bfe469..f3bc8881b87 100644 --- a/src/EFCore/Query/Internal/StructuralTypeMaterializerSource.cs +++ b/src/EFCore/Query/Internal/StructuralTypeMaterializerSource.cs @@ -34,6 +34,9 @@ public static readonly MethodInfo PopulateListMethod private static readonly bool UseOldBehavior37162 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue37162", out var enabled37162) && enabled37162; + private static readonly bool UseOldBehavior37304 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue37304", out var enabled37304) && enabled37304; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -207,7 +210,12 @@ IServiceProperty serviceProperty IComplexProperty complexProperty => CreateMaterializeExpression( - new StructuralTypeMaterializerSourceParameters(complexProperty.ComplexType, "complexType", complexProperty.ClrType, nullable || complexProperty.IsNullable, QueryTrackingBehavior: null), + new StructuralTypeMaterializerSourceParameters( + complexProperty.ComplexType, + "complexType", + complexProperty.ClrType, + UseOldBehavior37304 ? nullable || complexProperty.IsNullable : complexProperty.IsNullable, + QueryTrackingBehavior: null), bindingInfo.MaterializationContextExpression), _ => throw new UnreachableException() diff --git a/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs index c660e8aa42f..b2123250eac 100644 --- a/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AdHocComplexTypeQueryTestBase.cs @@ -268,6 +268,80 @@ public class ComplexTypeWithAllNulls #endregion 37162 + #region 37304 + + [ConditionalFact] + public virtual async Task Non_optional_complex_type_with_all_nullable_properties_via_left_join() + { + var contextFactory = await InitializeAsync( + seed: context => + { + context.Add( + new Context37304.Parent + { + Id = 1, + Children = + [ + new Context37304.Child + { + Id = 1, + ComplexType = new Context37304.ComplexTypeWithAllNulls() + } + ] + }); + return context.SaveChangesAsync(); + }); + + await using var context = contextFactory.CreateContext(); + + var parent = await context.Set().Include(p => p.Children).SingleAsync(); + + var child = parent.Children.Single(); + Assert.NotNull(child.ComplexType); + Assert.Null(child.ComplexType.NullableString); + Assert.Null(child.ComplexType.NullableDateTime); + } + + private class Context37304(DbContextOptions options) : DbContext(options) + { + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(b => + { + b.Property(p => p.Id).ValueGeneratedNever(); + }); + + modelBuilder.Entity(b => + { + b.Property(c => c.Id).ValueGeneratedNever(); + b.HasOne(c => c.Parent).WithMany(p => p.Children).HasForeignKey(c => c.ParentId); + b.ComplexProperty(c => c.ComplexType); + }); + } + + public class Parent + { + public int Id { get; set; } + public List Children { get; set; } = []; + } + + public class Child + { + public int Id { get; set; } + public int ParentId { get; set; } + public Parent Parent { get; set; } = null!; + public ComplexTypeWithAllNulls ComplexType { get; set; } = null!; + } + + public class ComplexTypeWithAllNulls + { + public string? NullableString { get; set; } + public DateTime? NullableDateTime { get; set; } + } + } + + #endregion 37304 + #region Issue37337 [ConditionalFact]