EntityFramework.Docs icon indicating copy to clipboard operation
EntityFramework.Docs copied to clipboard

EFCore 6 Table Splitting regression bug with Project <Nullable>disable</Nullable>

Open stevozilik opened this issue 2 years ago • 2 comments

Table Splitting "OrderDetails" entity returning null when upgrading project from .Net 5 with Nullable disabled

Bug

  • Expected behaviour - DetailedOrder should be returned as it includes non-nullable Status property set to 1 as per guidance https://docs.microsoft.com/en-us/ef/core/modeling/table-splitting
  • Actual behaviour - EFCore 6 does not return DetailedOrder (i.e. returns null)

Setup

  • The below code works as expected with EFCore 5, or EFCore 6 when project is configured <Nullable>enable</Nullable> (and after changing DetailedOrder.Content to nullable byte[]?)
  • Exception thrown with EFCore 6 when project is configured <Nullable>disable</Nullable> (which is default when upgrading from .Net 5 and older)

Code

using Microsoft.EntityFrameworkCore;

using (var db = new DemoDbContext())
{
    db.Database.EnsureDeleted();
    db.Database.EnsureCreated();

    var order = new Order
    {
        Status = 1,
        DetailedOrder = new DetailedOrder { Status = 1 }
    };

    db.Add(order);
    db.SaveChanges();

    var detailedOrder = db.DetailedOrders.FirstOrDefault();
    if (detailedOrder == null)
    {
        throw new Exception("DetailedOrder should be returned due to non-nullable Status");
    }
    else
    {
        Console.WriteLine($"Successfully returned {nameof(DetailedOrder)} Id {detailedOrder.Id}");
    }
}

public class DemoDbContext : DbContext
{
    public DbSet<Order> Orders { get; set; }
    public DbSet<DetailedOrder> DetailedOrders { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite($"Data Source=demodbcontext.db");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<DetailedOrder>(dob =>
        {
            dob.ToTable("Orders");
            dob.Property(o => o.Status).HasColumnName("Status");            
        });

        modelBuilder.Entity<Order>(ob =>
            {
                ob.ToTable("Orders");
                ob.Property(o => o.Status).HasColumnName("Status");
                ob.HasOne(o => o.DetailedOrder).WithOne()
                    .HasForeignKey<DetailedOrder>(o => o.Id);
            });
    }
}

public class Order
{
    public int Id { get; set; }
    public int Status { get; set; }
    public DetailedOrder DetailedOrder { get; set; }
}

public class DetailedOrder
{
    public int Id { get; set; }
    public int Status { get; set; }
    public byte[] Content { get; set; }
}

Workaround Notes

Changing project to <Nullable>enable</Nullable> is clearly not an acceptable workaround, as it changes the runtime behaviour (as demonstrated here) and can cause other regression bugs (with other frameworks).

With .Net 6 Nullable disabled, even after telling ModelBuilder that Content is not required dob.Property(o => o.Content).IsRequired(false); does not help

Include provider and version information

EF Core version: 6.0.6 Database provider: Microsoft.EntityFrameworkCore.Sqlite Target framework: .NET 6.0 <Nullable>disable</Nullable> Operating system: Win 10 IDE: Visual Studio 2022

stevozilik avatar Jul 12 '22 09:07 stevozilik

Try ob.Navigation(o => o.DetailedOrder).IsRequired();

AndriySvyryd avatar Jul 13 '22 20:07 AndriySvyryd

That helped thank you @AndriySvyryd

Worth updating the documentation? https://docs.microsoft.com/en-us/ef/core/modeling/table-splitting

stevozilik avatar Jul 16 '22 08:07 stevozilik