Skip to content

Default values in optional complex properties are not tracked/saved #37386

@peterwurzinger

Description

@peterwurzinger

Bug description

Tried to migrate OwnsOne(...). to an optional complex property, since #31376 fixed that.

It seems that default values for properties within the containing complex type are treated as unchanged, when the containing complex type was null before. I first observed this in actual application code for bool properties, but could reproduce it for DateTimeOffset as well.
The target SQL table shows a null value for the default-valued properties, so it's not an issue with materializing those on load.

Also complex types with more than one property are affected as well, I just used a one-property type for brevity. The main difference I could observe is, that for types with exactly one property on the complex type, the containing complex property would materialize to null on load, while for types with more than one property it would get created.
I haven't tried for types with multiple default-valued properties, though.

Your code

#:package Microsoft.EntityFrameworkCore.SqlServer@10.0.1
#:property PublishAot=false

using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;

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

var entity = new User()
{
  LockInfo = null
};
context.Users.Add(entity);
await context.SaveChangesAsync();

await LoadAndPrintLockInfo();

await LockUser(default(DateTimeOffset));
await LoadAndPrintLockInfo(); //Expectation would be "01.01.0001", actually is "null"

await LockUser(DateTimeOffset.Now.AddDays(30));
await LoadAndPrintLockInfo();

async Task LockUser(DateTimeOffset until)
{
  context.ChangeTracker.Clear();
  var user = await context.Users.SingleAsync();

  user.LockInfo = new(until);

  await context.SaveChangesAsync();
}

async Task LoadAndPrintLockInfo()
{
  context.ChangeTracker.Clear();
  var user = await context.Users.SingleAsync();

  Console.WriteLine("Currently locked until: " + (user.LockInfo?.LockedUntil.ToString() ?? "null"));
}


public class UserContext : DbContext
{
  public DbSet<User> Users { get; set; } = null!;

  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Initial Catalog=DefaultPropertyRepro;Integrated Security=true");

  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    var user = modelBuilder.Entity<User>();
    user.HasKey(p => p.Id);
    user.ComplexProperty(u => u.LockInfo).Property(p => p.LockedUntil);
  }
}

public class User
{
  public int Id { get; set; }
  public LockInfo? LockInfo { get; set; }
}

public record LockInfo(DateTimeOffset LockedUntil);

Stack traces


Verbose output


EF Core version

10.0.1

Database provider

Microsoft.EntityFrameworkCore.SqlServer

Target framework

.NET 10

Operating system

Windows 11

IDE

Visual Studio 2026

Metadata

Metadata

Assignees

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions