Skip to content

Commit 4923813

Browse files
authored
Fix to #33004 - Unfulfillable nullable contraints are set for complex types with TPH entities (#33054)
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 b0faa52 commit 4923813

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;
@@ -2141,6 +2142,72 @@ public virtual Task Create_table_with_optional_primitive_collection()
21412142
Assert.Single(customersTable.PrimaryKey!.Columns));
21422143
});
21432144

2145+
[ConditionalFact]
2146+
public virtual Task Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH()
2147+
=> Test(
2148+
builder => { },
2149+
builder =>
2150+
{
2151+
builder.Entity(
2152+
"Contact", e =>
2153+
{
2154+
e.Property<int>("Id").ValueGeneratedOnAdd();
2155+
e.HasKey("Id");
2156+
e.Property<string>("Name");
2157+
e.ToTable("Contacts");
2158+
});
2159+
builder.Entity(
2160+
"Supplier", e =>
2161+
{
2162+
e.HasBaseType("Contact");
2163+
e.Property<int>("Number");
2164+
e.ComplexProperty<MyComplex>("MyComplex", ct =>
2165+
{
2166+
ct.ComplexProperty<MyNestedComplex>("MyNestedComplex").IsRequired();
2167+
});
2168+
});
2169+
},
2170+
model =>
2171+
{
2172+
var contactsTable = Assert.Single(model.Tables.Where(t => t.Name == "Contacts"));
2173+
Assert.Collection(
2174+
contactsTable.Columns,
2175+
c => Assert.Equal("Id", c.Name),
2176+
c => Assert.Equal("Discriminator", c.Name),
2177+
c => Assert.Equal("Name", c.Name),
2178+
c => Assert.Equal("Number", c.Name),
2179+
c =>
2180+
{
2181+
Assert.Equal("MyComplex_Prop", c.Name);
2182+
Assert.Equal(true, c.IsNullable);
2183+
},
2184+
c =>
2185+
{
2186+
Assert.Equal("MyComplex_MyNestedComplex_Bar", c.Name);
2187+
Assert.Equal(true, c.IsNullable);
2188+
},
2189+
c =>
2190+
{
2191+
Assert.Equal("MyComplex_MyNestedComplex_Foo", c.Name);
2192+
Assert.Equal(true, c.IsNullable);
2193+
});
2194+
});
2195+
2196+
protected class MyComplex
2197+
{
2198+
[Required]
2199+
public string Prop { get; set; }
2200+
2201+
[Required]
2202+
public MyNestedComplex Nested { get; set; }
2203+
}
2204+
2205+
public class MyNestedComplex
2206+
{
2207+
public int Foo { get; set; }
2208+
public DateTime Bar { get; set; }
2209+
}
2210+
21442211
protected class Person
21452212
{
21462213
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
@@ -9328,6 +9328,25 @@ CONSTRAINT [PK_Customers] PRIMARY KEY ([Id])
93289328
""");
93299329
}
93309330

9331+
public override async Task Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH()
9332+
{
9333+
await base.Create_table_with_complex_type_with_required_properties_on_derived_entity_in_TPH();
9334+
9335+
AssertSql(
9336+
"""
9337+
CREATE TABLE [Contacts] (
9338+
[Id] int NOT NULL IDENTITY,
9339+
[Discriminator] nvarchar(8) NOT NULL,
9340+
[Name] nvarchar(max) NULL,
9341+
[Number] int NULL,
9342+
[MyComplex_Prop] nvarchar(max) NULL,
9343+
[MyComplex_MyNestedComplex_Bar] datetime2 NULL,
9344+
[MyComplex_MyNestedComplex_Foo] int NULL,
9345+
CONSTRAINT [PK_Contacts] PRIMARY KEY ([Id])
9346+
);
9347+
""");
9348+
}
9349+
93319350
protected override string NonDefaultCollation
93329351
=> _nonDefaultCollation ??= GetDatabaseCollation() == "German_PhoneBook_CI_AS"
93339352
? "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)