Skip to content

Commit b539faa

Browse files
committed
Fix to #33004 - Unfulfillable nullable contraints are set for complex types with TPH entities
When figuring out nullability of columns representing a given property, if property is declared on derived entity in TPH, we make that column nullable. For complex type properties we should be doing the same (and we did), but declaring type of that property is the complex type itself. Instead we should look at the ContainingEntityType rather than just DeclaringType.
1 parent bbe4c50 commit b539faa

File tree

4 files changed

+112
-1
lines changed

4 files changed

+112
-1
lines changed

src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ public static class RelationalPropertyExtensions
2020
private static readonly bool UseOldBehavior32763 =
2121
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue32763", out var enabled32763) && enabled32763;
2222

23+
private static readonly bool UseOldBehavior33004 =
24+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue33004", out var enabled33004) && enabled33004;
25+
2326
private static readonly MethodInfo GetFieldValueMethod =
2427
typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.GetFieldValue), new[] { typeof(int) })!;
2528

@@ -1191,8 +1194,12 @@ public static bool IsColumnNullable(this IReadOnlyProperty property, in StoreObj
11911194
return sharedTableRootProperty.IsColumnNullable(storeObject);
11921195
}
11931196

1197+
var declaringEntityType = UseOldBehavior33004
1198+
? property.DeclaringType
1199+
: property.DeclaringType.ContainingEntityType;
1200+
11941201
return property.IsNullable
1195-
|| (property.DeclaringType is IReadOnlyEntityType entityType
1202+
|| (declaringEntityType is IReadOnlyEntityType entityType
11961203
&& ((entityType.BaseType != null
11971204
&& entityType.GetMappingStrategy() == RelationalAnnotationNames.TphMappingStrategy)
11981205
|| IsOptionalSharingDependent(entityType, storeObject, 0)));

test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.ComponentModel.DataAnnotations;
45
using Microsoft.EntityFrameworkCore.Metadata.Internal;
56
using Microsoft.EntityFrameworkCore.Migrations.Internal;
67
using Microsoft.EntityFrameworkCore.Scaffolding.Metadata;
@@ -1874,6 +1875,72 @@ await Test(
18741875
""");
18751876
}
18761877

1878+
[ConditionalFact]
1879+
public virtual Task Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH()
1880+
=> Test(
1881+
builder => { },
1882+
builder =>
1883+
{
1884+
builder.Entity(
1885+
"Contact", e =>
1886+
{
1887+
e.Property<int>("Id").ValueGeneratedOnAdd();
1888+
e.HasKey("Id");
1889+
e.Property<string>("Name");
1890+
e.ToTable("Contacts");
1891+
});
1892+
builder.Entity(
1893+
"Supplier", e =>
1894+
{
1895+
e.HasBaseType("Contact");
1896+
e.Property<int>("Number");
1897+
e.ComplexProperty<MyComplex>("MyComplex", ct =>
1898+
{
1899+
ct.ComplexProperty<MyNestedComplex>("MyNestedComplex").IsRequired();
1900+
});
1901+
});
1902+
},
1903+
model =>
1904+
{
1905+
var contactsTable = Assert.Single(model.Tables.Where(t => t.Name == "Contacts"));
1906+
Assert.Collection(
1907+
contactsTable.Columns,
1908+
c => Assert.Equal("Id", c.Name),
1909+
c => Assert.Equal("Discriminator", c.Name),
1910+
c => Assert.Equal("Name", c.Name),
1911+
c => Assert.Equal("Number", c.Name),
1912+
c =>
1913+
{
1914+
Assert.Equal("MyComplex_Prop", c.Name);
1915+
Assert.Equal(true, c.IsNullable);
1916+
},
1917+
c =>
1918+
{
1919+
Assert.Equal("MyComplex_MyNestedComplex_Bar", c.Name);
1920+
Assert.Equal(true, c.IsNullable);
1921+
},
1922+
c =>
1923+
{
1924+
Assert.Equal("MyComplex_MyNestedComplex_Foo", c.Name);
1925+
Assert.Equal(true, c.IsNullable);
1926+
});
1927+
});
1928+
1929+
protected class MyComplex
1930+
{
1931+
[Required]
1932+
public string Prop { get; set; }
1933+
1934+
[Required]
1935+
public MyNestedComplex Nested { get; set; }
1936+
}
1937+
1938+
public class MyNestedComplex
1939+
{
1940+
public int Foo { get; set; }
1941+
public DateTime Bar { get; set; }
1942+
}
1943+
18771944
protected class Person
18781945
{
18791946
public int Id { get; set; }

test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9230,6 +9230,25 @@ await Test(
92309230
AssertSql();
92319231
}
92329232

9233+
public override async Task Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH()
9234+
{
9235+
await base.Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH();
9236+
9237+
AssertSql(
9238+
"""
9239+
CREATE TABLE [Contacts] (
9240+
[Id] int NOT NULL IDENTITY,
9241+
[Discriminator] nvarchar(8) NOT NULL,
9242+
[Name] nvarchar(max) NULL,
9243+
[Number] int NULL,
9244+
[MyComplex_Prop] nvarchar(max) NULL,
9245+
[MyComplex_MyNestedComplex_Bar] datetime2 NULL,
9246+
[MyComplex_MyNestedComplex_Foo] int NULL,
9247+
CONSTRAINT [PK_Contacts] PRIMARY KEY ([Id])
9248+
);
9249+
""");
9250+
}
9251+
92339252
protected override string NonDefaultCollation
92349253
=> _nonDefaultCollation ??= GetDatabaseCollation() == "German_PhoneBook_CI_AS"
92359254
? "French_CI_AS"

test/EFCore.Sqlite.FunctionalTests/Migrations/MigrationsSqliteTest.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1702,6 +1702,24 @@ await Test(
17021702
""");
17031703
}
17041704

1705+
public override async Task Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH()
1706+
{
1707+
await base.Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH();
1708+
1709+
AssertSql(
1710+
"""
1711+
CREATE TABLE "Contacts" (
1712+
"Id" INTEGER NOT NULL CONSTRAINT "PK_Contacts" PRIMARY KEY AUTOINCREMENT,
1713+
"Discriminator" TEXT NOT NULL,
1714+
"Name" TEXT NULL,
1715+
"Number" INTEGER NULL,
1716+
"MyComplex_Prop" TEXT NULL,
1717+
"MyComplex_MyNestedComplex_Bar" TEXT NULL,
1718+
"MyComplex_MyNestedComplex_Foo" INTEGER NULL
1719+
);
1720+
""");
1721+
}
1722+
17051723
public override Task Create_sequence()
17061724
=> AssertNotSupportedAsync(base.Create_sequence, SqliteStrings.SequencesNotSupported);
17071725

0 commit comments

Comments
 (0)