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 changed the title [WIP] Fix NullReferenceException when mapping complex properties Fix NullReferenceException when accessing null complex properties in TPH with shared columns 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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants