-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Description
Bug description
When using C# record's with expression on an owned entity stored as JSON, EF Core corrupts the in-memory representation of nested owned entities after calling SaveChanges(). The nested properties become null in memory, causing NullReferenceException when accessed, even though the data is correctly persisted to the database.
Steps to Reproduce
- Create an entity with an owned entity (C# record) stored as JSON that contains nested owned entities
- Add the entity to the context and call
SaveChanges()(first save) - Use the
withexpression to create a modified copy of the owned entity - Call
SaveChanges()again (second save) - Access nested properties of the owned entity
Expected Behavior
The in-memory entity graph should remain intact after SaveChanges(). All nested properties of the owned entity should be accessible with their correct values.
In my reproduction code, product.Metadata.Weight.Value should remain 5 after the second SaveChanges().
Actual Behavior
After the second SaveChanges(), nested owned entities become null in memory. Accessing product.Metadata.Weight.Value throws NullReferenceException because Weight is now null.
Important: The data IS correctly persisted to the database. This is purely an in-memory corruption of the tracked entity.
Your code
Full reproduction code including failing test can be found here: https://github.com/gustavlarson/efcore-record-bug
using Microsoft.EntityFrameworkCore;
// Entity model
public class Product
{
public int Id { get; set; }
public required string Name { get; set; }
public ProductMetadata? Metadata { get; set; }
}
public record ProductMetadata
{
public Weight? Weight { get; init; }
public string? Color { get; init; }
}
public record Weight
{
public int Value { get; init; }
public string Unit { get; init; }
}
// DbContext configuration
public class TestDbContext : DbContext
{
public TestDbContext(DbContextOptions<TestDbContext> options) : base(options) { }
public DbSet<Product> Products => Set<Product>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Name).IsRequired().HasMaxLength(200);
entity.OwnsOne(e => e.Metadata, metadata =>
{
metadata.ToJson(); // Key: stored as JSON
metadata.OwnsOne(e => e.Weight);
});
});
}
}
// Reproduction
var product = new Product
{
Name = "Test Product",
Metadata = new()
{
Weight = new() { Value = 5 }
}
};
context.Products.Add(product);
await context.SaveChangesAsync(); // First save - works fine
// Use 'with' expression to create modified copy
product.Metadata = product.Metadata with { Color = product.Metadata.Color };
// At this point, product.Metadata.Weight.Value is still 5
Console.WriteLine($"Before SaveChanges: {product.Metadata.Weight.Value}"); // Prints: 5
await context.SaveChangesAsync(); // Second save - triggers bug
// BUG: Weight is now null!
Console.WriteLine($"After SaveChanges: {product.Metadata.Weight.Value}"); // NullReferenceException!Stack traces
Verbose output
EF Core version
10.0.2
Database provider
No response
Target framework
.NET 10
Operating system
Windows 11
IDE
No response