efcore icon indicating copy to clipboard operation
efcore copied to clipboard

Unfulfillable nullable contraints are set for complex types with TPH entities

Open Wasserwecken opened this issue 1 year ago • 5 comments

Usually, if a field has the Required attribute it is mapped as nullable: false. But if this field is declared in a TPH entity, that condition may not be possible.

Unfortunately, required-flaged fields within complex types are set as NOT NULL anyway, which prevents persisting entities that do not implement those fields.

image

Core for reproduction

Migrations are required (Add-Migration Init -c CustomContext5)

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ConsoleApp5
{
    internal static class Program5
    {
        public static void Test(string[] args)
        {
            var supplier = new Supplier() { Name = "Supplier", Extra = new() { Field = "Field" } };
            var customer = new Customer() { Name = "Customer" };

            using (var ctx = new CustomContext5())
            {
                ctx.Database.Migrate();
                ctx.Add(supplier);
                ctx.SaveChanges();

                ctx.Add(customer);
                ctx.SaveChanges();
            }

            Console.WriteLine("Done!");
            Console.ReadLine();
        }

        public class CustomContext5 : DbContext
        {
            public DbSet<Contact> Contacts { get; set; }
            public DbSet<Supplier> Suppliers { get; set; }
            public DbSet<Customer> Customers { get; set; }

            protected override void OnConfiguring(DbContextOptionsBuilder builder)
            {
                builder.LogTo(Console.WriteLine, new[] { RelationalEventId.CommandExecuting }).EnableSensitiveDataLogging();
                builder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=TestDb;Trusted_Connection=True");
            }

            protected override void OnModelCreating(ModelBuilder builder)
            {
                builder.Entity<Contact>().HasDiscriminator(e => e.Discriminator);
            }
        }

        public abstract class Contact
        {
            public int Id { get; set; }
            public string? Discriminator { get; set; }
            public string? Name { get; set; }
        }

        public class Supplier : Contact
        {
            [Required]
            public string? AnotherExtra { get; set; }
            public required Extra Extra { get; set; }
        }

        public class Customer : Contact
        {
        }

        [ComplexType]
        public class Extra
        {
            [Required]
            public string? Field { get; set; }
        }
    }
}

Include provider and version information

EF Core version: 8.0.1 Database provider: Microsoft.EntityFrameworkCore.SqlServer

Wasserwecken avatar Feb 05 '24 10:02 Wasserwecken

Minor note: This also affects atomic field types such as int and bool that are not flagged with Required.

Wasserwecken avatar Feb 05 '24 12:02 Wasserwecken

Note for team: still repros on latest daily. Patch candidate.

ajcvickers avatar Feb 09 '24 10:02 ajcvickers

problem is in RelationalPropertyExtensions.IsColumnNullable - there we try to compute nullability of a column that the given property is mapped to. For inheritance scenario we look at declaring type and check if it's part of TPH. However for property of a complex type, declaring type is that complex type so we don't do the proper check. We should find the parent non-complex type instead

maumar avatar Feb 10 '24 05:02 maumar

Should we consider patching this (bug in new feature, extremely low risk)?

roji avatar Feb 10 '24 09:02 roji

reopening for patch

maumar avatar Feb 10 '24 23:02 maumar

merged to release/8.0, closing

maumar avatar Mar 05 '24 23:03 maumar