Skip to content

Commit 3697418

Browse files
authored
Update the entity type on check constraints when it's converted to a STET
Fixes #23399
1 parent 7ffacdb commit 3697418

File tree

5 files changed

+118
-30
lines changed

5 files changed

+118
-30
lines changed

src/EFCore.Relational/Metadata/Conventions/CheckConstraintConvention.cs

+43-2
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Conventions
1313
{
1414
/// <summary>
1515
/// A convention that ensures that the check constraints on the derived types are compatible with
16-
/// the check constraints on the base type.
16+
/// the check constraints on the base type. And also ensures that the declaring type is current.
1717
/// </summary>
1818
/// <remarks>
1919
/// See <see href="https://aka.ms/efcore-docs-conventions">Model building conventions</see> for more information.
2020
/// </remarks>
21-
public class CheckConstraintConvention : IEntityTypeBaseTypeChangedConvention
21+
public class CheckConstraintConvention : IEntityTypeBaseTypeChangedConvention, IEntityTypeAddedConvention
2222
{
2323
/// <summary>
2424
/// Creates a new instance of <see cref="CheckConstraintConvention" />.
@@ -43,6 +43,47 @@ public CheckConstraintConvention(
4343
/// </summary>
4444
protected virtual RelationalConventionSetBuilderDependencies RelationalDependencies { get; }
4545

46+
/// <summary>
47+
/// Called after an entity type is added to the model.
48+
/// </summary>
49+
/// <param name="entityTypeBuilder"> The builder for the entity type. </param>
50+
/// <param name="context"> Additional information associated with convention execution. </param>
51+
public virtual void ProcessEntityTypeAdded(IConventionEntityTypeBuilder entityTypeBuilder, IConventionContext<IConventionEntityTypeBuilder> context)
52+
{
53+
var entityType = entityTypeBuilder.Metadata;
54+
if (!entityType.HasSharedClrType)
55+
{
56+
return;
57+
}
58+
59+
List<IConventionCheckConstraint>? constraintsToReattach = null;
60+
foreach (var checkConstraint in entityType.GetCheckConstraints())
61+
{
62+
if (checkConstraint.EntityType == entityType)
63+
{
64+
continue;
65+
}
66+
67+
constraintsToReattach ??= new();
68+
69+
constraintsToReattach.Add(checkConstraint);
70+
}
71+
72+
if (constraintsToReattach == null)
73+
{
74+
return;
75+
}
76+
77+
foreach (var checkConstraint in constraintsToReattach)
78+
{
79+
var removedCheckConstraint = entityType.RemoveCheckConstraint(checkConstraint.ModelName);
80+
if (removedCheckConstraint != null)
81+
{
82+
CheckConstraint.Attach(entityType, removedCheckConstraint);
83+
}
84+
}
85+
}
86+
4687
/// <summary>
4788
/// Called after the base type of an entity type changes.
4889
/// </summary>

src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -67,19 +67,23 @@ public override ConventionSet CreateConventionSet()
6767
conventionSet.PropertyAddedConventions.Add(relationalColumnAttributeConvention);
6868
conventionSet.PropertyAddedConventions.Add(relationalCommentAttributeConvention);
6969

70+
var checkConstraintConvention = new CheckConstraintConvention(Dependencies, RelationalDependencies);
7071
var tableNameFromDbSetConvention = new TableNameFromDbSetConvention(Dependencies, RelationalDependencies);
7172
conventionSet.EntityTypeAddedConventions.Add(new RelationalTableAttributeConvention(Dependencies, RelationalDependencies));
7273
conventionSet.EntityTypeAddedConventions.Add(
7374
new RelationalTableCommentAttributeConvention(Dependencies, RelationalDependencies));
7475
conventionSet.EntityTypeAddedConventions.Add(tableNameFromDbSetConvention);
76+
conventionSet.EntityTypeAddedConventions.Add(checkConstraintConvention);
7577

7678
ValueGenerationConvention valueGenerationConvention =
7779
new RelationalValueGenerationConvention(Dependencies, RelationalDependencies);
7880
ReplaceConvention(conventionSet.EntityTypeBaseTypeChangedConventions, valueGenerationConvention);
81+
conventionSet.EntityTypeBaseTypeChangedConventions.Add(tableNameFromDbSetConvention);
82+
conventionSet.EntityTypeBaseTypeChangedConventions.Add(checkConstraintConvention);
83+
7984
ReplaceConvention(conventionSet.ForeignKeyPropertiesChangedConventions, valueGenerationConvention);
85+
8086
ReplaceConvention(conventionSet.ForeignKeyOwnershipChangedConventions, valueGenerationConvention);
81-
conventionSet.EntityTypeBaseTypeChangedConventions.Add(tableNameFromDbSetConvention);
82-
conventionSet.EntityTypeBaseTypeChangedConventions.Add(new CheckConstraintConvention(Dependencies, RelationalDependencies));
8387

8488
conventionSet.EntityTypeAnnotationChangedConventions.Add((RelationalValueGenerationConvention)valueGenerationConvention);
8589

src/EFCore.Relational/Metadata/Internal/CheckConstraint.cs

+19
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,23 @@ public static IEnumerable<IReadOnlyCheckConstraint> GetCheckConstraints(IReadOnl
166166
return null;
167167
}
168168

169+
/// <summary>
170+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
171+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
172+
/// any release. You should only use it directly in your code with extreme caution and knowing that
173+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
174+
/// </summary>
175+
public static void Attach(IConventionEntityType entityType, IConventionCheckConstraint detachedCheckConstraint)
176+
{
177+
var newCheckConstraint = new CheckConstraint(
178+
(IMutableEntityType)entityType,
179+
detachedCheckConstraint.ModelName,
180+
detachedCheckConstraint.Sql,
181+
detachedCheckConstraint.GetConfigurationSource());
182+
183+
Attach(detachedCheckConstraint, newCheckConstraint);
184+
}
185+
169186
/// <summary>
170187
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
171188
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -180,6 +197,8 @@ public static void Attach(IConventionCheckConstraint detachedCheckConstraint, IC
180197
((InternalCheckConstraintBuilder)existingCheckConstraint.Builder).HasName(
181198
detachedCheckConstraint.Name, nameConfigurationSource.Value);
182199
}
200+
201+
((InternalCheckConstraintBuilder)existingCheckConstraint.Builder).MergeAnnotationsFrom((CheckConstraint)detachedCheckConstraint);
183202
}
184203

185204
/// <summary>

src/EFCore.Relational/Metadata/Internal/InternalCheckConstraintBuilder.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal
1616
/// doing so can result in application failures when updating to a new Entity Framework Core release.
1717
/// </summary>
1818
public class InternalCheckConstraintBuilder :
19-
AnnotatableBuilder<CheckConstraint,
20-
IConventionModelBuilder>,
19+
AnnotatableBuilder<CheckConstraint, IConventionModelBuilder>,
2120
IConventionCheckConstraintBuilder
2221
{
2322
/// <summary>

test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs

+49-24
Original file line numberDiff line numberDiff line change
@@ -813,37 +813,62 @@ public override void Can_configure_owned_type()
813813
{
814814
var modelBuilder = CreateModelBuilder();
815815

816-
var ownedBuilder = modelBuilder.Entity<Customer>().OwnsOne(c => c.Details)
817-
.ToTable("CustomerDetails")
816+
modelBuilder.Ignore<Customer>();
817+
modelBuilder.Ignore<Product>();
818+
819+
var ownedBuilder = modelBuilder.Entity<OtherCustomer>().OwnsOne(c => c.Details)
820+
.ToTable("OtherCustomerDetails")
818821
.HasCheckConstraint("CK_CustomerDetails_T", "AlternateKey <> 0", c => c.HasName("CK_Guid"));
819822
ownedBuilder.Property(d => d.CustomerId);
820823
ownedBuilder.HasIndex(d => d.CustomerId);
821-
ownedBuilder.WithOwner(d => d.Customer)
824+
ownedBuilder.WithOwner(d => (OtherCustomer)d.Customer)
822825
.HasPrincipalKey(c => c.AlternateKey);
823826

827+
modelBuilder.Entity<SpecialCustomer>().OwnsOne(c => c.Details, b =>
828+
{
829+
b.ToTable("SpecialCustomerDetails");
830+
b.HasCheckConstraint("CK_CustomerDetails_T", "AlternateKey <> 0", c => c.HasName("CK_Guid"));
831+
b.Property(d => d.CustomerId);
832+
b.HasIndex(d => d.CustomerId);
833+
b.WithOwner(d => (SpecialCustomer)d.Customer)
834+
.HasPrincipalKey(c => c.AlternateKey);
835+
});
836+
824837
var model = modelBuilder.FinalizeModel();
825838

826-
var owner = model.FindEntityType(typeof(Customer));
827-
Assert.Equal(typeof(Customer).FullName, owner.Name);
828-
var ownership = owner.FindNavigation(nameof(Customer.Details)).ForeignKey;
829-
Assert.True(ownership.IsOwnership);
830-
Assert.Equal(nameof(Customer.Details), ownership.PrincipalToDependent.Name);
831-
Assert.Equal("CustomerAlternateKey", ownership.Properties.Single().Name);
832-
Assert.Equal(nameof(Customer.AlternateKey), ownership.PrincipalKey.Properties.Single().Name);
833-
var owned = ownership.DeclaringEntityType;
834-
Assert.Same(ownedBuilder.OwnedEntityType, owned);
835-
Assert.Equal("CustomerDetails", owned.GetTableName());
836-
var checkConstraint = owned.GetCheckConstraints().Single();
837-
Assert.Equal("CK_CustomerDetails_T", checkConstraint.ModelName);
838-
Assert.Equal("AlternateKey <> 0", checkConstraint.Sql);
839-
Assert.Equal("CK_Guid", checkConstraint.Name);
840-
Assert.Single(owned.GetForeignKeys());
841-
Assert.Equal(nameof(CustomerDetails.CustomerId), owned.GetIndexes().Single().Properties.Single().Name);
842-
Assert.Equal(
843-
new[] { "CustomerAlternateKey", nameof(CustomerDetails.CustomerId), nameof(CustomerDetails.Id) },
844-
owned.GetProperties().Select(p => p.Name));
845-
Assert.NotNull(model.FindEntityType(typeof(CustomerDetails)));
846-
Assert.Equal(1, model.GetEntityTypes().Count(e => e.ClrType == typeof(CustomerDetails)));
839+
var owner1 = model.FindEntityType(typeof(OtherCustomer));
840+
Assert.Equal(typeof(OtherCustomer).FullName, owner1.Name);
841+
AssertOwnership(owner1);
842+
843+
var owner2 = model.FindEntityType(typeof(SpecialCustomer));
844+
Assert.Equal(typeof(SpecialCustomer).FullName, owner2.Name);
845+
AssertOwnership(owner2);
846+
847+
Assert.Null(model.FindEntityType(typeof(CustomerDetails)));
848+
Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(CustomerDetails)));
849+
850+
static void AssertOwnership(IEntityType owner)
851+
{
852+
var ownership1 = owner.FindNavigation(nameof(Customer.Details)).ForeignKey;
853+
Assert.True(ownership1.IsOwnership);
854+
Assert.Equal(nameof(Customer.Details), ownership1.PrincipalToDependent.Name);
855+
Assert.Equal("CustomerAlternateKey", ownership1.Properties.Single().Name);
856+
Assert.Equal(nameof(Customer.AlternateKey), ownership1.PrincipalKey.Properties.Single().Name);
857+
var owned = ownership1.DeclaringEntityType;
858+
Assert.Equal(owner.ShortName() + "Details", owned.GetTableName());
859+
var checkConstraint = owned.GetCheckConstraints().Single();
860+
Assert.Same(owned, checkConstraint.EntityType);
861+
Assert.Equal("CK_CustomerDetails_T", checkConstraint.ModelName);
862+
Assert.Equal("AlternateKey <> 0", checkConstraint.Sql);
863+
Assert.Equal("CK_Guid", checkConstraint.Name);
864+
Assert.Single(owned.GetForeignKeys());
865+
var index = owned.GetIndexes().Single();
866+
Assert.Same(owned, index.DeclaringEntityType);
867+
Assert.Equal(nameof(CustomerDetails.CustomerId), index.Properties.Single().Name);
868+
Assert.Equal(
869+
new[] { "CustomerAlternateKey", nameof(CustomerDetails.CustomerId), nameof(CustomerDetails.Id) },
870+
owned.GetProperties().Select(p => p.Name));
871+
}
847872
}
848873

849874
[ConditionalFact]

0 commit comments

Comments
 (0)