From ab2bfacc1b49b6b12696510621adf0fd150a76c2 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Thu, 17 Mar 2022 15:20:05 -0700 Subject: [PATCH] Add TPC support for migrations Part of #3170 --- .../CSharpMigrationOperationGenerator.cs | 2 + .../Design/CSharpSnapshotGenerator.cs | 32 +- .../Design/AnnotationCodeGenerator.cs | 34 + .../Diagnostics/RelationalEventId.cs | 6 +- .../Diagnostics/RelationalLoggerExtensions.cs | 6 +- .../RelationalLoggingDefinitions.cs | 2 +- .../RelationalQueryableExtensions.cs | 2 +- .../RelationalModelValidator.cs | 14 +- .../Properties/RelationalStrings.Designer.cs | 44 +- .../Properties/RelationalStrings.resx | 20 +- .../Query/SqlExpressions/SelectExpression.cs | 2 +- .../SqlServerDbContextOptionsBuilder.cs | 2 +- .../SqlServerRetryingExecutionStrategy.cs | 12 +- .../Design/CSharpMigrationsGeneratorTest.cs | 10 +- .../Migrations/ModelSnapshotSqlServerTest.cs | 57 +- .../Query/TPTInheritanceQueryTestBase.cs | 4 +- .../RelationalModelValidatorTest.cs | 22 +- .../Metadata/RelationalModelTest.cs | 44 +- .../Internal/MigrationsModelDifferTest.cs | 1344 +++++++++++++++++ 19 files changed, 1557 insertions(+), 102 deletions(-) diff --git a/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs index b8aa23b7459..f7f702e97c0 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpMigrationOperationGenerator.cs @@ -2135,6 +2135,7 @@ protected virtual void Annotations( foreach (var annotation in annotations) { // TODO: Give providers an opportunity to render these as provider-specific extension methods + // Issue #6546 builder .AppendLine() .Append(".Annotation(") @@ -2157,6 +2158,7 @@ protected virtual void OldAnnotations( foreach (var annotation in annotations) { // TODO: Give providers an opportunity to render these as provider-specific extension methods + // Issue #6546 builder .AppendLine() .Append(".OldAnnotation(") diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index a7163f24519..b9dcc05a7a7 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -675,19 +675,32 @@ protected virtual void GenerateEntityTypeAnnotations( IEntityType entityType, IndentedStringBuilder stringBuilder) { - var annotationList = entityType.GetAnnotations().ToList(); + IAnnotation? discriminatorPropertyAnnotation = null; + IAnnotation? discriminatorValueAnnotation = null; + IAnnotation? discriminatorMappingCompleteAnnotation = null; - var discriminatorPropertyAnnotation = annotationList.FirstOrDefault(a => a.Name == CoreAnnotationNames.DiscriminatorProperty); - var discriminatorMappingCompleteAnnotation = - annotationList.FirstOrDefault(a => a.Name == CoreAnnotationNames.DiscriminatorMappingComplete); - var discriminatorValueAnnotation = annotationList.FirstOrDefault(a => a.Name == CoreAnnotationNames.DiscriminatorValue); + foreach (var annotation in entityType.GetAnnotations()) + { + switch (annotation.Name) + { + case CoreAnnotationNames.DiscriminatorProperty: + discriminatorPropertyAnnotation = annotation; + break; + case CoreAnnotationNames.DiscriminatorValue: + discriminatorValueAnnotation = annotation; + break; + case CoreAnnotationNames.DiscriminatorMappingComplete: + discriminatorMappingCompleteAnnotation = annotation; + break; + } + } var annotations = Dependencies.AnnotationCodeGenerator .FilterIgnoredAnnotations(entityType.GetAnnotations()) .ToDictionary(a => a.Name, a => a); var tableNameAnnotation = annotations.Find(RelationalAnnotationNames.TableName); - if (tableNameAnnotation?.Value != null + if (tableNameAnnotation != null || entityType.BaseType == null) { var tableName = (string?)tableNameAnnotation?.Value ?? entityType.GetTableName(); @@ -773,7 +786,7 @@ protected virtual void GenerateEntityTypeAnnotations( annotations.Remove(RelationalAnnotationNames.Schema); var viewNameAnnotation = annotations.Find(RelationalAnnotationNames.ViewName); - if (viewNameAnnotation?.Value != null + if (viewNameAnnotation != null || entityType.BaseType == null) { var viewName = (string?)viewNameAnnotation?.Value ?? entityType.GetViewName(); @@ -796,7 +809,6 @@ protected virtual void GenerateEntityTypeAnnotations( stringBuilder .Append(", ") .Append(Code.Literal((string)viewSchemaAnnotation.Value)); - annotations.Remove(viewSchemaAnnotation.Name); } stringBuilder.AppendLine(");"); @@ -807,7 +819,7 @@ protected virtual void GenerateEntityTypeAnnotations( annotations.Remove(RelationalAnnotationNames.ViewDefinitionSql); var functionNameAnnotation = annotations.Find(RelationalAnnotationNames.FunctionName); - if (functionNameAnnotation?.Value != null + if (functionNameAnnotation != null || entityType.BaseType == null) { var functionName = (string?)functionNameAnnotation?.Value ?? entityType.GetFunctionName(); @@ -828,7 +840,7 @@ protected virtual void GenerateEntityTypeAnnotations( } var sqlQueryAnnotation = annotations.Find(RelationalAnnotationNames.SqlQuery); - if (sqlQueryAnnotation?.Value != null + if (sqlQueryAnnotation != null || entityType.BaseType == null) { var sqlQuery = (string?)sqlQueryAnnotation?.Value ?? entityType.GetSqlQuery(); diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index 7b23e8c26d5..1094fa08b02 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -48,6 +48,18 @@ private static readonly MethodInfo EntityTypeHasCommentMethodInfo = typeof(RelationalEntityTypeBuilderExtensions).GetRuntimeMethod( nameof(RelationalEntityTypeBuilderExtensions.HasComment), new[] { typeof(EntityTypeBuilder), typeof(string) })!; + private static readonly MethodInfo EntityTypeUseTpcMappingStrategyMethodInfo + = typeof(RelationalEntityTypeBuilderExtensions).GetRuntimeMethod( + nameof(RelationalEntityTypeBuilderExtensions.UseTpcMappingStrategy), new[] { typeof(EntityTypeBuilder) })!; + + private static readonly MethodInfo EntityTypeUseTphMappingStrategyMethodInfo + = typeof(RelationalEntityTypeBuilderExtensions).GetRuntimeMethod( + nameof(RelationalEntityTypeBuilderExtensions.UseTphMappingStrategy), new[] { typeof(EntityTypeBuilder) })!; + + private static readonly MethodInfo EntityTypeUseTptMappingStrategyMethodInfo + = typeof(RelationalEntityTypeBuilderExtensions).GetRuntimeMethod( + nameof(RelationalEntityTypeBuilderExtensions.UseTptMappingStrategy), new[] { typeof(EntityTypeBuilder) })!; + private static readonly MethodInfo PropertyHasColumnNameMethodInfo = typeof(RelationalPropertyBuilderExtensions).GetRuntimeMethod( nameof(RelationalPropertyBuilderExtensions.HasColumnName), new[] { typeof(PropertyBuilder), typeof(string) })!; @@ -204,6 +216,28 @@ public virtual IReadOnlyList GenerateFluentApiCalls( annotations, RelationalAnnotationNames.Comment, EntityTypeHasCommentMethodInfo, methodCallCodeFragments); + if (annotations.TryGetValue(RelationalAnnotationNames.MappingStrategy, out var mappingStrategyAnnotation) + && mappingStrategyAnnotation.Value is string mappingStrategy) + { + var strategyCall = mappingStrategy switch + { + RelationalAnnotationNames.TpcMappingStrategy => EntityTypeUseTpcMappingStrategyMethodInfo, + RelationalAnnotationNames.TptMappingStrategy => EntityTypeUseTptMappingStrategyMethodInfo, + RelationalAnnotationNames.TphMappingStrategy => EntityTypeUseTphMappingStrategyMethodInfo, + _ => null + }; + + if (strategyCall != null) + { + if (entityType.BaseType == null) + { + methodCallCodeFragments.Add(new MethodCallCodeFragment(strategyCall)); + } + + annotations.Remove(mappingStrategyAnnotation.Name); + } + } + methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(entityType, annotations, GenerateFluentApi)); return methodCallCodeFragments; diff --git a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs index 68c19dcd735..a25572553c6 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalEventId.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalEventId.cs @@ -89,7 +89,7 @@ private enum Id ForeignKeyPropertiesMappedToUnrelatedTables, OptionalDependentWithoutIdentifyingPropertyWarning, DuplicateColumnOrders, - ForeignKeyTPCPrincipalWarning, + ForeignKeyTpcPrincipalWarning, TpcStoreGeneratedIdentityWarning, // Update events @@ -752,8 +752,8 @@ private static EventId MakeValidationId(Id id) /// This event uses the payload when used with a . /// /// - public static readonly EventId ForeignKeyTPCPrincipalWarning = - MakeValidationId(Id.ForeignKeyTPCPrincipalWarning); + public static readonly EventId ForeignKeyTpcPrincipalWarning = + MakeValidationId(Id.ForeignKeyTpcPrincipalWarning); /// /// The PK is using store-generated values in TPC. diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs index 15dc04155a9..82492b22aa6 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalLoggerExtensions.cs @@ -2832,15 +2832,15 @@ private static string ForeignKeyPropertiesMappedToUnrelatedTables(EventDefinitio } /// - /// Logs the event. + /// Logs the event. /// /// The diagnostics logger to use. /// The foreign key. - public static void ForeignKeyTPCPrincipalWarning( + public static void ForeignKeyTpcPrincipalWarning( this IDiagnosticsLogger diagnostics, IForeignKey foreignKey) { - var definition = RelationalResources.LogForeignKeyTPCPrincipal(diagnostics); + var definition = RelationalResources.LogForeignKeyTpcPrincipal(diagnostics); if (diagnostics.ShouldLog(definition)) { diff --git a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs index b2e97e7afb1..a1a9a69a164 100644 --- a/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs +++ b/src/EFCore.Relational/Diagnostics/RelationalLoggingDefinitions.cs @@ -491,7 +491,7 @@ public abstract class RelationalLoggingDefinitions : LoggingDefinitions /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public EventDefinitionBase? LogForeignKeyTPCPrincipal; + public EventDefinitionBase? LogForeignKeyTpcPrincipal; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs b/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs index 03670e2e7de..152562d7c78 100644 --- a/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalQueryableExtensions.cs @@ -152,7 +152,7 @@ private static FromSqlQueryRootExpression GenerateFromSqlQueryRoot( if ((entityType.BaseType != null || entityType.GetDirectlyDerivedTypes().Any()) && entityType.FindDiscriminatorProperty() == null) { - throw new InvalidOperationException(RelationalStrings.MethodOnNonTPHRootNotSupported(memberName, entityType.DisplayName())); + throw new InvalidOperationException(RelationalStrings.MethodOnNonTphRootNotSupported(memberName, entityType.DisplayName())); } return new FromSqlQueryRootExpression( diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index b1066abdcfe..848021babc3 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -134,7 +134,7 @@ protected virtual void ValidateDbFunctions( && entityType.FindDiscriminatorProperty() == null) { throw new InvalidOperationException( - RelationalStrings.TableValuedFunctionNonTPH(dbFunction.ModelName, entityType.DisplayName())); + RelationalStrings.TableValuedFunctionNonTph(dbFunction.ModelName, entityType.DisplayName())); } } @@ -1056,7 +1056,7 @@ protected virtual void ValidateSharedForeignKeysCompatibility( { if (foreignKey.PrincipalEntityType.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy) { - logger.ForeignKeyTPCPrincipalWarning(foreignKey); + logger.ForeignKeyTpcPrincipalWarning(foreignKey); } var derivedTables = foreignKey.DeclaringEntityType.GetDerivedTypes() @@ -1309,7 +1309,7 @@ protected override void ValidateInheritanceMapping( && storeObject != null) { throw new InvalidOperationException( - RelationalStrings.AbstractTPC(entityType.DisplayName(), storeObject)); + RelationalStrings.AbstractTpc(entityType.DisplayName(), storeObject)); } } @@ -1403,9 +1403,9 @@ private static void ValidateNonTPHMapping(IEntityType rootEntityType, bool forTa { throw new InvalidOperationException( forTables - ? RelationalStrings.NonTPHTableClash( + ? RelationalStrings.NonTphTableClash( entityType.DisplayName(), otherType.DisplayName(), entityType.GetSchemaQualifiedTableName()) - : RelationalStrings.NonTPHViewClash( + : RelationalStrings.NonTphViewClash( entityType.DisplayName(), otherType.DisplayName(), entityType.GetSchemaQualifiedViewName())); } @@ -1439,10 +1439,10 @@ private static void ValidateTPHMapping(IEntityType rootEntityType, bool forTable { throw new InvalidOperationException( forTables - ? RelationalStrings.TPHTableMismatch( + ? RelationalStrings.TphTableMismatch( entityType.DisplayName(), entityType.GetSchemaQualifiedTableName(), firstType.DisplayName(), firstType.GetSchemaQualifiedTableName()) - : RelationalStrings.TPHViewMismatch( + : RelationalStrings.TphViewMismatch( entityType.DisplayName(), entityType.GetSchemaQualifiedViewName(), firstType.DisplayName(), firstType.GetSchemaQualifiedViewName())); } diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index b3727c74382..dfc6f727e99 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -29,9 +29,9 @@ private static readonly ResourceManager _resourceManager /// /// The corresponding CLR type for entity type '{entityType}' cannot be instantiated, but the entity type was mapped to '{storeObject}' using the 'TPC' mapping strategy. Only instantiable types should be mapped. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. /// - public static string AbstractTPC(object? entityType, object? storeObject) + public static string AbstractTpc(object? entityType, object? storeObject) => string.Format( - GetString("AbstractTPC", nameof(entityType), nameof(storeObject)), + GetString("AbstractTpc", nameof(entityType), nameof(storeObject)), entityType, storeObject); /// @@ -834,9 +834,9 @@ public static string MappedFunctionNotFound(object? entityType, object? function /// /// Using '{methodName}' on DbSet of '{entityType}' is not supported since '{entityType}' is part of hierarchy and does not contain a discriminator property. /// - public static string MethodOnNonTPHRootNotSupported(object? methodName, object? entityType) + public static string MethodOnNonTphRootNotSupported(object? methodName, object? entityType) => string.Format( - GetString("MethodOnNonTPHRootNotSupported", nameof(methodName), nameof(entityType)), + GetString("MethodOnNonTphRootNotSupported", nameof(methodName), nameof(entityType)), methodName, entityType); /// @@ -982,17 +982,17 @@ public static string NonTphMappingStrategy(object? mappingStrategy, object? enti /// /// Both '{entityType}' and '{otherEntityType}' are mapped to the table '{table}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. /// - public static string NonTPHTableClash(object? entityType, object? otherEntityType, object? table) + public static string NonTphTableClash(object? entityType, object? otherEntityType, object? table) => string.Format( - GetString("NonTPHTableClash", nameof(entityType), nameof(otherEntityType), nameof(table)), + GetString("NonTphTableClash", nameof(entityType), nameof(otherEntityType), nameof(table)), entityType, otherEntityType, table); /// /// Both '{entityType}' and '{otherEntityType}' are mapped to the view '{view}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different views. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. /// - public static string NonTPHViewClash(object? entityType, object? otherEntityType, object? view) + public static string NonTphViewClash(object? entityType, object? otherEntityType, object? view) => string.Format( - GetString("NonTPHViewClash", nameof(entityType), nameof(otherEntityType), nameof(view)), + GetString("NonTphViewClash", nameof(entityType), nameof(otherEntityType), nameof(view)), entityType, otherEntityType, view); /// @@ -1070,9 +1070,9 @@ public static string RelationalNotInUse /// /// Cannot create a 'SelectExpression' with a custom 'TableExpressionBase' since the result type '{entityType}' is part of a hierarchy and does not contain a discriminator property. /// - public static string SelectExpressionNonTPHWithCustomTable(object? entityType) + public static string SelectExpressionNonTphWithCustomTable(object? entityType) => string.Format( - GetString("SelectExpressionNonTPHWithCustomTable", nameof(entityType)), + GetString("SelectExpressionNonTphWithCustomTable", nameof(entityType)), entityType); /// @@ -1120,9 +1120,9 @@ public static string TableOverrideMismatch(object? propertySpecification, object /// /// The element type of the result of '{dbFunction}' is mapped to '{entityType}'. This is not supported since '{entityType}' is part of hierarchy and does not contain a discriminator property. /// - public static string TableValuedFunctionNonTPH(object? dbFunction, object? entityType) + public static string TableValuedFunctionNonTph(object? dbFunction, object? entityType) => string.Format( - GetString("TableValuedFunctionNonTPH", nameof(dbFunction), nameof(entityType)), + GetString("TableValuedFunctionNonTph", nameof(dbFunction), nameof(entityType)), dbFunction, entityType); /// @@ -1152,17 +1152,17 @@ public static string TooFewReaderFields(object? expected, object? actual) /// /// '{entityType}' is mapped to the table '{table}' while '{otherEntityType}' is mapped to the table '{otherTable}'. Map all the entity types in the hierarchy to the same table, or remove the discriminator and map them all to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. /// - public static string TPHTableMismatch(object? entityType, object? table, object? otherEntityType, object? otherTable) + public static string TphTableMismatch(object? entityType, object? table, object? otherEntityType, object? otherTable) => string.Format( - GetString("TPHTableMismatch", nameof(entityType), nameof(table), nameof(otherEntityType), nameof(otherTable)), + GetString("TphTableMismatch", nameof(entityType), nameof(table), nameof(otherEntityType), nameof(otherTable)), entityType, table, otherEntityType, otherTable); /// /// '{entityType}' is mapped to the view '{view}' while '{otherEntityType}' is mapped to the view '{otherView}'. Map all the entity types in the hierarchy to the same view, or remove the discriminator and map them all to different views. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. /// - public static string TPHViewMismatch(object? entityType, object? view, object? otherEntityType, object? otherView) + public static string TphViewMismatch(object? entityType, object? view, object? otherEntityType, object? otherView) => string.Format( - GetString("TPHViewMismatch", nameof(entityType), nameof(view), nameof(otherEntityType), nameof(otherView)), + GetString("TphViewMismatch", nameof(entityType), nameof(view), nameof(otherEntityType), nameof(otherView)), entityType, view, otherEntityType, otherView); /// @@ -2123,20 +2123,20 @@ public static FallbackEventDefinition LogForeignKeyPropertiesMappedToUnrelatedTa /// /// The foreign key {foreignKeyProperties} on the entity type '{entityType}' targeting '{principalEntityType}' cannot be represented in the database. '{principalEntityType}' is mapped using the table per concrete type meaning that the derived entities will not be present in {'principalTable'}. If this foreign key on '{entityType}' will never reference entities derived from '{principalEntityType}' then the foreign key constraint name can be specified explicitly to force it to be created. /// - public static FallbackEventDefinition LogForeignKeyTPCPrincipal(IDiagnosticsLogger logger) + public static FallbackEventDefinition LogForeignKeyTpcPrincipal(IDiagnosticsLogger logger) { - var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogForeignKeyTPCPrincipal; + var definition = ((RelationalLoggingDefinitions)logger.Definitions).LogForeignKeyTpcPrincipal; if (definition == null) { definition = NonCapturingLazyInitializer.EnsureInitialized( - ref ((RelationalLoggingDefinitions)logger.Definitions).LogForeignKeyTPCPrincipal, + ref ((RelationalLoggingDefinitions)logger.Definitions).LogForeignKeyTpcPrincipal, logger, static logger => new FallbackEventDefinition( logger.Options, - RelationalEventId.ForeignKeyTPCPrincipalWarning, + RelationalEventId.ForeignKeyTpcPrincipalWarning, LogLevel.Warning, - "RelationalEventId.ForeignKeyTPCPrincipalWarning", - _resourceManager.GetString("LogForeignKeyTPCPrincipal")!)); + "RelationalEventId.ForeignKeyTpcPrincipalWarning", + _resourceManager.GetString("LogForeignKeyTpcPrincipal")!)); } return (FallbackEventDefinition)definition; diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 6e09693e958..800ae376254 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -117,7 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + The corresponding CLR type for entity type '{entityType}' cannot be instantiated, but the entity type was mapped to '{storeObject}' using the 'TPC' mapping strategy. Only instantiable types should be mapped. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. @@ -544,9 +544,9 @@ The foreign key {foreignKeyProperties} on the entity type '{entityType}' targeting '{principalEntityType}' cannot be represented in the database. Either the properties {foreignKeyProperties} aren't mapped to table '{table}', or the principal properties {principalProperties} aren't mapped to table '{principalTable}'. All foreign key properties must map to the table to which the dependent type is mapped, and all principal properties must map to a single table to which the principal type is mapped. Error RelationalEventId.ForeignKeyPropertiesMappedToUnrelatedTables string string string string string string string - + The foreign key {foreignKeyProperties} on the entity type '{entityType}' targeting '{principalEntityType}' cannot be represented in the database. '{principalEntityType}' is mapped using the table per concrete type meaning that the derived entities will not be present in {'principalTable'}. If this foreign key on '{entityType}' will never reference entities derived from '{principalEntityType}' then the foreign key constraint name can be specified explicitly to force it to be created. - Warning RelationalEventId.ForeignKeyTPCPrincipalWarning string string string string string string string + Warning RelationalEventId.ForeignKeyTpcPrincipalWarning string string string string string string string Generating down script for migration '{migration}'. @@ -671,7 +671,7 @@ The entity type '{entityType}' is mapped to the DbFunction named '{functionName}', but no DbFunction with that name was found in the model. Ensure that the entity type mapping is configured using the model name of a function in the model. - + Using '{methodName}' on DbSet of '{entityType}' is not supported since '{entityType}' is part of hierarchy and does not contain a discriminator property. @@ -734,10 +734,10 @@ The mapping strategy '{mappingStrategy}' specified on '{entityType}' is not supported for entity types with a discriminator. - + Both '{entityType}' and '{otherEntityType}' are mapped to the table '{table}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. - + Both '{entityType}' and '{otherEntityType}' are mapped to the view '{view}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different views. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. @@ -770,7 +770,7 @@ Relational-specific methods can only be used when the context is using a relational database provider. - + Cannot create a 'SelectExpression' with a custom 'TableExpressionBase' since the result type '{entityType}' is part of a hierarchy and does not contain a discriminator property. @@ -791,7 +791,7 @@ The property '{propertySpecification}' has specific configuration for the table '{table}', but isn't mapped to a column on that table. Remove the specific configuration, or map an entity type that contains this property to '{table}'. - + The element type of the result of '{dbFunction}' is mapped to '{entityType}'. This is not supported since '{entityType}' is part of hierarchy and does not contain a discriminator property. @@ -803,10 +803,10 @@ The underlying reader doesn't have as many fields as expected. Expected: {expected}, actual: {actual}. - + '{entityType}' is mapped to the table '{table}' while '{otherEntityType}' is mapped to the table '{otherTable}'. Map all the entity types in the hierarchy to the same table, or remove the discriminator and map them all to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. - + '{entityType}' is mapped to the view '{view}' while '{otherEntityType}' is mapped to the view '{otherView}'. Map all the entity types in the hierarchy to the same view, or remove the discriminator and map them all to different views. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 531b5ea81bd..bbfc2c85534 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -216,7 +216,7 @@ internal SelectExpression(IEntityType entityType, TableExpressionBase tableExpre if ((entityType.BaseType != null || entityType.GetDirectlyDerivedTypes().Any()) && entityType.FindDiscriminatorProperty() == null) { - throw new InvalidOperationException(RelationalStrings.SelectExpressionNonTPHWithCustomTable(entityType.DisplayName())); + throw new InvalidOperationException(RelationalStrings.SelectExpressionNonTphWithCustomTable(entityType.DisplayName())); } var table = tableExpressionBase switch diff --git a/src/EFCore.SqlServer/Infrastructure/SqlServerDbContextOptionsBuilder.cs b/src/EFCore.SqlServer/Infrastructure/SqlServerDbContextOptionsBuilder.cs index 521960728a1..25c9312e95a 100644 --- a/src/EFCore.SqlServer/Infrastructure/SqlServerDbContextOptionsBuilder.cs +++ b/src/EFCore.SqlServer/Infrastructure/SqlServerDbContextOptionsBuilder.cs @@ -101,6 +101,6 @@ public virtual SqlServerDbContextOptionsBuilder EnableRetryOnFailure(ICollection public virtual SqlServerDbContextOptionsBuilder EnableRetryOnFailure( int maxRetryCount, TimeSpan maxRetryDelay, - ICollection? errorNumbersToAdd) + IEnumerable? errorNumbersToAdd) => ExecutionStrategy(c => new SqlServerRetryingExecutionStrategy(c, maxRetryCount, maxRetryDelay, errorNumbersToAdd)); } diff --git a/src/EFCore.SqlServer/SqlServerRetryingExecutionStrategy.cs b/src/EFCore.SqlServer/SqlServerRetryingExecutionStrategy.cs index cca28b4a2eb..b621c7be216 100644 --- a/src/EFCore.SqlServer/SqlServerRetryingExecutionStrategy.cs +++ b/src/EFCore.SqlServer/SqlServerRetryingExecutionStrategy.cs @@ -28,7 +28,7 @@ namespace Microsoft.EntityFrameworkCore; /// public class SqlServerRetryingExecutionStrategy : ExecutionStrategy { - private readonly ICollection? _additionalErrorNumbers; + private readonly HashSet? _additionalErrorNumbers; /// /// Creates a new instance of . @@ -96,7 +96,7 @@ public SqlServerRetryingExecutionStrategy( /// Additional SQL error numbers that should be considered transient. public SqlServerRetryingExecutionStrategy( ExecutionStrategyDependencies dependencies, - ICollection errorNumbersToAdd) + IEnumerable errorNumbersToAdd) : this(dependencies, DefaultMaxRetryCount, DefaultMaxDelay, errorNumbersToAdd) { } @@ -112,13 +112,13 @@ public SqlServerRetryingExecutionStrategy( DbContext context, int maxRetryCount, TimeSpan maxRetryDelay, - ICollection? errorNumbersToAdd) + IEnumerable? errorNumbersToAdd) : base( context, maxRetryCount, maxRetryDelay) { - _additionalErrorNumbers = errorNumbersToAdd; + _additionalErrorNumbers = errorNumbersToAdd?.ToHashSet(); } /// @@ -132,10 +132,10 @@ public SqlServerRetryingExecutionStrategy( ExecutionStrategyDependencies dependencies, int maxRetryCount, TimeSpan maxRetryDelay, - ICollection? errorNumbersToAdd) + IEnumerable? errorNumbersToAdd) : base(dependencies, maxRetryCount, maxRetryDelay) { - _additionalErrorNumbers = errorNumbersToAdd; + _additionalErrorNumbers = errorNumbersToAdd?.ToHashSet(); } /// diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index fe4fd6f5b06..b52226369ec 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -74,7 +74,6 @@ public void Test_new_annotations_handled_for_entity_types() RelationalAnnotationNames.IsFixedLength, RelationalAnnotationNames.Collation, RelationalAnnotationNames.IsStored, - RelationalAnnotationNames.MappingStrategy, // Will be handled in the next PR RelationalAnnotationNames.TpcMappingStrategy, RelationalAnnotationNames.TphMappingStrategy, RelationalAnnotationNames.TptMappingStrategy, @@ -98,6 +97,15 @@ public void Test_new_annotations_handled_for_entity_types() + nameof(RelationalEntityTypeBuilderExtensions.ToTable) + @"(""WithAnnotations"", ""MySchema"")") }, + { + RelationalAnnotationNames.MappingStrategy, + (RelationalAnnotationNames.TphMappingStrategy, + _toTable + + ";" + + _nl + + _nl + + "entityTypeBuilder.UseTphMappingStrategy()") + }, { CoreAnnotationNames.DiscriminatorProperty, ("Id", _toTable diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 9489f117095..1393c44b570 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -605,6 +605,57 @@ public void Views_with_schemas_are_stored_in_the_model_snapshot() Assert.Equal("ViewSchema", o.GetEntityTypes().Single().GetViewSchema()); }); + [ConditionalFact] + public virtual void Entities_are_stored_in_model_snapshot_for_TPC() + => Test( + builder => + { + builder.Entity() + .ToTable("DerivedEntity", "foo") + .ToView("DerivedView", "foo"); + builder.Entity().UseTpcMappingStrategy(); + }, + AddBoilerPlate( + GetHeading() + + @" + modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+BaseEntity"", b => + { + b.Property(""Id"") + .ValueGeneratedOnAdd() + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + + b.Property(""Discriminator"") + .HasColumnType(""nvarchar(max)""); + + b.HasKey(""Id""); + + b.ToTable(""BaseEntity""); + + b.UseTpcMappingStrategy(); + }); + + modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity"", b => + { + b.HasBaseType(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+BaseEntity""); + + b.Property(""Name"") + .HasColumnType(""nvarchar(max)""); + + b.ToTable(""DerivedEntity"", ""foo""); + + b.ToView(""DerivedView"", ""foo""); + });"), + o => + { + Assert.Equal(4, o.GetAnnotations().Count()); + + var derived = o.FindEntityType("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity"); + Assert.Equal("DerivedEntity", derived.GetTableName()); + Assert.Equal("DerivedView", derived.GetViewName()); + }); + [ConditionalFact] public void Unmapped_entity_types_are_stored_in_the_model_snapshot() => Test( @@ -6215,6 +6266,9 @@ protected void Test(IModel model, string expectedCode, Action as var generator = CreateMigrationsGenerator(); var code = generator.GenerateSnapshot("RootNamespace", typeof(DbContext), "Snapshot", model); + var modelFromSnapshot = BuildModelFromSnapshotSource(code); + assert(modelFromSnapshot, model); + try { Assert.Equal(expectedCode, code, ignoreLineEndingDifferences: true); @@ -6224,9 +6278,6 @@ protected void Test(IModel model, string expectedCode, Action as throw new Exception(e.Message + Environment.NewLine + Environment.NewLine + "-- Actual code:" + Environment.NewLine + code); } - var modelFromSnapshot = BuildModelFromSnapshotSource(code); - assert(modelFromSnapshot, model); - var targetOptionsBuilder = TestHelpers .AddProviderOptions(new DbContextOptionsBuilder()) .UseModel(model) diff --git a/test/EFCore.Relational.Specification.Tests/Query/TPTInheritanceQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/TPTInheritanceQueryTestBase.cs index 7e889da5a0b..2ca542112b2 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/TPTInheritanceQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/TPTInheritanceQueryTestBase.cs @@ -42,12 +42,12 @@ public virtual void Using_from_sql_throws() var message = Assert.Throws(() => context.Set().FromSqlRaw("Select * from Birds")).Message; - Assert.Equal(RelationalStrings.MethodOnNonTPHRootNotSupported("FromSqlRaw", typeof(Bird).Name), message); + Assert.Equal(RelationalStrings.MethodOnNonTphRootNotSupported("FromSqlRaw", typeof(Bird).Name), message); message = Assert.Throws(() => context.Set().FromSqlInterpolated($"Select * from Birds")) .Message; - Assert.Equal(RelationalStrings.MethodOnNonTPHRootNotSupported("FromSqlInterpolated", typeof(Bird).Name), message); + Assert.Equal(RelationalStrings.MethodOnNonTphRootNotSupported("FromSqlInterpolated", typeof(Bird).Name), message); } protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index 1666ad475ca..3e2472cb2f4 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -1589,7 +1589,7 @@ public virtual void Detects_clashing_entity_types_in_view_TPT() modelBuilder.Entity().ToTable("Dog").ToView("Cat"); VerifyError( - RelationalStrings.NonTPHViewClash(nameof(Dog), nameof(Cat), "Cat"), + RelationalStrings.NonTphViewClash(nameof(Dog), nameof(Cat), "Cat"), modelBuilder); } @@ -1601,7 +1601,7 @@ public virtual void Detects_table_and_view_TPT_mismatch() modelBuilder.Entity().ToTable("Animal").ToView("Cat"); VerifyError( - RelationalStrings.NonTPHTableClash(nameof(Cat), nameof(Animal), "Animal"), + RelationalStrings.NonTphTableClash(nameof(Cat), nameof(Animal), "Animal"), modelBuilder); } @@ -1613,7 +1613,7 @@ public virtual void Detects_TPT_with_discriminator() modelBuilder.Entity().ToTable("Cat"); VerifyError( - RelationalStrings.TPHTableMismatch(nameof(Cat), nameof(Cat), nameof(Animal), nameof(Animal)), + RelationalStrings.TphTableMismatch(nameof(Cat), nameof(Cat), nameof(Animal), nameof(Animal)), modelBuilder); } @@ -1625,7 +1625,7 @@ public virtual void Detects_view_TPT_with_discriminator() modelBuilder.Entity().ToView("Cat"); VerifyError( - RelationalStrings.TPHViewMismatch(nameof(Cat), nameof(Cat), nameof(Animal), nameof(Animal)), + RelationalStrings.TphViewMismatch(nameof(Cat), nameof(Cat), nameof(Animal), nameof(Animal)), modelBuilder); } @@ -1799,7 +1799,7 @@ public virtual void Detects_ToTable_for_abstract_class_TPC() modelBuilder.Entity>(); VerifyError( - RelationalStrings.AbstractTPC(nameof(Abstract), "dbo.Abstract"), + RelationalStrings.AbstractTpc(nameof(Abstract), "dbo.Abstract"), modelBuilder); } @@ -1812,7 +1812,7 @@ public virtual void Detects_ToView_for_abstract_class_TPC() modelBuilder.Entity>(); VerifyError( - RelationalStrings.AbstractTPC(nameof(Abstract), "Abstract"), + RelationalStrings.AbstractTpc(nameof(Abstract), "Abstract"), modelBuilder); } @@ -1825,7 +1825,7 @@ public virtual void Detects_ToFunction_for_abstract_class_TPC() modelBuilder.Entity>(); VerifyError( - RelationalStrings.AbstractTPC(nameof(Abstract), "Abstract"), + RelationalStrings.AbstractTpc(nameof(Abstract), "Abstract"), modelBuilder); } @@ -1838,7 +1838,7 @@ public virtual void Detects_clashing_entity_types_in_views_TPC() modelBuilder.Entity().ToTable("Dog").ToView("Cat"); VerifyError( - RelationalStrings.NonTPHViewClash(nameof(Dog), nameof(Cat), "Cat"), + RelationalStrings.NonTphViewClash(nameof(Dog), nameof(Cat), "Cat"), modelBuilder); } @@ -1850,7 +1850,7 @@ public virtual void Detects_table_and_view_TPC_mismatch() modelBuilder.Entity().ToTable("Animal").ToView("Cat"); VerifyError( - RelationalStrings.NonTPHTableClash(nameof(Cat), nameof(Animal), "Animal"), + RelationalStrings.NonTphTableClash(nameof(Cat), nameof(Animal), "Animal"), modelBuilder); } @@ -1975,7 +1975,7 @@ public virtual void Detects_unmapped_foreign_keys_in_TPC() .HasPrincipalKey(a => a.Name); var definition = - RelationalResources.LogForeignKeyTPCPrincipal(new TestLogger()); + RelationalResources.LogForeignKeyTpcPrincipal(new TestLogger()); VerifyWarning( definition.GenerateMessage( l => l.Log( @@ -2411,7 +2411,7 @@ public virtual void Non_TPH_as_a_result_of_DbFunction_throws() modelBuilder.HasDbFunction(TestMethods.MethodFMi); VerifyError( - RelationalStrings.TableValuedFunctionNonTPH( + RelationalStrings.TableValuedFunctionNonTph( TestMethods.MethodFMi.DeclaringType.FullName + "." + TestMethods.MethodFMi.Name + "()", "C"), modelBuilder); } diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index c657323d7ac..ae8222c0982 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -32,11 +32,12 @@ public void Can_use_relational_model_with_tables(bool useExplicitMapping, Mappin var model = CreateTestModel(mapToTables: useExplicitMapping, mapping: mapping); Assert.Equal(11, model.Model.GetEntityTypes().Count()); - Assert.Equal(mapping == Mapping.TPC - ? 5 - : mapping == Mapping.TPH - ? 3 - : 6, model.Tables.Count()); + Assert.Equal(mapping switch + { + Mapping.TPC => 5, + Mapping.TPH => 3, + _ => 6 + }, model.Tables.Count()); Assert.Empty(model.Views); Assert.True(model.Model.GetEntityTypes().All(et => !et.GetViewMappings().Any())); @@ -53,11 +54,12 @@ public void Can_use_relational_model_with_views(Mapping mapping) var model = CreateTestModel(mapToTables: false, mapToViews: true, mapping); Assert.Equal(11, model.Model.GetEntityTypes().Count()); - Assert.Equal(mapping == Mapping.TPC - ? 5 - : mapping == Mapping.TPH - ? 3 - : 6, model.Views.Count()); + Assert.Equal(mapping switch + { + Mapping.TPC => 5, + Mapping.TPH => 3, + _ => 6 + }, model.Views.Count()); Assert.Empty(model.Tables); Assert.True(model.Model.GetEntityTypes().All(et => !et.GetTableMappings().Any())); @@ -74,16 +76,18 @@ public void Can_use_relational_model_with_views_and_tables(Mapping mapping) var model = CreateTestModel(mapToTables: true, mapToViews: true, mapping); Assert.Equal(11, model.Model.GetEntityTypes().Count()); - Assert.Equal(mapping == Mapping.TPC - ? 5 - : mapping == Mapping.TPH - ? 3 - : 6, model.Tables.Count()); - Assert.Equal(mapping == Mapping.TPC - ? 5 - : mapping == Mapping.TPH - ? 3 - : 6, model.Views.Count()); + Assert.Equal(mapping switch + { + Mapping.TPC => 5, + Mapping.TPH => 3, + _ => 6 + }, model.Tables.Count()); + Assert.Equal(mapping switch + { + Mapping.TPC => 5, + Mapping.TPH => 3, + _ => 6 + }, model.Views.Count()); AssertDefaultMappings(model, mapping); AssertTables(model, mapping); diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index 41aa9537405..c1cdf2a68e3 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -2113,6 +2113,52 @@ public void Add_table_sharing_to_TPT() Assert.Equal("OrderDate", operation.Name); }); + [ConditionalFact] + public void Add_table_sharing_to_TPC() + => Execute( + common => + { + common.Entity( + "Order", + x => + { + x.UseTpcMappingStrategy(); + x.ToTable("Order"); + x.Property("Id"); + }); + common.Entity( + "DetailedOrder", + x => + { + x.ToTable("DetailedOrder"); + x.HasBaseType("Order"); + x.Property("Description").HasColumnName("Description"); + }); + }, + _ => { }, + target => + { + target.Entity( + "OrderDetails", + x => + { + x.ToTable("DetailedOrder"); + x.Property("Id"); + x.Property("Description").HasColumnName("Description"); + x.Property("OrderDate"); + x.HasOne("DetailedOrder", null).WithOne().HasForeignKey("OrderDetails", "Id"); + }); + }, + operations => + { + Assert.Equal(1, operations.Count); + + var operation = Assert.IsType(operations[0]); + Assert.Null(operation.Schema); + Assert.Equal("DetailedOrder", operation.Table); + Assert.Equal("OrderDate", operation.Name); + }); + [ConditionalFact] public void Rename_column_in_TPT_with_table_sharing_and_seed_data() => Execute( @@ -6794,6 +6840,1304 @@ public void Change_TPH_to_TPT_with_FKs_and_seed_data_readonly_discriminator() Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); })); + // Seeding not supported yet + //[ConditionalFact] + public void Change_TPH_to_TPC_with_FKs_and_seed_data() + => Execute( + modelBuilder => + { + modelBuilder.Entity( + "Animal", x => + { + x.Property("Id"); + x.Property("MouseId"); + + x.HasOne("Mouse").WithMany().HasForeignKey("MouseId"); + }); + modelBuilder.Entity( + "Cat", x => + { + x.HasBaseType("Animal"); + x.Property("PreyId").HasColumnName("PreyId"); + + x.HasOne("Animal").WithMany().HasForeignKey("PreyId"); + x.HasData( + new { Id = 11, MouseId = 31 }); + }); + modelBuilder.Entity( + "Dog", x => + { + x.HasBaseType("Animal"); + x.Property("PreyId").HasColumnName("PreyId"); + + x.HasOne("Animal").WithMany().HasForeignKey("PreyId"); + x.HasData( + new { Id = 21, PreyId = 31 }); + }); + modelBuilder.Entity( + "Mouse", x => + { + x.HasBaseType("Animal"); + + x.HasData( + new { Id = 31 }); + }); + }, + source => + { + source.Entity( + "Animal", x => + { + x.Property("Discriminator").Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save); + }); + source.Entity( + "Cat", x => + { + x.HasData( + new { Id = 12, MouseId = 32 }, + new { Id = 13 }); + }); + source.Entity( + "Dog", x => + { + x.HasData( + new { Id = 22, PreyId = 32 }); + }); + source.Entity( + "Mouse", x => + { + x.HasData( + new { Id = 32 }); + }); + source.Entity( + "UnrelatedDog", x => + { + x.ToTable("Dogs"); + x.Property("Id"); + x.Property("PreyId"); + }); + }, + target => + { + target.Entity( + "Animal", x => + { + x.UseTpcMappingStrategy(); + }); + target.Entity( + "Cat", x => + { + x.ToTable("Cats"); + x.HasData( + new { Id = 12 }, + new { Id = 13, MouseId = 32 }); + }); + target.Entity( + "Dog", x => + { + x.ToTable("Dogs"); + x.HasOne("Animal", null).WithOne().HasForeignKey("Dog", "Id") + .HasConstraintName("FK_Dogs_Animal"); + x.HasData( + new { Id = 22, PreyId = 33 }, + new { Id = 23 }); + }); + target.Entity( + "Mouse", x => + { + x.ToTable("Mice"); + x.HasData( + new { Id = 33 }); + }); + }, + upOps => Assert.Collection( + upOps, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Animal_MouseId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Animal_PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Animal_PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Discriminator", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("MouseId", operation.Name); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(typeof(int), operation.ClrType); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Mice", operation.Name); + Assert.Collection( + operation.Columns, + c => + { + Assert.Equal("Id", c.Name); + Assert.Equal("default_int_mapping", c.ColumnType); + Assert.Equal("Mice", c.Table); + Assert.False(c.IsNullable); + Assert.False(c.IsRowVersion); + Assert.Null(c.IsUnicode); + Assert.Null(c.IsFixedLength); + Assert.Null(c.MaxLength); + Assert.Null(c.Precision); + Assert.Null(c.Scale); + Assert.Null(c.DefaultValue); + Assert.Null(c.DefaultValueSql); + Assert.Null(c.ComputedColumnSql); + Assert.Null(c.IsStored); + Assert.Null(c.Comment); + Assert.Null(c.Collation); + }, + c => + { + Assert.Equal("MouseId", c.Name); + Assert.Equal("default_int_mapping", c.ColumnType); + Assert.Equal("Mice", c.Table); + Assert.True(c.IsNullable); + }); + + var pk = operation.PrimaryKey; + Assert.Equal("PK_Mice", pk.Name); + Assert.Equal("Mice", pk.Table); + Assert.Equal(new[] { "Id" }, pk.Columns); + + var fk = operation.ForeignKeys.Single(); + Assert.Equal("FK_Mice_Mice_MouseId", fk.Name); + Assert.Equal("Mice", fk.Table); + Assert.Equal("Mice", fk.PrincipalTable); + Assert.Equal(new[] { "MouseId" }, fk.Columns); + Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, fk.OnDelete); + + Assert.Empty(operation.UniqueConstraints); + Assert.Null(operation.Comment); + Assert.Empty(operation.CheckConstraints); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Cats", operation.Name); + Assert.Collection( + operation.Columns, + c => + { + Assert.Equal("Id", c.Name); + Assert.Equal("default_int_mapping", c.ColumnType); + Assert.Equal("Cats", c.Table); + Assert.False(c.IsNullable); + }, + c => + { + Assert.Equal("MouseId", c.Name); + Assert.Equal("default_int_mapping", c.ColumnType); + Assert.Equal("Cats", c.Table); + Assert.True(c.IsNullable); + }, + c => + { + Assert.Equal("PreyId", c.Name); + Assert.Equal("default_int_mapping", c.ColumnType); + Assert.Equal("Cats", c.Table); + Assert.True(c.IsNullable); + }); + + var pk = operation.PrimaryKey; + Assert.Equal("PK_Cats", pk.Name); + Assert.Equal("Cats", pk.Table); + Assert.Equal(new[] { "Id" }, pk.Columns); + + Assert.Collection( + operation.ForeignKeys, + fk => + { + Assert.Equal("FK_Cats_Mice_MouseId", fk.Name); + Assert.Equal("Cats", fk.Table); + Assert.Equal("Mice", fk.PrincipalTable); + Assert.Equal(new[] { "MouseId" }, fk.Columns); + Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, fk.OnDelete); + }); + + Assert.Empty(operation.UniqueConstraints); + Assert.Null(operation.Comment); + Assert.Empty(operation.CheckConstraints); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Cats", operation.Table); + Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(12, v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Cats", operation.Table); + Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(13, v), + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(23, v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(33, v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(21, v), + v => Assert.Equal(31, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(22, v), + v => Assert.Equal(33, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(23, v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Mice", operation.Table); + Assert.Equal(new[] { "Id" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(33, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Dogs_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Cats_PreyId", operation.Name); + Assert.Equal("Cats", operation.Table); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Mice_MouseId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal("Mice", operation.PrincipalTable); + Assert.Equal(new[] { "MouseId" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal", operation.Name); + Assert.Equal("Dogs", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "Id" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.Cascade, operation.OnDelete); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); + }), + downOps => Assert.Collection( + downOps, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Mice_MouseId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Cats", operation.Name); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Mice", operation.Name); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Dogs_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(21, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(22, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(23, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(23, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(33, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Discriminator", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(typeof(string), operation.ClrType); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Discriminator", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(typeof(string), operation.ClrType); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(typeof(int), operation.ClrType); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(11, v)); + + Assert.Equal(new[] { "Discriminator" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Cat", v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(13, v)); + + Assert.Equal(new[] { "Discriminator", "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Cat", v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(21, v)); + + Assert.Equal(new[] { "Discriminator", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Dog", v), + v => Assert.Equal(31, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(31, v)); + + Assert.Equal(new[] { "Discriminator" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Mouse", v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id", "Discriminator", "MouseId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(32, v), + v => Assert.Equal("Mouse", v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(12, v)); + + Assert.Equal(new[] { "Discriminator", "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Cat", v), + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(22, v)); + + Assert.Equal(new[] { "Discriminator", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Dog", v), + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Animal_PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Animal_MouseId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "MouseId" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Animal_PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); + })); + + // Seeding not supported yet + //[ConditionalFact] + public void Change_TPT_to_TPC_with_FKs_and_seed_data() + => Execute( + modelBuilder => + { + modelBuilder.Entity( + "Animal", x => + { + x.Property("Id"); + x.Property("MouseId"); + + x.HasOne("Mouse").WithMany().HasForeignKey("MouseId"); + }); + modelBuilder.Entity( + "Cat", x => + { + x.HasBaseType("Animal"); + x.Property("PreyId").HasColumnName("PreyId"); + + x.HasOne("Animal").WithMany().HasForeignKey("PreyId"); + x.HasData( + new { Id = 11, MouseId = 31 }); + }); + modelBuilder.Entity( + "Dog", x => + { + x.HasBaseType("Animal"); + x.Property("PreyId").HasColumnName("PreyId"); + + x.HasOne("Animal").WithMany().HasForeignKey("PreyId"); + x.HasData( + new { Id = 21, PreyId = 31 }); + }); + modelBuilder.Entity( + "Mouse", x => + { + x.HasBaseType("Animal"); + + x.HasData( + new { Id = 31 }); + }); + }, + source => + { + source.Entity( + "Animal", x => + { + x.UseTptMappingStrategy(); + }); + source.Entity( + "Cat", x => + { + x.ToTable("Cats"); + x.HasData( + new { Id = 12, MouseId = 32 }, + new { Id = 13 }); + }); + source.Entity( + "Dog", x => + { + x.ToTable("Dogs"); + x.HasData( + new { Id = 22, PreyId = 32 }); + }); + source.Entity( + "Mouse", x => + { + x.ToTable("Mice"); + x.HasData( + new { Id = 32 }); + }); + source.Entity( + "UnrelatedDog", x => + { + x.ToTable("Dogs"); + x.Property("Id"); + x.Property("PreyId"); + }); + }, + target => + { + target.Entity( + "Animal", x => + { + x.UseTpcMappingStrategy(); + }); + target.Entity( + "Cat", x => + { + x.ToTable("Cats"); + x.HasData( + new { Id = 12 }, + new { Id = 13, MouseId = 32 }); + }); + target.Entity( + "Dog", x => + { + x.ToTable("Dogs"); + x.HasOne("Animal", null).WithOne().HasForeignKey("Dog", "Id") + .HasConstraintName("FK_Dogs_Animal"); + x.HasData( + new { Id = 22, PreyId = 33 }, + new { Id = 23 }); + }); + target.Entity( + "Mouse", x => + { + x.ToTable("Mice"); + x.HasData( + new { Id = 33 }); + }); + }, + upOps => Assert.Collection( + upOps, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Animal_MouseId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Animal_PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Animal_PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Cats", operation.Name); + Assert.Collection( + operation.Columns, + c => + { + Assert.Equal("Id", c.Name); + Assert.Equal("default_int_mapping", c.ColumnType); + Assert.Equal("Cats", c.Table); + Assert.False(c.IsNullable); + Assert.False(c.IsRowVersion); + Assert.Null(c.IsUnicode); + Assert.Null(c.IsFixedLength); + Assert.Null(c.MaxLength); + Assert.Null(c.Precision); + Assert.Null(c.Scale); + Assert.Null(c.DefaultValue); + Assert.Null(c.DefaultValueSql); + Assert.Null(c.ComputedColumnSql); + Assert.Null(c.IsStored); + Assert.Null(c.Comment); + Assert.Null(c.Collation); + }, + c => + { + Assert.Equal("PreyId", c.Name); + Assert.Equal("default_int_mapping", c.ColumnType); + Assert.Equal("Cats", c.Table); + Assert.True(c.IsNullable); + Assert.False(c.IsRowVersion); + Assert.Null(c.IsUnicode); + Assert.Null(c.IsFixedLength); + Assert.Null(c.MaxLength); + Assert.Null(c.Precision); + Assert.Null(c.Scale); + Assert.Null(c.DefaultValue); + Assert.Null(c.DefaultValueSql); + Assert.Null(c.ComputedColumnSql); + Assert.Null(c.IsStored); + Assert.Null(c.Comment); + Assert.Null(c.Collation); + }); + + var pk = operation.PrimaryKey; + Assert.Equal("PK_Cats", pk.Name); + Assert.Equal("Cats", pk.Table); + Assert.Equal(new[] { "Id" }, pk.Columns); + + Assert.Collection( + operation.ForeignKeys, + fk => + { + Assert.Equal("FK_Cats_Animal_Id", fk.Name); + Assert.Equal("Cats", fk.Table); + Assert.Equal("Animal", fk.PrincipalTable); + Assert.Equal(new[] { "Id" }, fk.Columns); + Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); + Assert.Equal(ReferentialAction.Cascade, fk.OnDelete); + }, + fk => + { + Assert.Equal("FK_Cats_Animal_PreyId", fk.Name); + Assert.Equal("Cats", fk.Table); + Assert.Equal("Animal", fk.PrincipalTable); + Assert.Equal(new[] { "PreyId" }, fk.Columns); + Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, fk.OnDelete); + }); + + Assert.Empty(operation.UniqueConstraints); + Assert.Null(operation.Comment); + Assert.Empty(operation.CheckConstraints); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Mice", operation.Name); + Assert.Collection( + operation.Columns, + c => + { + Assert.Equal("Id", c.Name); + Assert.Equal("default_int_mapping", c.ColumnType); + Assert.Equal("Mice", c.Table); + Assert.False(c.IsNullable); + Assert.False(c.IsRowVersion); + Assert.Null(c.IsUnicode); + Assert.Null(c.IsFixedLength); + Assert.Null(c.MaxLength); + Assert.Null(c.Precision); + Assert.Null(c.Scale); + Assert.Null(c.DefaultValue); + Assert.Null(c.DefaultValueSql); + Assert.Null(c.ComputedColumnSql); + Assert.Null(c.IsStored); + Assert.Null(c.Comment); + Assert.Null(c.Collation); + }); + + var pk = operation.PrimaryKey; + Assert.Equal("PK_Mice", pk.Name); + Assert.Equal("Mice", pk.Table); + Assert.Equal(new[] { "Id" }, pk.Columns); + + var fk = operation.ForeignKeys.Single(); + Assert.Equal("FK_Mice_Animal_Id", fk.Name); + Assert.Equal("Mice", fk.Table); + Assert.Equal("Animal", fk.PrincipalTable); + Assert.Equal(new[] { "Id" }, fk.Columns); + Assert.Equal(new[] { "Id" }, fk.PrincipalColumns); + Assert.Equal(ReferentialAction.Cascade, fk.OnDelete); + + Assert.Empty(operation.UniqueConstraints); + Assert.Null(operation.Comment); + Assert.Empty(operation.CheckConstraints); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Discriminator", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(typeof(string), operation.ClrType); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(12, v)); + + Assert.Equal(new[] { "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(13, v)); + + Assert.Equal(new[] { "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(23, v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id", "MouseId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(33, v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(21, v), + v => Assert.Equal(31, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(22, v), + v => Assert.Equal(33, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "Id", "PreyId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(23, v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Mice", operation.Table); + Assert.Equal(new[] { "Id" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(33, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Dogs_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Cats_PreyId", operation.Name); + Assert.Equal("Cats", operation.Table); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Mice_MouseId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal("Mice", operation.PrincipalTable); + Assert.Equal(new[] { "MouseId" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal", operation.Name); + Assert.Equal("Dogs", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "Id" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.Cascade, operation.OnDelete); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); + }), + downOps => Assert.Collection( + downOps, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Mice_MouseId", operation.Name); + Assert.Equal("Animal", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Dogs_Animal_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Cats", operation.Name); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Mice", operation.Name); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Dogs_PreyId", operation.Name); + Assert.Equal("Dogs", operation.Table); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(21, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(22, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Dogs", operation.Table); + + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(23, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(23, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(33, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Discriminator", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(typeof(string), operation.ClrType); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Discriminator", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(typeof(string), operation.ClrType); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(typeof(int), operation.ClrType); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(11, v)); + + //Assert.Equal(new[] { "Discriminator" }, operation.Columns); + //Assert.Null(operation.ColumnTypes); + //AssertMultidimensionalArray( + // operation.Values, + // v => Assert.Equal("Cat", v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(13, v)); + + Assert.Equal(new[] { "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(21, v)); + + Assert.Equal(new[] { "Discriminator", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Dog", v), + v => Assert.Equal(31, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(31, v)); + + Assert.Equal(new[] { "Discriminator" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Mouse", v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id", "Discriminator", "MouseId" }, operation.Columns); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal(32, v), + v => Assert.Equal("Mouse", v), + v => Assert.Null(v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(12, v)); + + Assert.Equal(new[] { "Discriminator", "MouseId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Cat", v), + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "Id" }, operation.KeyColumns); + Assert.Null(operation.KeyColumnTypes); + AssertMultidimensionalArray( + operation.KeyValues, + v => Assert.Equal(22, v)); + + Assert.Equal(new[] { "Discriminator", "PreyId" }, operation.Columns); + Assert.Null(operation.ColumnTypes); + AssertMultidimensionalArray( + operation.Values, + v => Assert.Equal("Dog", v), + v => Assert.Equal(32, v)); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("IX_Animal_PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Animal_MouseId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "MouseId" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); + }, + o => + { + var operation = Assert.IsType(o); + Assert.Equal("FK_Animal_Animal_PreyId", operation.Name); + Assert.Equal("Animal", operation.Table); + Assert.Equal("Animal", operation.PrincipalTable); + Assert.Equal(new[] { "PreyId" }, operation.Columns); + Assert.Equal(new[] { "Id" }, operation.PrincipalColumns); + Assert.Equal(ReferentialAction.NoAction, operation.OnDelete); + })); [ConditionalFact] public void Add_foreign_key_on_base_type() => Execute(