From c168e4f4b2866415c09d95d2587555f5b4d78671 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Thu, 21 Apr 2022 12:13:44 -0700 Subject: [PATCH] Return null constraint names for keys and FKs on entity types not mapped to a table. Fixes #27854 --- .../RelationalForeignKeyExtensions.cs | 18 +++++ .../Extensions/RelationalKeyExtensions.cs | 19 ++++- .../RelationalTestModelBuilderExtensions.cs | 76 ++++++++++++++++++ .../SqlServerModelBuilderGenericTest.cs | 78 +++++++++++++++++++ 4 files changed, 189 insertions(+), 2 deletions(-) diff --git a/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs index 9b258e3fc4c..68c9ed47297 100644 --- a/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs @@ -26,6 +26,12 @@ public static class RelationalForeignKeyExtensions /// The foreign key constraint name. public static string? GetConstraintName(this IReadOnlyForeignKey foreignKey) { + var tableName = foreignKey.DeclaringEntityType.GetTableName(); + if (tableName == null) + { + return null; + } + var annotation = foreignKey.FindAnnotation(RelationalAnnotationNames.Name); return annotation != null ? (string?)annotation.Value @@ -44,6 +50,12 @@ public static class RelationalForeignKeyExtensions in StoreObjectIdentifier storeObject, in StoreObjectIdentifier principalStoreObject) { + if (storeObject.StoreObjectType != StoreObjectType.Table + || principalStoreObject.StoreObjectType != StoreObjectType.Table) + { + return null; + } + var annotation = foreignKey.FindAnnotation(RelationalAnnotationNames.Name); return annotation != null ? (string?)annotation.Value @@ -85,6 +97,12 @@ public static string GetDefaultName(this IReadOnlyForeignKey foreignKey) in StoreObjectIdentifier storeObject, in StoreObjectIdentifier principalStoreObject) { + if (storeObject.StoreObjectType != StoreObjectType.Table + || principalStoreObject.StoreObjectType != StoreObjectType.Table) + { + return null; + } + var propertyNames = foreignKey.Properties.GetColumnNames(storeObject); var principalPropertyNames = foreignKey.PrincipalKey.Properties.GetColumnNames(principalStoreObject); if (propertyNames == null diff --git a/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs index bddee9d0b1a..5eab8584ec3 100644 --- a/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs @@ -25,7 +25,10 @@ public static class RelationalKeyExtensions /// The key. /// The key constraint name for this key. public static string? GetName(this IReadOnlyKey key) - => key.GetName(StoreObjectIdentifier.Table(key.DeclaringEntityType.GetTableName()!, key.DeclaringEntityType.GetSchema())); + { + var table = StoreObjectIdentifier.Create(key.DeclaringEntityType, StoreObjectType.Table); + return !table.HasValue ? null : key.GetName(table.Value); + } /// /// Returns the key constraint name for this key for a particular table. @@ -34,8 +37,15 @@ public static class RelationalKeyExtensions /// The identifier of the containing store object. /// The key constraint name for this key. public static string? GetName(this IReadOnlyKey key, in StoreObjectIdentifier storeObject) - => (string?)key[RelationalAnnotationNames.Name] + { + if (storeObject.StoreObjectType != StoreObjectType.Table) + { + return null; + } + + return (string?)key[RelationalAnnotationNames.Name] ?? key.GetDefaultName(storeObject); + } /// /// Returns the default key constraint name that would be used for this key. @@ -65,6 +75,11 @@ public static string GetDefaultName(this IReadOnlyKey key) /// The default key constraint name that would be used for this key. public static string? GetDefaultName(this IReadOnlyKey key, in StoreObjectIdentifier storeObject) { + if (storeObject.StoreObjectType != StoreObjectType.Table) + { + return null; + } + string? name; if (key.IsPrimaryKey()) { diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs index e5550344414..8d8580503b6 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs @@ -277,6 +277,82 @@ public static ModelBuilderTest.TestOwnedNavigationBuilder ToView( + this ModelBuilderTest.TestEntityTypeBuilder builder, + string? schema) + where TEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.ToView(schema); + break; + case IInfrastructure nongenericBuilder: + nongenericBuilder.Instance.ToView(schema); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestEntityTypeBuilder ToView( + this ModelBuilderTest.TestEntityTypeBuilder builder, + string? name, + string? schema) + where TEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.ToView(name, schema); + break; + case IInfrastructure nongenericBuilder: + nongenericBuilder.Instance.ToView(name, schema); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestOwnedNavigationBuilder ToView( + this ModelBuilderTest.TestOwnedNavigationBuilder builder, + string? schema) + where TOwnerEntity : class + where TRelatedEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.ToView(schema); + break; + case IInfrastructure nongenericBuilder: + nongenericBuilder.Instance.ToView(schema); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestOwnedNavigationBuilder ToView( + this ModelBuilderTest.TestOwnedNavigationBuilder builder, + string? name, + string? schema) + where TOwnerEntity : class + where TRelatedEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.ToView(name, schema); + break; + case IInfrastructure nongenericBuilder: + nongenericBuilder.Instance.ToView(name, schema); + break; + } + + return builder; + } + public static ModelBuilderTest.TestEntityTypeBuilder HasCheckConstraint( this ModelBuilderTest.TestEntityTypeBuilder builder, string name, diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs index 633c22fa350..ce901bbb878 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs @@ -836,6 +836,84 @@ public virtual void Owned_type_collections_can_be_mapped_to_different_tables() Assert.Equal("blah", owned.GetTableName()); Assert.Null(owned.GetSchema()); } + + [ConditionalFact] + public virtual void Owned_type_collections_can_be_mapped_to_a_view() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().OwnsMany( + c => c.Orders, + r => + { + r.HasKey(o => o.OrderId); + r.Ignore(o => o.OrderCombination); + r.Ignore(o => o.Details); + r.ToView("bar", "foo"); + }); + + var model = modelBuilder.FinalizeModel(); + + var owner = model.FindEntityType(typeof(Customer)); + var ownership = owner.FindNavigation(nameof(Customer.Orders)).ForeignKey; + var owned = ownership.DeclaringEntityType; + Assert.True(ownership.IsOwnership); + Assert.Equal(nameof(Order.Customer), ownership.DependentToPrincipal.Name); + Assert.Empty(ownership.GetMappedConstraints()); + + Assert.Equal(nameof(Customer), owner.GetTableName()); + Assert.Null(owner.GetSchema()); + + Assert.Null(owned.GetForeignKeys().Single().GetConstraintName()); + Assert.Single(owned.GetIndexes()); + Assert.Null(owned.FindPrimaryKey().GetName()); + Assert.Equal( + new[] { nameof(Order.OrderId), nameof(Order.AnotherCustomerId), nameof(Order.CustomerId) }, + owned.GetProperties().Select(p => p.GetColumnBaseName())); + Assert.Null(owned.GetTableName()); + Assert.Null(owned.GetSchema()); + Assert.Equal("bar", owned.GetViewName()); + Assert.Equal("foo", owned.GetViewSchema()); + } + + [ConditionalFact] + public virtual void Owner_can_be_mapped_to_a_view() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().OwnsMany( + c => c.Orders, + r => + { + r.HasKey(o => o.OrderId); + r.Ignore(o => o.OrderCombination); + r.Ignore(o => o.Details); + }) + .ToView("bar", "foo"); + + var model = modelBuilder.FinalizeModel(); + + var owner = model.FindEntityType(typeof(Customer)); + var ownership = owner.FindNavigation(nameof(Customer.Orders)).ForeignKey; + var owned = ownership.DeclaringEntityType; + Assert.True(ownership.IsOwnership); + Assert.Equal(nameof(Order.Customer), ownership.DependentToPrincipal.Name); + Assert.Empty(ownership.GetMappedConstraints()); + + Assert.Null(owner.GetTableName()); + Assert.Null(owner.GetSchema()); + Assert.Equal("bar", owner.GetViewName()); + Assert.Equal("foo", owner.GetViewSchema()); + + Assert.Equal("FK_Order__CustomerId", owned.GetForeignKeys().Single().GetConstraintName()); + Assert.Equal("IX_Order_CustomerId", owned.GetIndexes().Single().GetDatabaseName()); + Assert.Equal("PK_Order", owned.FindPrimaryKey().GetName()); + Assert.Equal( + new[] { nameof(Order.OrderId), nameof(Order.AnotherCustomerId), nameof(Order.CustomerId) }, + owned.GetProperties().Select(p => p.GetColumnBaseName())); + Assert.Equal(nameof(Order), owned.GetTableName()); + Assert.Null(owned.GetSchema()); + } [ConditionalFact] public override void Can_configure_owned_type()