Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Dec 16, 2025

Problem

When two derived types in TPH share a nullable complex property mapped to the same column, accessing the property when null throws NullReferenceException, particularly during materialization or when calling entry.OriginalValues.ToObject().

modelBuilder.Entity<Product>().HasDiscriminator<string>("Discriminator")
    .HasValue<Product1>("Product1")
    .HasValue<Product2>("Product2");

// Both map Price.Amount to the same "Price" column
modelBuilder.Entity<Product1>().ComplexProperty(x => x.Price, 
    p => p.Property(a => a.Amount).HasColumnName("Price"));
modelBuilder.Entity<Product2>().ComplexProperty(x => x.Price, 
    p => p.Property(a => a.Amount).HasColumnName("Price"));

// This throws NullReferenceException when Product1.Price is null
var original = (Product)entry.OriginalValues.ToObject();

Changes

Modified PropertyAccessorsFactory.CreateMemberAccess

Previously disabled null checks unconditionally when fromDeclaringType was true. Now preserves null checks for nullable reference type complex properties:

  • Checks if the property is a complex property (not a regular scalar property)
  • Checks if both the declaring entity and complex property types are reference types (not structs)
  • Respects the incoming addNullCheck parameter

Added test coverage in TPHComplexPropertySharingTest

  • Validates materialization with null complex properties
  • Validates original values access with null and populated complex properties
Original prompt

This section details on the original issue you should resolve

<issue_title>map two classes with same nullable complex properties to same column will result in a NullReferenceException</issue_title>
<issue_description>### Bug description

I have a base class and several child classes with some of them share some complex properties. I try to map them into one table and want to map same complex properties into the same column. This will result in a NullReferenceException if the property is null. See example code.

With different column names the same setup works.

Your code

using Microsoft.EntityFrameworkCore;

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

context.Products.Add(new Product1() { Name = "Product 1" });
await context.SaveChangesAsync();

context.ChangeTracker.Clear();
var product = await context.Products.FirstAsync();
var originalProduct = (Product)context.ChangeTracker.Entries().First().OriginalValues.ToObject();


public sealed class ProductContext : DbContext
{
    public DbSet<Product> Products { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer(Environment.GetEnvironmentVariable("Test__SqlServer__DefaultConnection"))
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>().HasDiscriminator<string>("Discriminator")
            .HasValue<Product1>("Product1").HasValue<Product2>("Product2");
        modelBuilder.Entity<Product1>().ComplexProperty(x => x.Price, p => p.Property(a => a.Amount).HasColumnName("Price"));
        modelBuilder.Entity<Product2>().ComplexProperty(x => x.Price, p => p.Property(a => a.Amount).HasColumnName("Price"));
    }
}

public abstract class Product
{
    public int Id { get; private set; }
    public required string Name { get; set; }
    
}

public class Product1 : Product
{
    public Price? Price { get; set; }
}
public class Product2 : Product
{
    public Price? Price { get; set; }
}

public sealed class Price
{
    public required string Amount { get; init; }
}

Stack traces


Verbose output


EF Core version

10.0.1

Database provider

No response

Target framework

.NET 10

Operating system

No response

IDE

Rider</issue_description>

<agent_instructions>Start by creating a test that reproduces the reported issue</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@roji Confirmed, see exception below. @AndriySvyryd is this scenario supposed to be currently supported?
Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at lambda_method53(Closure, Product1, IReadOnlyList`1, String)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntryBase.WritePropertyValue(IPropertyBase propertyBase, Object value, Boolean forMaterialization)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntryBase.SetProperty(IPropertyBase propertyBase, Object value, Boolean isMaterialization, Boolean setModified, Boolean isCascadeDelete, CurrentValueType valueType)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntryBase.SetProperty(IPropertyBase propertyBase, Object value, Boolean isMaterialization, Boolean setModified, Boolean isCascadeDelete)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntryBase.set_Item(IPropertyBase propertyBase, Object value)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntryBase.AcceptChanges()
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.AcceptChanges()
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.AcceptAllChanges(IReadOnlyList`1 changedEntries)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(StateManager stateManager, Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken)
   at Program.<Main>$(String[] args) in /Users/roji/projects/test/Program.cs:line 15
   at Program.<Main>$(String[] args) in /Users/roji/projects/test/Program.cs:line 19
   at Program.<Main>(String[] args)
```</body></comment_ne...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes dotnet/efcore#37335

<!-- START COPILOT CODING AGENT TIPS -->
---

💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey).
Copilot AI and others added 4 commits December 16, 2025 23:35
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
…perties

Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Co-authored-by: AndriySvyryd <6539701+AndriySvyryd@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix NullReferenceException when mapping complex properties Dec 16, 2025
Copilot AI requested a review from AndriySvyryd December 16, 2025 23:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants