diff --git a/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs index f636e420da1..11eb1322aee 100644 --- a/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs @@ -67,7 +67,7 @@ public static string GetDefaultName([NotNull] this IForeignKey foreignKey) .Append("_") .Append(principalTableName) .Append("_") - .AppendJoin(foreignKey.Properties.Select(p => p.GetColumnName(tableName, schema)), "_") + .AppendJoin(foreignKey.Properties.Select(p => p.GetColumnName()), "_") .ToString(); return Uniquifier.Truncate(name, foreignKey.DeclaringEntityType.Model.GetMaxIdentifierLength()); diff --git a/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs b/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs index 8a1c4256e8c..6216b7c4152 100644 --- a/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs @@ -65,7 +65,7 @@ public static string GetDefaultDatabaseName([NotNull] this IIndex index) .Append("IX_") .Append(tableName) .Append("_") - .AppendJoin(index.Properties.Select(p => p.GetColumnName(tableName, schema)), "_") + .AppendJoin(index.Properties.Select(p => p.GetColumnName()), "_") .ToString(); return Uniquifier.Truncate(baseName, index.DeclaringEntityType.Model.GetMaxIdentifierLength()); diff --git a/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs index 9a64befc26f..a2d6b6af795 100644 --- a/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs @@ -59,7 +59,7 @@ public static string GetDefaultName([NotNull] this IKey key) .Append("AK_") .Append(tableName) .Append("_") - .AppendJoin(key.Properties.Select(p => p.GetColumnName(tableName, key.DeclaringEntityType.GetSchema())), "_") + .AppendJoin(key.Properties.Select(p => p.GetColumnName()), "_") .ToString(); } diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index 6c8d5ab724d..f0562bf9db7 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -222,12 +222,29 @@ protected virtual void ValidateSharedTableCompatibility( IEntityType root = null; foreach (var mappedType in mappedTypes) { - if ((mappedType.BaseType != null && unvalidatedTypes.Contains(mappedType.BaseType)) - || (mappedType.FindPrimaryKey() != null + if (mappedType.BaseType != null && unvalidatedTypes.Contains(mappedType.BaseType)) + { + continue; + } + + if (mappedType.FindPrimaryKey() != null && mappedType.FindForeignKeys(mappedType.FindPrimaryKey().Properties) .Any(fk => fk.PrincipalKey.IsPrimaryKey() - && unvalidatedTypes.Contains(fk.PrincipalEntityType)))) + && unvalidatedTypes.Contains(fk.PrincipalEntityType))) { + if (mappedType.BaseType != null) + { + var principalType = mappedType.FindForeignKeys(mappedType.FindPrimaryKey().Properties) + .First(fk => fk.PrincipalKey.IsPrimaryKey() + && unvalidatedTypes.Contains(fk.PrincipalEntityType)) + .PrincipalEntityType; + throw new InvalidOperationException( + RelationalStrings.IncompatibleTableDerivedRelationship( + Format(tableName, schema), + mappedType.DisplayName(), + principalType.DisplayName())); + } + continue; } @@ -352,7 +369,13 @@ protected virtual void ValidateSharedColumnsCompatibility( storeConcurrencyTokens = new Dictionary(); } - storeConcurrencyTokens[property.GetColumnName(tableName, schema)] = property; + var columnName = property.GetColumnName(tableName, schema); + if (columnName == null) + { + continue; + } + + storeConcurrencyTokens[columnName] = property; if (missingConcurrencyTokens == null) { missingConcurrencyTokens = new HashSet(); diff --git a/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs b/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs index 06a96f1615b..77fbb42f1b6 100644 --- a/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/SharedTableConvention.cs @@ -159,6 +159,11 @@ private static void TryUniquifyColumnNames( foreach (var property in entityType.GetDeclaredProperties()) { var columnName = property.GetColumnName(tableName, schema); + if (columnName == null) + { + continue; + } + if (!properties.TryGetValue(columnName, out var otherProperty)) { properties[columnName] = property; diff --git a/src/EFCore.Relational/Metadata/Conventions/TableSharingConcurrencyTokenConvention.cs b/src/EFCore.Relational/Metadata/Conventions/TableSharingConcurrencyTokenConvention.cs index ae0cc06717b..105f710d63f 100644 --- a/src/EFCore.Relational/Metadata/Conventions/TableSharingConcurrencyTokenConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/TableSharingConcurrencyTokenConvention.cs @@ -151,6 +151,11 @@ private void GetMappings(IConventionModel model, } var columnName = property.GetColumnName(tableName, table.Schema); + if (columnName == null) + { + continue; + } + if (!columnToProperties.TryGetValue(columnName, out var properties)) { properties = new List(); diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalForeignKeyExtensions.cs b/src/EFCore.Relational/Metadata/Internal/RelationalForeignKeyExtensions.cs index 0031c3c23db..3d3423ff404 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalForeignKeyExtensions.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalForeignKeyExtensions.cs @@ -74,11 +74,8 @@ public static bool AreCompatible( return false; } - if (!foreignKey.PrincipalKey.Properties - .Select(p => p.GetColumnName(tableName, schema)) - .SequenceEqual( - duplicateForeignKey.PrincipalKey.Properties - .Select(p => p.GetColumnName(tableName, schema)))) + if (!foreignKey.PrincipalKey.Properties.Select(p => p.GetColumnName(tableName, schema)) + .SequenceEqual(duplicateForeignKey.PrincipalKey.Properties.Select(p => p.GetColumnName(tableName, schema)))) { if (shouldThrow) { diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index bfa56d43f12..276aa4b2605 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -865,6 +865,14 @@ public static string PropertyNotMappedToTable([CanBeNull] object property, [CanB GetString("PropertyNotMappedToTable", nameof(property), nameof(entityType), nameof(table)), property, entityType, table); + /// + /// Cannot use table '{table}' for entity type '{entityType}' since it is being used for entity type '{otherEntityType}', there is a relationship between their primary keys in which '{entityType}' is the dependent and '{entityType}' has a base entity type mapped to a different table. Either map '{otherEntityType}' to a different table or invert the relationship between '{entityType}' and '{otherEntityType}'. + /// + public static string IncompatibleTableDerivedRelationship([CanBeNull] object table, [CanBeNull] object entityType, [CanBeNull] object otherEntityType) + => string.Format( + GetString("IncompatibleTableDerivedRelationship", nameof(table), nameof(entityType), nameof(otherEntityType)), + table, entityType, otherEntityType); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); @@ -1252,7 +1260,7 @@ public static EventDefinition LogCreatedTransactionSavepoint([NotNull] IDiagnost } /// - /// Rolling back to transaction savepoint. + /// Rolling back to transaction savepoint.. /// public static EventDefinition LogRollingBackToTransactionSavepoint([NotNull] IDiagnosticsLogger logger) { @@ -1276,7 +1284,7 @@ public static EventDefinition LogRollingBackToTransactionSavepoint([NotNull] IDi } /// - /// Rolled back to transaction savepoint. + /// Rolled back to transaction savepoint.. /// public static EventDefinition LogRolledBackToTransactionSavepoint([NotNull] IDiagnosticsLogger logger) { diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index bd038924836..b5d3f241ea2 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -654,4 +654,7 @@ The unnamed index on the entity type '{entityType}' specifies properties {indexPropertiesList}. The property '{propertyName1}' is mapped to table(s) {tableList1}, whereas the property '{propertyName2}' is mapped to table(s) {tableList2}. All index properties must map to at least one common table. Error RelationalEventId.IndexPropertiesMappedToNonOverlappingTables string string string string string string + + Cannot use table '{table}' for entity type '{entityType}' since it is being used for entity type '{otherEntityType}', there is a relationship between their primary keys in which '{entityType}' is the dependent and '{entityType}' has a base entity type mapped to a different table. Either map '{otherEntityType}' to a different table or invert the relationship between '{entityType}' and '{otherEntityType}'. + \ No newline at end of file diff --git a/src/EFCore.SqlServer/Internal/SqlServerModelValidator.cs b/src/EFCore.SqlServer/Internal/SqlServerModelValidator.cs index 06973386e3c..dd51991b2ef 100644 --- a/src/EFCore.SqlServer/Internal/SqlServerModelValidator.cs +++ b/src/EFCore.SqlServer/Internal/SqlServerModelValidator.cs @@ -237,7 +237,13 @@ protected override void ValidateSharedColumnsCompatibility( { if (property.GetValueGenerationStrategy(tableName, schema) == SqlServerValueGenerationStrategy.IdentityColumn) { - identityColumns[property.GetColumnName(tableName, schema)] = property; + var columnName = property.GetColumnName(tableName, schema); + if (columnName == null) + { + continue; + } + + identityColumns[columnName] = property; } } diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerIndexConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerIndexConvention.cs index 4eb75d01a4f..7924309408a 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerIndexConvention.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerIndexConvention.cs @@ -179,6 +179,7 @@ private string CreateIndexFilter(IIndex index) { return null; } + var schema = index.DeclaringEntityType.GetSchema(); var nullableColumns = index.Properties diff --git a/src/EFCore.SqlServer/Metadata/Internal/SqlServerIndexExtensions.cs b/src/EFCore.SqlServer/Metadata/Internal/SqlServerIndexExtensions.cs index 70bdd2ad2d8..5b06b0109f9 100644 --- a/src/EFCore.SqlServer/Metadata/Internal/SqlServerIndexExtensions.cs +++ b/src/EFCore.SqlServer/Metadata/Internal/SqlServerIndexExtensions.cs @@ -34,9 +34,11 @@ public static bool AreCompatibleForSqlServer( if (index.GetIncludeProperties() == null || duplicateIndex.GetIncludeProperties() == null || !index.GetIncludeProperties().Select( - p => index.DeclaringEntityType.FindProperty(p).GetColumnName(tableName, schema)).SequenceEqual( - duplicateIndex.GetIncludeProperties().Select( - p => duplicateIndex.DeclaringEntityType.FindProperty(p).GetColumnName(tableName, schema)))) { + p => index.DeclaringEntityType.FindProperty(p).GetColumnName(tableName, schema)) + .SequenceEqual( + duplicateIndex.GetIncludeProperties().Select( + p => duplicateIndex.DeclaringEntityType.FindProperty(p).GetColumnName(tableName, schema)))) + { if (shouldThrow) { throw new InvalidOperationException( diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index 98721d029e9..70b495924d4 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -1169,6 +1169,49 @@ public virtual void Detects_view_TPT_with_discriminator() modelBuilder.Model); } + [ConditionalFact] + public virtual void Passes_on_valid_table_sharing_with_TPT() + { + var modelBuilder = CreateConventionalModelBuilder(); + + modelBuilder.Entity() + .Ignore(a => a.FavoritePerson); + + modelBuilder.Entity( + x => + { + x.ToTable("Cat"); + x.HasOne(c => c.FavoritePerson).WithOne().HasForeignKey(c => c.Id); + }); + + modelBuilder.Entity().ToTable("Cat"); + + Validate(modelBuilder.Model); + } + + [ConditionalFact] + public virtual void Detects_linking_relationship_on_derived_type_in_TPT() + { + var modelBuilder = CreateConventionalModelBuilder(); + + modelBuilder.Entity() + .Ignore(a => a.FavoritePerson); + + modelBuilder.Entity( + x => + { + x.ToTable("Cat"); + x.HasOne(c => c.FavoritePerson).WithOne().HasForeignKey(c => c.Id); + }); + + modelBuilder.Entity().ToTable("Cat"); + + VerifyError( + RelationalStrings.IncompatibleTableDerivedRelationship( + "Cat", "Cat", "Person"), + modelBuilder.Model); + } + [ConditionalFact] public virtual void Passes_for_valid_table_overrides() { diff --git a/test/EFCore.Relational.Tests/Metadata/Conventions/TableSharingConcurrencyTokenConventionTest.cs b/test/EFCore.Relational.Tests/Metadata/Conventions/TableSharingConcurrencyTokenConventionTest.cs index ea8969d81e4..5c09e9ede4e 100644 --- a/test/EFCore.Relational.Tests/Metadata/Conventions/TableSharingConcurrencyTokenConventionTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/Conventions/TableSharingConcurrencyTokenConventionTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; @@ -36,6 +37,23 @@ public virtual void Missing_concurrency_token_property_is_created_on_the_base_ty Assert.Equal(ValueGenerated.OnAddOrUpdate, concurrencyProperty.ValueGenerated); } + [ConditionalFact] + public virtual void Missing_concurrency_token_property_is_not_created_for_TPT() + { + var modelBuilder = GetModelBuilder(); + modelBuilder.Entity().Ignore(a => a.FavoritePerson) + .Property("Version").IsRowVersion().HasColumnName("Version"); + modelBuilder.Entity().HasOne(a => a.FavoritePerson).WithOne().HasForeignKey(p => p.Id); + modelBuilder.Entity().ToTable(nameof(Cat)); + modelBuilder.Entity().ToTable(nameof(Cat)); + + var model = modelBuilder.Model; + model.FinalizeModel(); + + var person = model.FindEntityType(typeof(Person)); + Assert.DoesNotContain(person.GetProperties(), p => p.IsConcurrencyToken); + } + [ConditionalFact] public virtual void Missing_concurrency_token_properties_are_created_on_the_base_most_types() { diff --git a/test/EFCore.SqlServer.FunctionalTests/MigrationsInfrastructureSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/MigrationsInfrastructureSqlServerTest.cs index 395b24fbc5d..8c4637186ad 100644 --- a/test/EFCore.SqlServer.FunctionalTests/MigrationsInfrastructureSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/MigrationsInfrastructureSqlServerTest.cs @@ -498,7 +498,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("NormalizedName") .IsUnique() - .HasName("RoleNameIndex") + .HasName("RoleNameIndex") // Don't change to HasDatabaseName .HasFilter("[NormalizedName] IS NOT NULL"); b.ToTable("AspNetRoles");