From dffc214afdf7829c4103c5080cf42ee653e98c8f Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Tue, 14 Jun 2022 12:11:19 -0700 Subject: [PATCH] Add entity-splitting model building support Allow specifying different column names per table in TPT, TPC or entity splitting Move EntityTypeBuilder properties on store object builders to explicit implementation Change TableValuedFunctionBuilder to follow the pattern Introduce IReadOnlyStoreObjectDictionary to abstract table-specific configuration storage Rename GetColumnBaseName to GetColumnName Part of #620 Fixes #19811 --- .../Internal/CSharpEntityTypeGenerator.cs | 2 +- .../Design/AnnotationCodeGenerator.cs | 3 +- ...nalCSharpRuntimeAnnotationCodeGenerator.cs | 125 +- ...onalEntityTypeBuilderExtensions.ToTable.cs | 804 +++++++++ ...ionalEntityTypeBuilderExtensions.ToView.cs | 668 ++++++++ .../RelationalEntityTypeBuilderExtensions.cs | 988 +---------- .../RelationalEntityTypeExtensions.cs | 275 ++- .../RelationalForeignKeyExtensions.cs | 2 +- .../Extensions/RelationalIndexExtensions.cs | 2 +- .../Extensions/RelationalKeyExtensions.cs | 2 +- .../RelationalPropertyBuilderExtensions.cs | 3 +- .../RelationalPropertyExtensions.cs | 193 ++- .../RelationalModelValidator.cs | 327 +++- .../Builders/CheckConstraintBuilder.cs | 16 + .../Metadata/Builders/ColumnBuilder.cs | 95 ++ ...tionTableBuilder`.cs => ColumnBuilder`.cs} | 25 +- .../OwnedNavigationSplitTableBuilder.cs | 129 ++ .../OwnedNavigationSplitTableBuilder``.cs | 60 + .../OwnedNavigationSplitViewBuilder.cs | 98 ++ .../OwnedNavigationSplitViewBuilder``.cs | 49 + .../Builders/OwnedNavigationTableBuilder.cs | 75 +- .../Builders/OwnedNavigationTableBuilder``.cs | 60 + ...nedNavigationTableValuedFunctionBuilder.cs | 62 + ...dNavigationTableValuedFunctionBuilder``.cs | 70 + .../Builders/OwnedNavigationViewBuilder.cs | 100 ++ .../Builders/OwnedNavigationViewBuilder``.cs | 49 + .../Metadata/Builders/SplitTableBuilder.cs | 129 ++ .../Metadata/Builders/SplitTableBuilder`.cs | 51 + .../Metadata/Builders/SplitViewBuilder.cs | 98 ++ .../Metadata/Builders/SplitViewBuilder`.cs | 41 + .../Metadata/Builders/TableBuilder.cs | 56 +- .../Metadata/Builders/TableBuilder`.cs | 22 +- .../Builders/TableValuedFunctionBuilder.cs | 22 +- .../Builders/TableValuedFunctionBuilder`.cs | 62 + .../Metadata/Builders/ViewBuilder.cs | 100 ++ .../Metadata/Builders/ViewBuilder`.cs | 41 + .../Metadata/Builders/ViewColumnBuilder.cs | 95 ++ .../Metadata/Builders/ViewColumnBuilder`.cs | 39 + .../EntityTypeHierarchyMappingConvention.cs | 7 +- .../RelationalRuntimeModelConvention.cs | 57 +- .../Conventions/StoreGenerationConvention.cs | 9 +- .../IConventionEntityTypeMappingFragment.cs | 37 + .../IConventionRelationalPropertyOverrides.cs | 45 + .../Metadata/IEntityTypeMappingFragment.cs | 18 + .../IMutableEntityTypeMappingFragment.cs | 24 + .../IMutableRelationalPropertyOverrides.cs | 28 + .../IReadOnlyEntityTypeMappingFragment.cs | 29 + .../IReadOnlyRelationalPropertyOverrides.cs | 33 + .../IReadOnlyStoreObjectDictionary.cs | 29 + .../Metadata/IRelationalPropertyOverrides.cs | 18 + .../Internal/EntityTypeMappingFragment.cs | 233 +++ .../Internal/IRelationalPropertyOverrides.cs | 37 - .../Internal/InternalTriggerBuilder.cs | 1 - .../Metadata/Internal/RelationalModel.cs | 2 +- .../Internal/RelationalPropertyOverrides.cs | 154 +- .../Metadata/Internal/Table.cs | 2 +- .../Metadata/Internal/TableBase.cs | 6 +- .../Metadata/RelationalAnnotationNames.cs | 5 + .../RuntimeEntityTypeMappingFragment.cs | 59 + .../RuntimeRelationalPropertyOverrides.cs | 19 +- .../Metadata/StoreObjectDictionary.cs | 42 + .../Migrations/HistoryRepository.cs | 4 +- .../Properties/RelationalStrings.Designer.cs | 86 +- .../Properties/RelationalStrings.resx | 21 + .../Query/SqlExpressions/SelectExpression.cs | 11 +- .../SqlServerTableBuilderExtensions.cs | 38 +- .../Internal/SqlServerModelValidator.cs | 12 +- ...NavigationTemporalPeriodPropertyBuilder.cs | 2 +- .../OwnedNavigationTemporalTableBuilder.cs | 2 +- ... OwnedNavigationTemporalTableBuilder``.cs} | 18 +- .../Builders/TemporalPeriodPropertyBuilder.cs | 2 +- .../Metadata/Builders/TemporalTableBuilder.cs | 2 +- .../Builders/TemporalTableBuilder`.cs | 2 +- .../SqlServerValueGenerationConvention.cs | 9 +- ...ServerValueGenerationStrategyConvention.cs | 18 +- src/EFCore/Infrastructure/IInfrastructure.cs | 2 +- src/EFCore/Metadata/IProperty.cs | 1 - .../Design/CSharpMigrationsGeneratorTest.cs | 3 + .../Design/SnapshotModelProcessorTest.cs | 1 + .../Internal/CSharpEntityTypeGeneratorTest.cs | 4 +- .../CSharpRuntimeModelCodeGeneratorTest.cs | 102 +- .../RelationalScaffoldingModelFactoryTest.cs | 16 +- .../DataAnnotationInMemoryTest.cs | 2 + .../TestUtilities/TestSqlLoggerFactory.cs | 13 - .../RelationalModelValidatorTest.cs | 129 +- ...leSharingConcurrencyTokenConventionTest.cs | 8 +- .../RelationalBuilderExtensionsTest.cs | 6 +- .../RelationalMetadataExtensionsTest.cs | 6 +- .../Metadata/RelationalModelTest.cs | 10 +- ...lationalPropertyAttributeConventionTest.cs | 8 +- .../Metadata/TriggerTest.cs | 3 +- .../RelationalModelBuilderTest.cs | 825 ++++++++- .../RelationalTestModelBuilderExtensions.cs | 566 +++++-- .../RelationalApiConsistencyTest.cs | 112 +- .../ApiConsistencyTestBase.cs | 236 ++- .../DataAnnotationTestBase.cs | 25 +- .../TestUtilities/TestHelpers.cs | 11 +- .../DataAnnotationSqlServerTest.cs | 25 +- ...eteMappingInheritanceQuerySqlServerTest.cs | 14 +- .../Query/InheritanceQuerySqlServerTest.cs | 14 +- .../SqlServerApiConsistencyTest.cs | 16 +- .../UpdatesSqlServerTest.cs | 4 +- .../SqlServerMetadataExtensionsTest.cs | 8 +- .../SqlServerModelBuilderGenericTest.cs | 1506 +---------------- .../SqlServerModelBuilderNonGenericTest.cs | 66 + .../SqlServerModelBuilderTestBase.cs | 1498 ++++++++++++++++ .../DataAnnotationSqliteTest.cs | 8 +- test/EFCore.Tests/ApiConsistencyTest.cs | 24 +- .../ModelBuilding/ModelBuilderGenericTest.cs | 2 +- .../ModelBuilderNonGenericTest.cs | 2 +- .../ModelBuilding/NonRelationshipTestBase.cs | 8 +- 111 files changed, 8362 insertions(+), 3081 deletions(-) create mode 100644 src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.ToTable.cs create mode 100644 src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.ToView.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/ColumnBuilder.cs rename src/EFCore.Relational/Metadata/Builders/{OwnedNavigationTableBuilder`.cs => ColumnBuilder`.cs} (50%) create mode 100644 src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder``.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitViewBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitViewBuilder``.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder``.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableValuedFunctionBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableValuedFunctionBuilder``.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/OwnedNavigationViewBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/OwnedNavigationViewBuilder``.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/SplitTableBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/SplitTableBuilder`.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/SplitViewBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/SplitViewBuilder`.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/TableValuedFunctionBuilder`.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/ViewBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/ViewBuilder`.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/ViewColumnBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/ViewColumnBuilder`.cs create mode 100644 src/EFCore.Relational/Metadata/IConventionEntityTypeMappingFragment.cs create mode 100644 src/EFCore.Relational/Metadata/IConventionRelationalPropertyOverrides.cs create mode 100644 src/EFCore.Relational/Metadata/IEntityTypeMappingFragment.cs create mode 100644 src/EFCore.Relational/Metadata/IMutableEntityTypeMappingFragment.cs create mode 100644 src/EFCore.Relational/Metadata/IMutableRelationalPropertyOverrides.cs create mode 100644 src/EFCore.Relational/Metadata/IReadOnlyEntityTypeMappingFragment.cs create mode 100644 src/EFCore.Relational/Metadata/IReadOnlyRelationalPropertyOverrides.cs create mode 100644 src/EFCore.Relational/Metadata/IReadOnlyStoreObjectDictionary.cs create mode 100644 src/EFCore.Relational/Metadata/IRelationalPropertyOverrides.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/EntityTypeMappingFragment.cs delete mode 100644 src/EFCore.Relational/Metadata/Internal/IRelationalPropertyOverrides.cs create mode 100644 src/EFCore.Relational/Metadata/RuntimeEntityTypeMappingFragment.cs create mode 100644 src/EFCore.Relational/Metadata/StoreObjectDictionary.cs rename src/EFCore.SqlServer/Metadata/Builders/{OwnedNavigationTemporalTableBuilder`.cs => OwnedNavigationTemporalTableBuilder``.cs} (68%) create mode 100644 test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs create mode 100644 test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs index 88d0236e569..7e9eafcc7f4 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs @@ -324,7 +324,7 @@ private void GenerateKeyAttribute(IProperty property) private void GenerateColumnAttribute(IProperty property) { - var columnName = property.GetColumnBaseName(); + var columnName = property.GetColumnName(); var columnType = property.GetConfiguredColumnType(); var delimitedColumnName = columnName != null && columnName != property.Name ? _code.Literal(columnName) : null; diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index 1094fa08b02..3e80ae0dc5d 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -31,6 +31,7 @@ public class AnnotationCodeGenerator : IAnnotationCodeGenerator RelationalAnnotationNames.Triggers, RelationalAnnotationNames.Sequences, RelationalAnnotationNames.DbFunctions, + RelationalAnnotationNames.MappingFragments, RelationalAnnotationNames.RelationalOverrides }; @@ -161,7 +162,7 @@ public virtual void RemoveAnnotationsHandledByConventions( IProperty property, IDictionary annotations) { - var columnName = property.GetColumnBaseName(); + var columnName = property.GetColumnName(); if (columnName == property.Name) { annotations.Remove(RelationalAnnotationNames.ColumnName); diff --git a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs index dd359245331..85e3714737f 100644 --- a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs @@ -326,6 +326,24 @@ public override void Generate(IEntityType entityType, CSharpRuntimeAnnotationCod annotations[RelationalAnnotationNames.SqlQuery] = entityType.GetSqlQuery(); annotations[RelationalAnnotationNames.FunctionName] = entityType.GetFunctionName(); + if (annotations.TryGetAndRemove( + RelationalAnnotationNames.MappingFragments, + out IReadOnlyStoreObjectDictionary fragments)) + { + AddNamespace(typeof(StoreObjectDictionary), parameters.Namespaces); + AddNamespace(typeof(StoreObjectIdentifier), parameters.Namespaces); + var fragmentsVariable = Dependencies.CSharpHelper.Identifier("fragments", parameters.ScopeVariables, capitalize: false); + parameters.MainBuilder + .Append("var ").Append(fragmentsVariable).AppendLine(" = new StoreObjectDictionary();"); + + foreach (var fragment in fragments.GetValues()) + { + Create(fragment, fragmentsVariable, parameters); + } + + GenerateSimpleAnnotation(RelationalAnnotationNames.MappingFragments, fragmentsVariable, parameters); + } + if (annotations.TryGetAndRemove( RelationalAnnotationNames.Triggers, out SortedDictionary triggers)) @@ -347,6 +365,37 @@ public override void Generate(IEntityType entityType, CSharpRuntimeAnnotationCod base.Generate(entityType, parameters); } + private void Create( + IEntityTypeMappingFragment fragment, + string fragmentsVariable, + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + var storeObject = fragment.StoreObject; + var code = Dependencies.CSharpHelper; + var overrideVariable = + code.Identifier(storeObject.Name + "Fragment", parameters.ScopeVariables, capitalize: false); + var mainBuilder = parameters.MainBuilder; + mainBuilder + .Append("var ").Append(overrideVariable).AppendLine(" = new RuntimeEntityTypeMappingFragment(").IncrementIndent() + .Append(parameters.TargetName).AppendLine(","); + + AppendLiteral(storeObject, mainBuilder, code); + mainBuilder.AppendLine(",") + .Append(code.Literal(fragment.IsTableExcludedFromMigrations)).AppendLine(");").DecrementIndent(); + + CreateAnnotations( + fragment, + GenerateOverrides, + parameters with { TargetName = overrideVariable }); + + mainBuilder.Append(fragmentsVariable).Append(".Add("); + AppendLiteral(storeObject, mainBuilder, code); + + mainBuilder + .Append(", ") + .Append(overrideVariable).AppendLine(");"); + } + private void Create(ITrigger trigger, string triggersVariable, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { var code = Dependencies.CSharpHelper; @@ -410,16 +459,17 @@ public override void Generate(IProperty property, CSharpRuntimeAnnotationCodeGen if (annotations.TryGetAndRemove( RelationalAnnotationNames.RelationalOverrides, - out SortedDictionary overrides)) + out IReadOnlyStoreObjectDictionary tableOverrides)) { - parameters.Namespaces.Add(typeof(SortedDictionary).Namespace!); + AddNamespace(typeof(StoreObjectDictionary), parameters.Namespaces); + AddNamespace(typeof(StoreObjectIdentifier), parameters.Namespaces); var overridesVariable = Dependencies.CSharpHelper.Identifier("overrides", parameters.ScopeVariables, capitalize: false); parameters.MainBuilder - .Append("var ").Append(overridesVariable).AppendLine(" = new SortedDictionary();"); + .Append("var ").Append(overridesVariable).AppendLine(" = new StoreObjectDictionary();"); - foreach (var (key, value) in overrides) + foreach (var overrides in tableOverrides.GetValues()) { - Create((IRelationalPropertyOverrides)value, key, overridesVariable, parameters); + Create(overrides, overridesVariable, parameters); } GenerateSimpleAnnotation(RelationalAnnotationNames.RelationalOverrides, overridesVariable, parameters); @@ -431,17 +481,20 @@ public override void Generate(IProperty property, CSharpRuntimeAnnotationCodeGen private void Create( IRelationalPropertyOverrides overrides, - StoreObjectIdentifier storeObject, string overridesVariable, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { + var storeObject = overrides.StoreObject; var code = Dependencies.CSharpHelper; var overrideVariable = code.Identifier(parameters.TargetName + Capitalize(storeObject.Name), parameters.ScopeVariables, capitalize: false); var mainBuilder = parameters.MainBuilder; mainBuilder .Append("var ").Append(overrideVariable).AppendLine(" = new RuntimeRelationalPropertyOverrides(").IncrementIndent() - .Append(parameters.TargetName).AppendLine(",") + .Append(parameters.TargetName).AppendLine(","); + AppendLiteral(storeObject, mainBuilder, code); + + mainBuilder.AppendLine(",") .Append(code.Literal(overrides.ColumnNameOverridden)).AppendLine(",") .Append(code.UnknownLiteral(overrides.ColumnName)).AppendLine(");").DecrementIndent(); @@ -450,33 +503,12 @@ private void Create( GenerateOverrides, parameters with { TargetName = overrideVariable }); - mainBuilder.Append(overridesVariable).Append("[StoreObjectIdentifier."); - - switch (storeObject.StoreObjectType) - { - case StoreObjectType.Table: - mainBuilder - .Append("Table(").Append(code.Literal(storeObject.Name)) - .Append(", ").Append(code.UnknownLiteral(storeObject.Schema)).Append(")"); - break; - case StoreObjectType.View: - mainBuilder - .Append("View(").Append(code.Literal(storeObject.Name)) - .Append(", ").Append(code.UnknownLiteral(storeObject.Schema)).Append(")"); - break; - case StoreObjectType.SqlQuery: - mainBuilder - .Append("SqlQuery(").Append(code.Literal(storeObject.Name)).Append(")"); - break; - case StoreObjectType.Function: - mainBuilder - .Append("DbFunction(").Append(code.Literal(storeObject.Name)).Append(")"); - break; - } + mainBuilder.Append(overridesVariable).Append(".Add("); + AppendLiteral(storeObject, mainBuilder, code); mainBuilder - .Append("] = ") - .Append(overrideVariable).AppendLine(";"); + .Append(", ") + .Append(overrideVariable).AppendLine(");"); } /// @@ -553,4 +585,33 @@ private static string Capitalize(string @string) return char.ToUpperInvariant(@string[0]) + @string[1..]; } } + + private static void AppendLiteral(StoreObjectIdentifier storeObject, IndentedStringBuilder builder, ICSharpHelper code) + { + builder.Append("StoreObjectIdentifier."); + switch (storeObject.StoreObjectType) + { + case StoreObjectType.Table: + builder + .Append("Table(").Append(code.Literal(storeObject.Name)) + .Append(", ").Append(code.UnknownLiteral(storeObject.Schema)).Append(")"); + break; + case StoreObjectType.View: + builder + .Append("View(").Append(code.Literal(storeObject.Name)) + .Append(", ").Append(code.UnknownLiteral(storeObject.Schema)).Append(")"); + break; + case StoreObjectType.SqlQuery: + builder + .Append("SqlQuery(").Append(code.Literal(storeObject.Name)).Append(")"); + break; + case StoreObjectType.Function: + builder + .Append("DbFunction(").Append(code.Literal(storeObject.Name)).Append(")"); + break; + default: + Check.DebugAssert(false, "Unexpected StoreObjectType: " + storeObject.StoreObjectType); + break; + } + } } diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.ToTable.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.ToTable.cs new file mode 100644 index 00000000000..904d53978a2 --- /dev/null +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.ToTable.cs @@ -0,0 +1,804 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore; + +/// +/// Relational database specific extension methods for . +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public static partial class RelationalEntityTypeBuilderExtensions +{ + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the table. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToTable( + this EntityTypeBuilder entityTypeBuilder, + string? name) + { + Check.NullButNotEmpty(name, nameof(name)); + + entityTypeBuilder.Metadata.SetTableName(name); + entityTypeBuilder.Metadata.SetSchema(null); + + return entityTypeBuilder; + } + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToTable( + this EntityTypeBuilder entityTypeBuilder, + Action buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(new (null, entityTypeBuilder)); + + return entityTypeBuilder; + } + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the table. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToTable( + this EntityTypeBuilder entityTypeBuilder, + string name, + Action buildAction) + { + Check.NotNull(name, nameof(name)); + Check.NotNull(buildAction, nameof(buildAction)); + + entityTypeBuilder.Metadata.SetTableName(name); + entityTypeBuilder.Metadata.SetSchema(null); + buildAction(new (StoreObjectIdentifier.Table(name, null), entityTypeBuilder)); + + return entityTypeBuilder; + } + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type being configured. + /// The builder for the entity type being configured. + /// The name of the table. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToTable( + this EntityTypeBuilder entityTypeBuilder, + string? name) + where TEntity : class + => (EntityTypeBuilder)((EntityTypeBuilder)entityTypeBuilder).ToTable(name); + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type being configured. + /// The builder for the entity type being configured. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToTable( + this EntityTypeBuilder entityTypeBuilder, + Action> buildAction) + where TEntity : class + { + Check.NotNull(buildAction, nameof(buildAction)); + + var entityTypeConventionBuilder = entityTypeBuilder.GetInfrastructure(); + if (entityTypeConventionBuilder.Metadata[RelationalAnnotationNames.TableName] == null) + { + entityTypeConventionBuilder.ToTable(entityTypeBuilder.Metadata.GetDefaultTableName()); + } + + buildAction(new (null, entityTypeBuilder)); + + return entityTypeBuilder; + } + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type being configured. + /// The builder for the entity type being configured. + /// The name of the table. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToTable( + this EntityTypeBuilder entityTypeBuilder, + string name, + Action> buildAction) + where TEntity : class + { + Check.NotNull(name, nameof(name)); + Check.NotNull(buildAction, nameof(buildAction)); + + entityTypeBuilder.Metadata.SetTableName(name); + entityTypeBuilder.Metadata.SetSchema(null); + buildAction(new (StoreObjectIdentifier.Table(name, null), entityTypeBuilder)); + + return entityTypeBuilder; + } + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the table. + /// The schema of the table. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToTable( + this EntityTypeBuilder entityTypeBuilder, + string name, + string? schema) + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + + entityTypeBuilder.Metadata.SetTableName(name); + entityTypeBuilder.Metadata.SetSchema(schema); + return entityTypeBuilder; + } + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the table. + /// The schema of the table. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToTable( + this EntityTypeBuilder entityTypeBuilder, + string name, + string? schema, + Action buildAction) + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + Check.NotNull(buildAction, nameof(buildAction)); + + entityTypeBuilder.Metadata.SetTableName(name); + entityTypeBuilder.Metadata.SetSchema(schema); + buildAction(new (StoreObjectIdentifier.Table(name, schema), entityTypeBuilder)); + + return entityTypeBuilder; + } + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type being configured. + /// The builder for the entity type being configured. + /// The name of the table. + /// The schema of the table. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToTable( + this EntityTypeBuilder entityTypeBuilder, + string name, + string? schema) + where TEntity : class + => (EntityTypeBuilder)((EntityTypeBuilder)entityTypeBuilder).ToTable(name, schema); + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type being configured. + /// The builder for the entity type being configured. + /// The name of the table. + /// The schema of the table. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToTable( + this EntityTypeBuilder entityTypeBuilder, + string name, + string? schema, + Action> buildAction) + where TEntity : class + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + Check.NotNull(buildAction, nameof(buildAction)); + + entityTypeBuilder.Metadata.SetTableName(name); + entityTypeBuilder.Metadata.SetSchema(schema); + buildAction(new (StoreObjectIdentifier.Table(name, schema), entityTypeBuilder)); + + return entityTypeBuilder; + } + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the table. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, + string? name) + { + Check.NullButNotEmpty(name, nameof(name)); + + ownedNavigationBuilder.OwnedEntityType.SetTableName(name); + ownedNavigationBuilder.OwnedEntityType.SetSchema(null); + + return ownedNavigationBuilder; + } + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, + Action buildAction) + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(new (null, ownedNavigationBuilder)); + + return ownedNavigationBuilder; + } + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. + /// The builder for the entity type being configured. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, + Action> buildAction) + where TOwnerEntity : class + where TDependentEntity : class + { + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(new (null, ownedNavigationBuilder)); + + return ownedNavigationBuilder; + } + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. + /// The builder for the entity type being configured. + /// The name of the table. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, + string? name) + where TOwnerEntity : class + where TDependentEntity : class + => (OwnedNavigationBuilder)((OwnedNavigationBuilder)ownedNavigationBuilder).ToTable(name); + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the table. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + Action buildAction) + { + Check.NotNull(name, nameof(name)); + Check.NotNull(buildAction, nameof(buildAction)); + + ownedNavigationBuilder.OwnedEntityType.SetTableName(name); + ownedNavigationBuilder.OwnedEntityType.SetSchema(null); + buildAction(new (StoreObjectIdentifier.Table(name, null), ownedNavigationBuilder)); + + return ownedNavigationBuilder; + } + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. + /// The builder for the entity type being configured. + /// The name of the table. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + Action> buildAction) + where TOwnerEntity : class + where TDependentEntity : class + { + Check.NotNull(name, nameof(name)); + Check.NotNull(buildAction, nameof(buildAction)); + + ownedNavigationBuilder.OwnedEntityType.SetTableName(name); + ownedNavigationBuilder.OwnedEntityType.SetSchema(null); + buildAction(new (StoreObjectIdentifier.Table(name, null), ownedNavigationBuilder)); + + return ownedNavigationBuilder; + } + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the table. + /// The schema of the table. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + string? schema) + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + + ownedNavigationBuilder.OwnedEntityType.SetTableName(name); + ownedNavigationBuilder.OwnedEntityType.SetSchema(schema); + + return ownedNavigationBuilder; + } + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the table. + /// The schema of the table. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + string? schema, + Action buildAction) + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + Check.NotNull(buildAction, nameof(buildAction)); + + ownedNavigationBuilder.OwnedEntityType.SetTableName(name); + ownedNavigationBuilder.OwnedEntityType.SetSchema(schema); + buildAction(new (StoreObjectIdentifier.Table(name, schema), ownedNavigationBuilder)); + + return ownedNavigationBuilder; + } + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. + /// The builder for the entity type being configured. + /// The name of the table. + /// The schema of the table. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + string? schema) + where TOwnerEntity : class + where TDependentEntity : class + => (OwnedNavigationBuilder)((OwnedNavigationBuilder)ownedNavigationBuilder).ToTable( + name, schema); + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. + /// The builder for the entity type being configured. + /// The name of the table. + /// The schema of the table. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + string? schema, + Action> buildAction) + where TOwnerEntity : class + where TDependentEntity : class + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + Check.NotNull(buildAction, nameof(buildAction)); + + ownedNavigationBuilder.OwnedEntityType.SetTableName(name); + ownedNavigationBuilder.OwnedEntityType.SetSchema(schema); + buildAction(new (StoreObjectIdentifier.Table(name, schema), ownedNavigationBuilder)); + + return ownedNavigationBuilder; + } + + /// + /// Configures some of the properties on this entity type to be mapped to a different table. + /// The primary key properties are mapped to all tables, other properties must be explicitly mapped. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the table. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder SplitToTable( + this EntityTypeBuilder entityTypeBuilder, + string name, + Action buildAction) + => entityTypeBuilder.SplitToTable(name, null, buildAction); + + /// + /// Configures some of the properties on this entity type to be mapped to a different table. + /// The primary key properties are mapped to all tables, other properties must be explicitly mapped. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type being configured. + /// The builder for the entity type being configured. + /// The name of the table. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder SplitToTable( + this EntityTypeBuilder entityTypeBuilder, + string name, + Action> buildAction) + where TEntity : class + => entityTypeBuilder.SplitToTable(name, null, buildAction); + + /// + /// Configures some of the properties on this entity type to be mapped to a different table. + /// The primary key properties are mapped to all tables, other properties must be explicitly mapped. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the table. + /// The schema of the table. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder SplitToTable( + this EntityTypeBuilder entityTypeBuilder, + string name, + string? schema, + Action buildAction) + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(new(StoreObjectIdentifier.Table(name, schema), entityTypeBuilder)); + + return entityTypeBuilder; + } + + /// + /// Configures some of the properties on this entity type to be mapped to a different table. + /// The primary key properties are mapped to all tables, other properties must be explicitly mapped. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type being configured. + /// The builder for the entity type being configured. + /// The name of the table. + /// The schema of the table. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder SplitToTable( + this EntityTypeBuilder entityTypeBuilder, + string name, + string? schema, + Action> buildAction) + where TEntity : class + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(new(StoreObjectIdentifier.Table(name, schema), entityTypeBuilder)); + + return entityTypeBuilder; + } + + /// + /// Configures some of the properties on this entity type to be mapped to a different table. + /// The primary key properties are mapped to all tables, other properties must be explicitly mapped. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the table. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder SplitToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + Action buildAction) + => ownedNavigationBuilder.SplitToTable(name, null, buildAction); + + /// + /// Configures some of the properties on this entity type to be mapped to a different table. + /// The primary key properties are mapped to all tables, other properties must be explicitly mapped. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. + /// The builder for the entity type being configured. + /// The name of the table. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder SplitToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + Action> buildAction) + where TOwnerEntity : class + where TDependentEntity : class + => ownedNavigationBuilder.SplitToTable(name, null, buildAction); + + /// + /// Configures some of the properties on this entity type to be mapped to a different table. + /// The primary key properties are mapped to all tables, other properties must be explicitly mapped. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the table. + /// The schema of the table. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder SplitToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + string? schema, + Action buildAction) + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(new (StoreObjectIdentifier.Table(name, schema), ownedNavigationBuilder)); + + return ownedNavigationBuilder; + } + + /// + /// Configures some of the properties on this entity type to be mapped to a different table. + /// The primary key properties are mapped to all tables, other properties must be explicitly mapped. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. + /// The builder for the entity type being configured. + /// The name of the table. + /// The schema of the table. + /// An action that performs configuration of the table. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder SplitToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + string? schema, + Action> buildAction) + where TOwnerEntity : class + where TDependentEntity : class + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(new (StoreObjectIdentifier.Table(name, schema), ownedNavigationBuilder)); + + return ownedNavigationBuilder; + } + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the table. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, otherwise. + /// + public static IConventionEntityTypeBuilder? ToTable( + this IConventionEntityTypeBuilder entityTypeBuilder, + string? name, + bool fromDataAnnotation = false) + { + if (!entityTypeBuilder.CanSetTable(name, fromDataAnnotation)) + { + return null; + } + + entityTypeBuilder.Metadata.SetTableName(name, fromDataAnnotation); + return entityTypeBuilder; + } + + /// + /// Configures the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the table. + /// The schema of the table. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, otherwise. + /// + public static IConventionEntityTypeBuilder? ToTable( + this IConventionEntityTypeBuilder entityTypeBuilder, + string? name, + string? schema, + bool fromDataAnnotation = false) + { + if (!entityTypeBuilder.CanSetTable(name, fromDataAnnotation) + || !entityTypeBuilder.CanSetSchema(schema, fromDataAnnotation)) + { + return null; + } + + entityTypeBuilder.Metadata.SetTableName(name, fromDataAnnotation); + entityTypeBuilder.Metadata.SetSchema(schema, fromDataAnnotation); + return entityTypeBuilder; + } + + /// + /// Returns a value indicating whether the table name can be set for this entity type + /// using the specified configuration source. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the table. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + public static bool CanSetTable( + this IConventionEntityTypeBuilder entityTypeBuilder, + string? name, + bool fromDataAnnotation = false) + { + Check.NullButNotEmpty(name, nameof(name)); + + return entityTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.TableName, name, fromDataAnnotation); + } + + /// + /// Configures the schema of the table that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The schema of the table. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, otherwise. + /// + public static IConventionEntityTypeBuilder? ToSchema( + this IConventionEntityTypeBuilder entityTypeBuilder, + string? schema, + bool fromDataAnnotation = false) + { + if (!entityTypeBuilder.CanSetSchema(schema, fromDataAnnotation)) + { + return null; + } + + entityTypeBuilder.Metadata.SetSchema(schema, fromDataAnnotation); + return entityTypeBuilder; + } + + /// + /// Returns a value indicating whether the schema of the table name can be set for this entity type + /// using the specified configuration source. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The schema of the table. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + public static bool CanSetSchema( + this IConventionEntityTypeBuilder entityTypeBuilder, + string? schema, + bool fromDataAnnotation = false) + { + Check.NullButNotEmpty(schema, nameof(schema)); + + return entityTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.Schema, schema, fromDataAnnotation); + } +} diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.ToView.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.ToView.cs new file mode 100644 index 00000000000..0f1981767f2 --- /dev/null +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.ToView.cs @@ -0,0 +1,668 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore; + +/// +/// Relational database specific extension methods for . +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public static partial class RelationalEntityTypeBuilderExtensions +{ + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToView( + this EntityTypeBuilder entityTypeBuilder, + string? name) + => entityTypeBuilder.ToView(name, (string?)null); + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type being configured. + /// The builder for the entity type being configured. + /// The name of the view. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToView( + this EntityTypeBuilder entityTypeBuilder, + string? name) + where TEntity : class + => (EntityTypeBuilder)ToView((EntityTypeBuilder)entityTypeBuilder, name); + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// The schema of the view. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToView( + this EntityTypeBuilder entityTypeBuilder, + string? name, + string? schema) + { + Check.NullButNotEmpty(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + + entityTypeBuilder.Metadata.SetViewName(name); + entityTypeBuilder.Metadata.SetViewSchema(schema); + entityTypeBuilder.Metadata.SetAnnotation(RelationalAnnotationNames.ViewDefinitionSql, null); + + return entityTypeBuilder; + } + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type being configured. + /// The builder for the entity type being configured. + /// The name of the view. + /// The schema of the view. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToView( + this EntityTypeBuilder entityTypeBuilder, + string? name, + string? schema) + where TEntity : class + => (EntityTypeBuilder)ToView((EntityTypeBuilder)entityTypeBuilder, name, schema); + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// An action that performs configuration of the view. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToView( + this EntityTypeBuilder entityTypeBuilder, + string name, + Action buildAction) + => entityTypeBuilder.ToView(name, null, buildAction); + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type being configured. + /// The builder for the entity type being configured. + /// The name of the view. + /// An action that performs configuration of the view. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToView( + this EntityTypeBuilder entityTypeBuilder, + string name, + Action> buildAction) + where TEntity : class + => ToView(entityTypeBuilder, name, null, buildAction); + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// The schema of the view. + /// An action that performs configuration of the view. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToView( + this EntityTypeBuilder entityTypeBuilder, + string name, + string? schema, + Action buildAction) + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + + entityTypeBuilder.Metadata.SetViewName(name); + entityTypeBuilder.Metadata.SetViewSchema(schema); + entityTypeBuilder.Metadata.SetAnnotation(RelationalAnnotationNames.ViewDefinitionSql, null); + buildAction(new(StoreObjectIdentifier.View(name, schema), entityTypeBuilder)); + + return entityTypeBuilder; + } + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type being configured. + /// The builder for the entity type being configured. + /// The name of the view. + /// The schema of the view. + /// An action that performs configuration of the view. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder ToView( + this EntityTypeBuilder entityTypeBuilder, + string name, + string? schema, + Action> buildAction) + where TEntity : class + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + + entityTypeBuilder.Metadata.SetViewName(name); + entityTypeBuilder.Metadata.SetViewSchema(schema); + entityTypeBuilder.Metadata.SetAnnotation(RelationalAnnotationNames.ViewDefinitionSql, null); + buildAction(new(StoreObjectIdentifier.View(name, schema), entityTypeBuilder)); + + return entityTypeBuilder; + } + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToView( + this OwnedNavigationBuilder ownedNavigationBuilder, + string? name) + => ownedNavigationBuilder.ToView(name, (string?)null); + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. + /// The builder for the entity type being configured. + /// The name of the view. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToView( + this OwnedNavigationBuilder ownedNavigationBuilder, + string? name) + where TOwnerEntity : class + where TDependentEntity : class + => (OwnedNavigationBuilder)ToView((OwnedNavigationBuilder)ownedNavigationBuilder, name); + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// The schema of the view. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToView( + this OwnedNavigationBuilder ownedNavigationBuilder, + string? name, + string? schema) + { + Check.NullButNotEmpty(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + + ownedNavigationBuilder.OwnedEntityType.SetViewName(name); + ownedNavigationBuilder.OwnedEntityType.SetViewSchema(schema); + ownedNavigationBuilder.OwnedEntityType.SetAnnotation(RelationalAnnotationNames.ViewDefinitionSql, null); + + return ownedNavigationBuilder; + } + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. + /// The builder for the entity type being configured. + /// The name of the view. + /// The schema of the view. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToView( + this OwnedNavigationBuilder ownedNavigationBuilder, + string? name, + string? schema) + where TOwnerEntity : class + where TDependentEntity : class + => (OwnedNavigationBuilder)ToView( + (OwnedNavigationBuilder)ownedNavigationBuilder, name, schema); + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// An action that performs configuration of the view. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToView( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + Action buildAction) + => ownedNavigationBuilder.ToView(name, null, buildAction); + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. + /// The builder for the entity type being configured. + /// The name of the view. + /// An action that performs configuration of the view. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToView( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + Action> buildAction) + where TOwnerEntity : class + where TDependentEntity : class + => ToView(ownedNavigationBuilder, name, null, buildAction); + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// The schema of the view. + /// An action that performs configuration of the view. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToView( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + string? schema, + Action buildAction) + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + + ownedNavigationBuilder.OwnedEntityType.SetViewName(name); + ownedNavigationBuilder.OwnedEntityType.SetViewSchema(schema); + ownedNavigationBuilder.OwnedEntityType.SetAnnotation(RelationalAnnotationNames.ViewDefinitionSql, null); + buildAction(new(StoreObjectIdentifier.View(name, schema), ownedNavigationBuilder)); + + return ownedNavigationBuilder; + } + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. + /// The builder for the entity type being configured. + /// The name of the view. + /// The schema of the view. + /// An action that performs configuration of the view. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder ToView( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + string? schema, + Action> buildAction) + where TOwnerEntity : class + where TDependentEntity : class + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + + ownedNavigationBuilder.OwnedEntityType.SetViewName(name); + ownedNavigationBuilder.OwnedEntityType.SetViewSchema(schema); + ownedNavigationBuilder.OwnedEntityType.SetAnnotation(RelationalAnnotationNames.ViewDefinitionSql, null); + buildAction(new(StoreObjectIdentifier.View(name, schema), ownedNavigationBuilder)); + + return ownedNavigationBuilder; + } + + /// + /// Configures some of the properties on this entity type to be mapped to a different view. + /// The primary key properties are mapped to all views, other properties must be explicitly mapped. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// An action that performs configuration of the view. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder SplitToView( + this EntityTypeBuilder entityTypeBuilder, + string name, + Action buildAction) + => entityTypeBuilder.SplitToView(name, null, buildAction); + + /// + /// Configures some of the properties on this entity type to be mapped to a different view. + /// The primary key properties are mapped to all views, other properties must be explicitly mapped. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type being configured. + /// The builder for the entity type being configured. + /// The name of the view. + /// An action that performs configuration of the view. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder SplitToView( + this EntityTypeBuilder entityTypeBuilder, + string name, + Action> buildAction) + where TEntity : class + => entityTypeBuilder.SplitToView(name, null, buildAction); + + /// + /// Configures some of the properties on this entity type to be mapped to a different view. + /// The primary key properties are mapped to all views, other properties must be explicitly mapped. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// The schema of the view. + /// An action that performs configuration of the view. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder SplitToView( + this EntityTypeBuilder ownedNavigationBuilder, + string name, + string? schema, + Action buildAction) + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(new(StoreObjectIdentifier.View(name, schema), ownedNavigationBuilder)); + + return ownedNavigationBuilder; + } + + /// + /// Configures some of the properties on this entity type to be mapped to a different view. + /// The primary key properties are mapped to all views, other properties must be explicitly mapped. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type being configured. + /// The builder for the entity type being configured. + /// The name of the view. + /// The schema of the view. + /// An action that performs configuration of the view. + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder SplitToView( + this EntityTypeBuilder entityTypeBuilder, + string name, + string? schema, + Action> buildAction) + where TEntity : class + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(new(StoreObjectIdentifier.View(name, schema), entityTypeBuilder)); + + return entityTypeBuilder; + } + + /// + /// Configures some of the properties on this entity type to be mapped to a different view. + /// The primary key properties are mapped to all views, other properties must be explicitly mapped. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// An action that performs configuration of the view. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder SplitToView( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + Action buildAction) + => ownedNavigationBuilder.SplitToView(name, null, buildAction); + + /// + /// Configures some of the properties on this entity type to be mapped to a different view. + /// The primary key properties are mapped to all views, other properties must be explicitly mapped. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. + /// The builder for the entity type being configured. + /// The name of the view. + /// An action that performs configuration of the view. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder SplitToView( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + Action> buildAction) + where TOwnerEntity : class + where TDependentEntity : class + => ownedNavigationBuilder.SplitToView(name, null, buildAction); + + /// + /// Configures some of the properties on this entity type to be mapped to a different view. + /// The primary key properties are mapped to all views, other properties must be explicitly mapped. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// The schema of the view. + /// An action that performs configuration of the view. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder SplitToView( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + string? schema, + Action buildAction) + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(new (StoreObjectIdentifier.View(name, schema), ownedNavigationBuilder)); + + return ownedNavigationBuilder; + } + + /// + /// Configures some of the properties on this entity type to be mapped to a different view. + /// The primary key properties are mapped to all views, other properties must be explicitly mapped. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. + /// The builder for the entity type being configured. + /// The name of the view. + /// The schema of the view. + /// An action that performs configuration of the view. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationBuilder SplitToView( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + string? schema, + Action> buildAction) + where TOwnerEntity : class + where TDependentEntity : class + { + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); + Check.NotNull(buildAction, nameof(buildAction)); + + buildAction(new (StoreObjectIdentifier.View(name, schema), ownedNavigationBuilder)); + + return ownedNavigationBuilder; + } + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, otherwise. + /// + public static IConventionEntityTypeBuilder? ToView( + this IConventionEntityTypeBuilder entityTypeBuilder, + string? name, + bool fromDataAnnotation = false) + { + if (!entityTypeBuilder.CanSetView(name, fromDataAnnotation)) + { + return null; + } + + entityTypeBuilder.Metadata.SetViewName(name, fromDataAnnotation); + return entityTypeBuilder; + } + + /// + /// Configures the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// The schema of the view. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, otherwise. + /// + public static IConventionEntityTypeBuilder? ToView( + this IConventionEntityTypeBuilder entityTypeBuilder, + string? name, + string? schema, + bool fromDataAnnotation = false) + { + if (!entityTypeBuilder.CanSetView(name, fromDataAnnotation) + || !entityTypeBuilder.CanSetViewSchema(schema, fromDataAnnotation)) + { + return null; + } + + entityTypeBuilder.Metadata.SetViewName(name, fromDataAnnotation); + entityTypeBuilder.Metadata.SetViewSchema(schema, fromDataAnnotation); + return entityTypeBuilder; + } + + /// + /// Returns a value indicating whether the view name can be set for this entity type + /// using the specified configuration source. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The name of the view. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + public static bool CanSetView( + this IConventionEntityTypeBuilder entityTypeBuilder, + string? name, + bool fromDataAnnotation = false) + { + Check.NullButNotEmpty(name, nameof(name)); + + return entityTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.ViewName, name, fromDataAnnotation); + } + + /// + /// Configures the schema of the view that the entity type maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The schema of the view. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, otherwise. + /// + public static IConventionEntityTypeBuilder? ToViewSchema( + this IConventionEntityTypeBuilder entityTypeBuilder, + string? schema, + bool fromDataAnnotation = false) + { + if (!entityTypeBuilder.CanSetSchema(schema, fromDataAnnotation)) + { + return null; + } + + entityTypeBuilder.Metadata.SetViewSchema(schema, fromDataAnnotation); + return entityTypeBuilder; + } + + /// + /// Returns a value indicating whether the schema of the view can be set for this entity type + /// using the specified configuration source. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The builder for the entity type being configured. + /// The schema of the view. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + public static bool CanSetViewSchema( + this IConventionEntityTypeBuilder entityTypeBuilder, + string? schema, + bool fromDataAnnotation = false) + { + Check.NullButNotEmpty(schema, nameof(schema)); + + return entityTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.ViewSchema, schema, fromDataAnnotation); + } +} diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs index c899390b9bb..853202cc686 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs @@ -13,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore; /// /// See Modeling entity types and relationships for more information and examples. /// -public static class RelationalEntityTypeBuilderExtensions +public static partial class RelationalEntityTypeBuilderExtensions { /// /// Configures TPC as the mapping strategy for the derived types. Each type will be mapped to a different database object. @@ -78,607 +78,11 @@ public static EntityTypeBuilder UseTphMappingStrategy(this Ent /// Configures TPT as the mapping strategy for the derived types. Each type will be mapped to a different database object. /// Only the declared properties will be mapped to columns on the corresponding object. /// - /// - /// See Database migrations for more information and examples. - /// - /// The entity type being configured. - /// The builder for the entity type being configured. - /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder UseTptMappingStrategy(this EntityTypeBuilder entityTypeBuilder) - where TEntity : class - => (EntityTypeBuilder)((EntityTypeBuilder)entityTypeBuilder).UseTptMappingStrategy(); - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the table. - /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder ToTable( - this EntityTypeBuilder entityTypeBuilder, - string? name) - { - Check.NullButNotEmpty(name, nameof(name)); - - entityTypeBuilder.Metadata.SetTableName(name); - entityTypeBuilder.Metadata.SetSchema(null); - - return entityTypeBuilder; - } - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// An action that performs configuration of the table. - /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder ToTable( - this EntityTypeBuilder entityTypeBuilder, - Action buildAction) - { - Check.NotNull(buildAction, nameof(buildAction)); - - buildAction(new TableBuilder(null, null, entityTypeBuilder)); - - return entityTypeBuilder; - } - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the table. - /// An action that performs configuration of the table. - /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder ToTable( - this EntityTypeBuilder entityTypeBuilder, - string? name, - Action buildAction) - { - Check.NullButNotEmpty(name, nameof(name)); - Check.NotNull(buildAction, nameof(buildAction)); - - entityTypeBuilder.Metadata.SetTableName(name); - entityTypeBuilder.Metadata.SetSchema(null); - buildAction(new TableBuilder(name, null, entityTypeBuilder)); - - return entityTypeBuilder; - } - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The entity type being configured. - /// The builder for the entity type being configured. - /// The name of the table. - /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder ToTable( - this EntityTypeBuilder entityTypeBuilder, - string? name) - where TEntity : class - => (EntityTypeBuilder)((EntityTypeBuilder)entityTypeBuilder).ToTable(name); - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The entity type being configured. - /// The builder for the entity type being configured. - /// An action that performs configuration of the table. - /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder ToTable( - this EntityTypeBuilder entityTypeBuilder, - Action> buildAction) - where TEntity : class - { - Check.NotNull(buildAction, nameof(buildAction)); - - var entityTypeConventionBuilder = entityTypeBuilder.GetInfrastructure(); - if (entityTypeConventionBuilder.Metadata[RelationalAnnotationNames.TableName] == null) - { - entityTypeConventionBuilder.ToTable(entityTypeBuilder.Metadata.GetDefaultTableName()); - } - - buildAction(new TableBuilder(null, null, entityTypeBuilder)); - - return entityTypeBuilder; - } - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The entity type being configured. - /// The builder for the entity type being configured. - /// The name of the table. - /// An action that performs configuration of the table. - /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder ToTable( - this EntityTypeBuilder entityTypeBuilder, - string? name, - Action> buildAction) - where TEntity : class - { - Check.NullButNotEmpty(name, nameof(name)); - Check.NotNull(buildAction, nameof(buildAction)); - - entityTypeBuilder.Metadata.SetTableName(name); - entityTypeBuilder.Metadata.SetSchema(null); - buildAction(new TableBuilder(name, null, entityTypeBuilder)); - - return entityTypeBuilder; - } - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the table. - /// The schema of the table. - /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder ToTable( - this EntityTypeBuilder entityTypeBuilder, - string name, - string? schema) - { - Check.NotNull(name, nameof(name)); - Check.NullButNotEmpty(schema, nameof(schema)); - - entityTypeBuilder.Metadata.SetTableName(name); - entityTypeBuilder.Metadata.SetSchema(schema); - return entityTypeBuilder; - } - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the table. - /// The schema of the table. - /// An action that performs configuration of the table. - /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder ToTable( - this EntityTypeBuilder entityTypeBuilder, - string name, - string? schema, - Action buildAction) - { - Check.NotNull(name, nameof(name)); - Check.NullButNotEmpty(schema, nameof(schema)); - Check.NotNull(buildAction, nameof(buildAction)); - - entityTypeBuilder.Metadata.SetTableName(name); - entityTypeBuilder.Metadata.SetSchema(schema); - buildAction(new TableBuilder(name, schema, entityTypeBuilder)); - - return entityTypeBuilder; - } - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The entity type being configured. - /// The builder for the entity type being configured. - /// The name of the table. - /// The schema of the table. - /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder ToTable( - this EntityTypeBuilder entityTypeBuilder, - string name, - string? schema) - where TEntity : class - => (EntityTypeBuilder)((EntityTypeBuilder)entityTypeBuilder).ToTable(name, schema); - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The entity type being configured. - /// The builder for the entity type being configured. - /// The name of the table. - /// The schema of the table. - /// An action that performs configuration of the table. - /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder ToTable( - this EntityTypeBuilder entityTypeBuilder, - string name, - string? schema, - Action> buildAction) - where TEntity : class - { - Check.NotNull(name, nameof(name)); - Check.NullButNotEmpty(schema, nameof(schema)); - Check.NotNull(buildAction, nameof(buildAction)); - - entityTypeBuilder.Metadata.SetTableName(name); - entityTypeBuilder.Metadata.SetSchema(schema); - buildAction(new TableBuilder(name, schema, entityTypeBuilder)); - - return entityTypeBuilder; - } - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the table. - /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToTable( - this OwnedNavigationBuilder referenceOwnershipBuilder, - string? name) - { - Check.NullButNotEmpty(name, nameof(name)); - - referenceOwnershipBuilder.OwnedEntityType.SetTableName(name); - referenceOwnershipBuilder.OwnedEntityType.SetSchema(null); - - return referenceOwnershipBuilder; - } - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// An action that performs configuration of the table. - /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToTable( - this OwnedNavigationBuilder referenceOwnershipBuilder, - Action buildAction) - { - Check.NotNull(buildAction, nameof(buildAction)); - - buildAction(new OwnedNavigationTableBuilder(null, null, referenceOwnershipBuilder)); - - return referenceOwnershipBuilder; - } - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// An action that performs configuration of the table. - /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToTable( - this OwnedNavigationBuilder referenceOwnershipBuilder, - Action> buildAction) - where TOwnerEntity : class - where TRelatedEntity : class - { - Check.NotNull(buildAction, nameof(buildAction)); - - buildAction(new OwnedNavigationTableBuilder(null, null, referenceOwnershipBuilder)); - - return referenceOwnershipBuilder; - } - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the table. - /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToTable( - this OwnedNavigationBuilder referenceOwnershipBuilder, - string? name) - where TOwnerEntity : class - where TRelatedEntity : class - => (OwnedNavigationBuilder)((OwnedNavigationBuilder)referenceOwnershipBuilder).ToTable(name); - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the table. - /// An action that performs configuration of the table. - /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToTable( - this OwnedNavigationBuilder referenceOwnershipBuilder, - string? name, - Action buildAction) - { - Check.NullButNotEmpty(name, nameof(name)); - Check.NotNull(buildAction, nameof(buildAction)); - - referenceOwnershipBuilder.OwnedEntityType.SetTableName(name); - referenceOwnershipBuilder.OwnedEntityType.SetSchema(null); - buildAction(new OwnedNavigationTableBuilder(name, null, referenceOwnershipBuilder)); - - return referenceOwnershipBuilder; - } - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the table. - /// An action that performs configuration of the table. - /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToTable( - this OwnedNavigationBuilder referenceOwnershipBuilder, - string? name, - Action> buildAction) - where TOwnerEntity : class - where TRelatedEntity : class - { - Check.NullButNotEmpty(name, nameof(name)); - Check.NotNull(buildAction, nameof(buildAction)); - - referenceOwnershipBuilder.OwnedEntityType.SetTableName(name); - referenceOwnershipBuilder.OwnedEntityType.SetSchema(null); - buildAction(new OwnedNavigationTableBuilder(name, null, referenceOwnershipBuilder)); - - return referenceOwnershipBuilder; - } - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the table. - /// The schema of the table. - /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToTable( - this OwnedNavigationBuilder referenceOwnershipBuilder, - string name, - string? schema) - { - Check.NotNull(name, nameof(name)); - Check.NullButNotEmpty(schema, nameof(schema)); - - referenceOwnershipBuilder.OwnedEntityType.SetTableName(name); - referenceOwnershipBuilder.OwnedEntityType.SetSchema(schema); - - return referenceOwnershipBuilder; - } - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the table. - /// The schema of the table. - /// An action that performs configuration of the table. - /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToTable( - this OwnedNavigationBuilder referenceOwnershipBuilder, - string name, - string? schema, - Action buildAction) - { - Check.NotNull(name, nameof(name)); - Check.NullButNotEmpty(schema, nameof(schema)); - Check.NotNull(buildAction, nameof(buildAction)); - - referenceOwnershipBuilder.OwnedEntityType.SetTableName(name); - referenceOwnershipBuilder.OwnedEntityType.SetSchema(schema); - buildAction(new OwnedNavigationTableBuilder(name, schema, referenceOwnershipBuilder)); - - return referenceOwnershipBuilder; - } - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the table. - /// The schema of the table. - /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToTable( - this OwnedNavigationBuilder referenceOwnershipBuilder, - string name, - string? schema) - where TOwnerEntity : class - where TRelatedEntity : class - => (OwnedNavigationBuilder)((OwnedNavigationBuilder)referenceOwnershipBuilder).ToTable( - name, schema); - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the table. - /// The schema of the table. - /// An action that performs configuration of the table. - /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToTable( - this OwnedNavigationBuilder referenceOwnershipBuilder, - string name, - string? schema, - Action> buildAction) - where TOwnerEntity : class - where TRelatedEntity : class - { - Check.NotNull(name, nameof(name)); - Check.NullButNotEmpty(schema, nameof(schema)); - Check.NotNull(buildAction, nameof(buildAction)); - - referenceOwnershipBuilder.OwnedEntityType.SetTableName(name); - referenceOwnershipBuilder.OwnedEntityType.SetSchema(schema); - buildAction(new OwnedNavigationTableBuilder(name, schema, referenceOwnershipBuilder)); - - return referenceOwnershipBuilder; - } - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the table. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, otherwise. - /// - public static IConventionEntityTypeBuilder? ToTable( - this IConventionEntityTypeBuilder entityTypeBuilder, - string? name, - bool fromDataAnnotation = false) - { - if (!entityTypeBuilder.CanSetTable(name, fromDataAnnotation)) - { - return null; - } - - entityTypeBuilder.Metadata.SetTableName(name, fromDataAnnotation); - return entityTypeBuilder; - } - - /// - /// Configures the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the table. - /// The schema of the table. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, otherwise. - /// - public static IConventionEntityTypeBuilder? ToTable( - this IConventionEntityTypeBuilder entityTypeBuilder, - string? name, - string? schema, - bool fromDataAnnotation = false) - { - if (!entityTypeBuilder.CanSetTable(name, fromDataAnnotation) - || !entityTypeBuilder.CanSetSchema(schema, fromDataAnnotation)) - { - return null; - } - - entityTypeBuilder.Metadata.SetTableName(name, fromDataAnnotation); - entityTypeBuilder.Metadata.SetSchema(schema, fromDataAnnotation); - return entityTypeBuilder; - } - - /// - /// Returns a value indicating whether the table name can be set for this entity type - /// using the specified configuration source. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the table. - /// Indicates whether the configuration was specified using a data annotation. - /// if the configuration can be applied. - public static bool CanSetTable( - this IConventionEntityTypeBuilder entityTypeBuilder, - string? name, - bool fromDataAnnotation = false) - { - Check.NullButNotEmpty(name, nameof(name)); - - return entityTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.TableName, name, fromDataAnnotation); - } - - /// - /// Configures the schema of the table that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The schema of the table. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, otherwise. - /// - public static IConventionEntityTypeBuilder? ToSchema( - this IConventionEntityTypeBuilder entityTypeBuilder, - string? schema, - bool fromDataAnnotation = false) - { - if (!entityTypeBuilder.CanSetSchema(schema, fromDataAnnotation)) - { - return null; - } - - entityTypeBuilder.Metadata.SetSchema(schema, fromDataAnnotation); - return entityTypeBuilder; - } - - /// - /// Returns a value indicating whether the schema of the table name can be set for this entity type - /// using the specified configuration source. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// /// The builder for the entity type being configured. - /// The schema of the table. - /// Indicates whether the configuration was specified using a data annotation. - /// if the configuration can be applied. - public static bool CanSetSchema( - this IConventionEntityTypeBuilder entityTypeBuilder, - string? schema, - bool fromDataAnnotation = false) - { - Check.NullButNotEmpty(schema, nameof(schema)); - - return entityTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.Schema, schema, fromDataAnnotation); - } + /// The same builder instance so that multiple calls can be chained. + public static EntityTypeBuilder UseTptMappingStrategy(this EntityTypeBuilder entityTypeBuilder) + where TEntity : class + => (EntityTypeBuilder)((EntityTypeBuilder)entityTypeBuilder).UseTptMappingStrategy(); /// /// Mark the table that this entity type is mapped to as excluded from migrations. @@ -725,277 +129,6 @@ public static bool CanExcludeTableFromMigrations( => entityTypeBuilder.CanSetAnnotation (RelationalAnnotationNames.IsTableExcludedFromMigrations, excludedFromMigrations, fromDataAnnotation); - /// - /// Configures the view that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the view. - /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder ToView( - this EntityTypeBuilder entityTypeBuilder, - string? name) - => entityTypeBuilder.ToView(name, null); - - /// - /// Configures the view that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The entity type being configured. - /// The builder for the entity type being configured. - /// The name of the view. - /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder ToView( - this EntityTypeBuilder referenceOwnershipBuilder, - string? name) - where TEntity : class - => (EntityTypeBuilder)ToView((EntityTypeBuilder)referenceOwnershipBuilder, name); - - /// - /// Configures the view that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the view. - /// The schema of the view. - /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder ToView( - this EntityTypeBuilder entityTypeBuilder, - string? name, - string? schema) - { - Check.NullButNotEmpty(name, nameof(name)); - Check.NullButNotEmpty(schema, nameof(schema)); - - entityTypeBuilder.Metadata.SetViewName(name); - entityTypeBuilder.Metadata.SetViewSchema(schema); - entityTypeBuilder.Metadata.SetAnnotation(RelationalAnnotationNames.ViewDefinitionSql, null); - - return entityTypeBuilder; - } - - /// - /// Configures the view that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The entity type being configured. - /// The builder for the entity type being configured. - /// The name of the view. - /// The schema of the view. - /// The same builder instance so that multiple calls can be chained. - public static EntityTypeBuilder ToView( - this EntityTypeBuilder entityTypeBuilder, - string? name, - string? schema) - where TEntity : class - => (EntityTypeBuilder)ToView((EntityTypeBuilder)entityTypeBuilder, name, schema); - - /// - /// Configures the view that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the view. - /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToView( - this OwnedNavigationBuilder referenceOwnershipBuilder, - string? name) - => referenceOwnershipBuilder.ToView(name, null); - - /// - /// Configures the view that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the view. - /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToView( - this OwnedNavigationBuilder referenceOwnershipBuilder, - string? name) - where TOwnerEntity : class - where TRelatedEntity : class - => (OwnedNavigationBuilder)ToView((OwnedNavigationBuilder)referenceOwnershipBuilder, name); - - /// - /// Configures the view that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the view. - /// The schema of the view. - /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToView( - this OwnedNavigationBuilder referenceOwnershipBuilder, - string? name, - string? schema) - { - Check.NullButNotEmpty(name, nameof(name)); - Check.NullButNotEmpty(schema, nameof(schema)); - - referenceOwnershipBuilder.OwnedEntityType.SetViewName(name); - referenceOwnershipBuilder.OwnedEntityType.SetViewSchema(schema); - referenceOwnershipBuilder.OwnedEntityType.SetAnnotation(RelationalAnnotationNames.ViewDefinitionSql, null); - - return referenceOwnershipBuilder; - } - - /// - /// Configures the view that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the view. - /// The schema of the view. - /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToView( - this OwnedNavigationBuilder referenceOwnershipBuilder, - string? name, - string? schema) - where TOwnerEntity : class - where TRelatedEntity : class - => (OwnedNavigationBuilder)ToView( - (OwnedNavigationBuilder)referenceOwnershipBuilder, name, schema); - - /// - /// Configures the view that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the view. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, otherwise. - /// - public static IConventionEntityTypeBuilder? ToView( - this IConventionEntityTypeBuilder entityTypeBuilder, - string? name, - bool fromDataAnnotation = false) - { - if (!entityTypeBuilder.CanSetView(name, fromDataAnnotation)) - { - return null; - } - - entityTypeBuilder.Metadata.SetViewName(name, fromDataAnnotation); - return entityTypeBuilder; - } - - /// - /// Configures the view that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the view. - /// The schema of the view. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, otherwise. - /// - public static IConventionEntityTypeBuilder? ToView( - this IConventionEntityTypeBuilder entityTypeBuilder, - string? name, - string? schema, - bool fromDataAnnotation = false) - { - if (!entityTypeBuilder.CanSetView(name, fromDataAnnotation) - || !entityTypeBuilder.CanSetViewSchema(schema, fromDataAnnotation)) - { - return null; - } - - entityTypeBuilder.Metadata.SetViewName(name, fromDataAnnotation); - entityTypeBuilder.Metadata.SetViewSchema(schema, fromDataAnnotation); - return entityTypeBuilder; - } - - /// - /// Returns a value indicating whether the view name can be set for this entity type - /// using the specified configuration source. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The name of the view. - /// Indicates whether the configuration was specified using a data annotation. - /// if the configuration can be applied. - public static bool CanSetView( - this IConventionEntityTypeBuilder entityTypeBuilder, - string? name, - bool fromDataAnnotation = false) - { - Check.NullButNotEmpty(name, nameof(name)); - - return entityTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.ViewName, name, fromDataAnnotation); - } - - /// - /// Configures the schema of the view that the entity type maps to when targeting a relational database. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The schema of the view. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, otherwise. - /// - public static IConventionEntityTypeBuilder? ToViewSchema( - this IConventionEntityTypeBuilder entityTypeBuilder, - string? schema, - bool fromDataAnnotation = false) - { - if (!entityTypeBuilder.CanSetSchema(schema, fromDataAnnotation)) - { - return null; - } - - entityTypeBuilder.Metadata.SetViewSchema(schema, fromDataAnnotation); - return entityTypeBuilder; - } - - /// - /// Returns a value indicating whether the schema of the view can be set for this entity type - /// using the specified configuration source. - /// - /// - /// See Modeling entity types and relationships for more information and examples. - /// - /// The builder for the entity type being configured. - /// The schema of the view. - /// Indicates whether the configuration was specified using a data annotation. - /// if the configuration can be applied. - public static bool CanSetViewSchema( - this IConventionEntityTypeBuilder entityTypeBuilder, - string? schema, - bool fromDataAnnotation = false) - { - Check.NullButNotEmpty(schema, nameof(schema)); - - return entityTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.ViewSchema, schema, fromDataAnnotation); - } - /// /// Configures a SQL string used to provide data for the entity type. /// @@ -1136,7 +269,7 @@ public static EntityTypeBuilder ToFunction( Check.NotNull(name, nameof(name)); Check.NotNull(configureFunction, nameof(configureFunction)); - configureFunction(new TableValuedFunctionBuilder(ToFunction(name, entityTypeBuilder.Metadata))); + configureFunction(new (ToFunction(name, entityTypeBuilder.Metadata), entityTypeBuilder)); return entityTypeBuilder; } @@ -1159,7 +292,7 @@ public static EntityTypeBuilder ToFunction( Check.NotNull(function, nameof(function)); Check.NotNull(configureFunction, nameof(configureFunction)); - configureFunction(new TableValuedFunctionBuilder(ToFunction(function, entityTypeBuilder.Metadata))); + configureFunction(new (ToFunction(function, entityTypeBuilder.Metadata), entityTypeBuilder)); return entityTypeBuilder; } @@ -1210,9 +343,16 @@ public static EntityTypeBuilder ToFunction( public static EntityTypeBuilder ToFunction( this EntityTypeBuilder entityTypeBuilder, string name, - Action configureFunction) + Action> configureFunction) where TEntity : class - => (EntityTypeBuilder)ToFunction((EntityTypeBuilder)entityTypeBuilder, name, configureFunction); + { + Check.NotNull(name, nameof(name)); + Check.NotNull(configureFunction, nameof(configureFunction)); + + configureFunction(new(ToFunction(name, entityTypeBuilder.Metadata), entityTypeBuilder)); + + return entityTypeBuilder; + } /// /// Configures the function that the entity type maps to when targeting a relational database. @@ -1228,9 +368,16 @@ public static EntityTypeBuilder ToFunction( public static EntityTypeBuilder ToFunction( this EntityTypeBuilder entityTypeBuilder, MethodInfo function, - Action configureFunction) + Action> configureFunction) where TEntity : class - => (EntityTypeBuilder)ToFunction((EntityTypeBuilder)entityTypeBuilder, function, configureFunction); + { + Check.NotNull(function, nameof(function)); + Check.NotNull(configureFunction, nameof(configureFunction)); + + configureFunction(new(ToFunction(function, entityTypeBuilder.Metadata), entityTypeBuilder)); + + return entityTypeBuilder; + } /// /// Configures the function that the entity type maps to when targeting a relational database. @@ -1283,12 +430,12 @@ public static OwnedNavigationBuilder ToFunction( public static OwnedNavigationBuilder ToFunction( this OwnedNavigationBuilder ownedNavigationBuilder, string name, - Action configureFunction) + Action configureFunction) { Check.NullButNotEmpty(name, nameof(name)); Check.NotNull(configureFunction, nameof(configureFunction)); - configureFunction(new TableValuedFunctionBuilder(ToFunction(name, ownedNavigationBuilder.OwnedEntityType))); + configureFunction(new (ToFunction(name, ownedNavigationBuilder.OwnedEntityType), ownedNavigationBuilder)); return ownedNavigationBuilder; } @@ -1306,12 +453,12 @@ public static OwnedNavigationBuilder ToFunction( public static OwnedNavigationBuilder ToFunction( this OwnedNavigationBuilder ownedNavigationBuilder, MethodInfo function, - Action configureFunction) + Action configureFunction) { Check.NotNull(function, nameof(function)); Check.NotNull(configureFunction, nameof(configureFunction)); - configureFunction(new TableValuedFunctionBuilder(ToFunction(function, ownedNavigationBuilder.OwnedEntityType))); + configureFunction(new (ToFunction(function, ownedNavigationBuilder.OwnedEntityType), ownedNavigationBuilder)); return ownedNavigationBuilder; } @@ -1322,15 +469,18 @@ public static OwnedNavigationBuilder ToFunction( /// /// See Modeling entity types and relationships for more information and examples. /// - /// The builder for the entity type being configured. + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. + /// The builder for the entity type being configured. /// The name of the function. /// The function configuration builder. - public static OwnedNavigationBuilder ToFunction( - this OwnedNavigationBuilder referenceOwnershipBuilder, + public static OwnedNavigationBuilder ToFunction( + this OwnedNavigationBuilder ownedNavigationBuilder, string? name) where TOwnerEntity : class - where TRelatedEntity : class - => (OwnedNavigationBuilder)ToFunction((OwnedNavigationBuilder)referenceOwnershipBuilder, name); + where TDependentEntity : class + => (OwnedNavigationBuilder)ToFunction( + (OwnedNavigationBuilder)ownedNavigationBuilder, name); /// /// Configures the function that the entity type maps to when targeting a relational database. @@ -1338,16 +488,18 @@ public static OwnedNavigationBuilder ToFunction /// See Modeling entity types and relationships for more information and examples. /// - /// The builder for the entity type being configured. + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. + /// The builder for the entity type being configured. /// The method representing the function. /// The function configuration builder. - public static OwnedNavigationBuilder ToFunction( - this OwnedNavigationBuilder referenceOwnershipBuilder, + public static OwnedNavigationBuilder ToFunction( + this OwnedNavigationBuilder ownedNavigationBuilder, MethodInfo? function) where TOwnerEntity : class - where TRelatedEntity : class - => (OwnedNavigationBuilder)ToFunction( - (OwnedNavigationBuilder)referenceOwnershipBuilder, function); + where TDependentEntity : class + => (OwnedNavigationBuilder)ToFunction( + (OwnedNavigationBuilder)ownedNavigationBuilder, function); /// /// Configures the function that the entity type maps to when targeting a relational database. @@ -1355,18 +507,26 @@ public static OwnedNavigationBuilder ToFunction /// See Modeling entity types and relationships for more information and examples. /// - /// The builder for the entity type being configured. + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. + /// The builder for the entity type being configured. /// The name of the function. /// The function configuration action. /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToFunction( - this OwnedNavigationBuilder referenceOwnershipBuilder, + public static OwnedNavigationBuilder ToFunction( + this OwnedNavigationBuilder ownedNavigationBuilder, string name, - Action configureFunction) + Action> configureFunction) where TOwnerEntity : class - where TRelatedEntity : class - => (OwnedNavigationBuilder)ToFunction( - (OwnedNavigationBuilder)referenceOwnershipBuilder, name, configureFunction); + where TDependentEntity : class + { + Check.NullButNotEmpty(name, nameof(name)); + Check.NotNull(configureFunction, nameof(configureFunction)); + + configureFunction(new(ToFunction(name, ownedNavigationBuilder.OwnedEntityType), ownedNavigationBuilder)); + + return ownedNavigationBuilder; + } /// /// Configures the function that the entity type maps to when targeting a relational database. @@ -1374,18 +534,26 @@ public static OwnedNavigationBuilder ToFunction /// See Modeling entity types and relationships for more information and examples. /// - /// The builder for the entity type being configured. + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. + /// The builder for the entity type being configured. /// The method representing the function. /// The function configuration action. /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToFunction( - this OwnedNavigationBuilder referenceOwnershipBuilder, + public static OwnedNavigationBuilder ToFunction( + this OwnedNavigationBuilder ownedNavigationBuilder, MethodInfo function, - Action configureFunction) + Action> configureFunction) where TOwnerEntity : class - where TRelatedEntity : class - => (OwnedNavigationBuilder)ToFunction( - (OwnedNavigationBuilder)referenceOwnershipBuilder, function, configureFunction); + where TDependentEntity : class + { + Check.NotNull(function, nameof(function)); + Check.NotNull(configureFunction, nameof(configureFunction)); + + configureFunction(new(ToFunction(function, ownedNavigationBuilder.OwnedEntityType), ownedNavigationBuilder)); + + return ownedNavigationBuilder; + } [return: NotNullIfNotNull("name")] private static IMutableDbFunction? ToFunction(string? name, IMutableEntityType entityType) @@ -1589,7 +757,7 @@ public static EntityTypeBuilder HasCheckConstraint( entityTypeBuilder.HasCheckConstraint(name, sql); - buildAction(new CheckConstraintBuilder(entityTypeBuilder.Metadata.FindCheckConstraint(name)!)); + buildAction(new (entityTypeBuilder.Metadata.FindCheckConstraint(name)!)); return entityTypeBuilder; } @@ -1662,6 +830,8 @@ public static OwnedNavigationBuilder HasCheckConstraint( /// /// See Database check constraints for more information and examples. /// + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. /// The navigation builder for the owned type. /// The name of the check constraint. /// The logical constraint sql used in the check constraint. @@ -1697,7 +867,7 @@ public static OwnedNavigationBuilder HasCheckConstraint( ownedNavigationBuilder.HasCheckConstraint(name, sql); - buildAction(new CheckConstraintBuilder(ownedNavigationBuilder.OwnedEntityType.FindCheckConstraint(name)!)); + buildAction(new (ownedNavigationBuilder.OwnedEntityType.FindCheckConstraint(name)!)); return ownedNavigationBuilder; } diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs index 5e368799897..40837779424 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeExtensions.cs @@ -828,6 +828,179 @@ public static void SetComment(this IMutableEntityType entityType, string? commen #endregion Comment + /// + /// + /// Returns all configured entity type mapping fragments. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The entity type. + /// The configured entity type mapping fragments. + public static IEnumerable GetMappingFragments(this IReadOnlyEntityType entityType) + => EntityTypeMappingFragment.Get(entityType) ?? Enumerable.Empty(); + + /// + /// + /// Returns all configured entity type mapping fragments. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The entity type. + /// The configured entity type mapping fragments. + public static IEnumerable GetMappingFragments(this IEntityType entityType) + => EntityTypeMappingFragment.Get(entityType)?.Cast() + ?? Enumerable.Empty(); + + /// + /// + /// Returns the entity type mapping for a particular table-like store object. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The entity type. + /// The identifier of a table-like store object. + /// An object that represents an entity type mapping fragment. + public static IReadOnlyEntityTypeMappingFragment? FindMappingFragment( + this IReadOnlyEntityType entityType, in StoreObjectIdentifier storeObject) + => EntityTypeMappingFragment.Find(entityType, storeObject); + + /// + /// + /// Returns the entity type mapping for a particular table-like store object. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The entity type. + /// The identifier of a table-like store object. + /// An object that represents an entity type mapping fragment. + public static IMutableEntityTypeMappingFragment? FindMappingFragment( + this IMutableEntityType entityType, in StoreObjectIdentifier storeObject) + => (IMutableEntityTypeMappingFragment?)EntityTypeMappingFragment.Find(entityType, storeObject); + + /// + /// + /// Returns the entity type mapping for a particular table-like store object. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The entity type. + /// The identifier of a table-like store object. + /// An object that represents an entity type mapping fragment. + public static IConventionEntityTypeMappingFragment? FindMappingFragment( + this IConventionEntityType entityType, in StoreObjectIdentifier storeObject) + => (IConventionEntityTypeMappingFragment?)EntityTypeMappingFragment.Find(entityType, storeObject); + + /// + /// + /// Returns the entity type mapping for a particular table-like store object. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The entity type. + /// The identifier of a table-like store object. + /// An object that represents an entity type mapping fragment. + public static IEntityTypeMappingFragment? FindMappingFragment( + this IEntityType entityType, in StoreObjectIdentifier storeObject) + => (IEntityTypeMappingFragment?)EntityTypeMappingFragment.Find(entityType, storeObject); + + /// + /// + /// Returns the entity type mapping for a particular table-like store object. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The entity type. + /// The identifier of a table-like store object. + /// An object that represents an entity type mapping fragment. + public static IMutableEntityTypeMappingFragment GetOrCreateMappingFragment( + this IMutableEntityType entityType, + in StoreObjectIdentifier storeObject) + => EntityTypeMappingFragment.GetOrCreate(entityType, storeObject, ConfigurationSource.Explicit); + + /// + /// + /// Returns the entity type mapping for a particular table-like store object. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The entity type. + /// The identifier of a table-like store object. + /// Indicates whether the configuration was specified using a data annotation. + /// An object that represents an entity type mapping fragment. + public static IConventionEntityTypeMappingFragment GetOrCreateMappingFragment( + this IConventionEntityType entityType, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => EntityTypeMappingFragment.GetOrCreate((IMutableEntityType)entityType, storeObject, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// + /// Removes the entity type mapping for a particular table-like store object. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The entity type. + /// The identifier of a table-like store object. + /// + /// The removed or + /// if no overrides for the given store object were found. + /// + public static IMutableEntityTypeMappingFragment? RemoveMappingFragment( + this IMutableEntityType entityType, + in StoreObjectIdentifier storeObject) + => EntityTypeMappingFragment.Remove(entityType, storeObject, ConfigurationSource.Explicit); + + /// + /// + /// Removes the entity type mapping for a particular table-like store object. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The entity type. + /// The identifier of a table-like store object. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The removed or + /// if no overrides for the given store object were found or the existing overrides were configured from a higher source. + /// + public static IConventionEntityTypeMappingFragment? RemoveMappingFragment( + this IConventionEntityType entityType, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => EntityTypeMappingFragment.Remove((IMutableEntityType)entityType, storeObject, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + /// /// Gets the foreign keys for the given entity type that point to other entity types /// sharing the same table-like store object. @@ -959,6 +1132,36 @@ public static bool IsTableExcludedFromMigrations(this IReadOnlyEntityType entity return false; } + /// + /// Gets a value indicating whether the specified table is ignored by Migrations. + /// + /// The entity type. + /// The identifier of the table-like store object. + /// A value indicating whether the associated table is ignored by Migrations. + public static bool IsTableExcludedFromMigrations( + this IReadOnlyEntityType entityType, + in StoreObjectIdentifier storeObject) + { + if (entityType is RuntimeEntityType) + { + throw new InvalidOperationException(CoreStrings.RuntimeModelMissingData); + } + + var overrides = entityType.FindMappingFragment(storeObject); + if (overrides != null) + { + return overrides.IsTableExcludedFromMigrations ?? entityType.IsTableExcludedFromMigrations(); + } + + if (StoreObjectIdentifier.Create(entityType, storeObject.StoreObjectType) == storeObject) + { + return entityType.IsTableExcludedFromMigrations(); + } + + throw new InvalidOperationException( + RelationalStrings.TableNotMappedEntityType(entityType.DisplayName(), storeObject.DisplayName())); + } + /// /// Sets a value indicating whether the associated table is ignored by Migrations. /// @@ -983,35 +1186,72 @@ public static void SetIsTableExcludedFromMigrations(this IMutableEntityType enti ?.Value; /// - /// Gets the for . + /// Sets a value indicating whether the associated table is ignored by Migrations. + /// + /// The entity type. + /// A value indicating whether the associated table is ignored by Migrations. + /// The identifier of the table-like store object. + public static void SetIsTableExcludedFromMigrations( + this IMutableEntityType entityType, + bool? excluded, + in StoreObjectIdentifier storeObject) + => entityType.GetOrCreateMappingFragment(storeObject).IsTableExcludedFromMigrations = excluded; + + /// + /// Sets a value indicating whether the associated table is ignored by Migrations. + /// + /// The entity type. + /// A value indicating whether the associated table is ignored by Migrations. + /// The identifier of the table-like store object. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + public static bool? SetIsTableExcludedFromMigrations( + this IConventionEntityType entityType, + bool? excluded, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => entityType.GetOrCreateMappingFragment(storeObject, fromDataAnnotation).SetIsTableExcludedFromMigrations( + excluded, fromDataAnnotation); + + /// + /// Gets the for . /// /// The entity type to find configuration source for. - /// The for . + /// + /// The for . + /// public static ConfigurationSource? GetIsTableExcludedFromMigrationsConfigurationSource( this IConventionEntityType entityType) => entityType.FindAnnotation(RelationalAnnotationNames.IsTableExcludedFromMigrations) ?.GetConfigurationSource(); + /// + /// Gets the for . + /// + /// The entity type to find configuration source for. + /// The identifier of the table-like store object. + /// + /// The for . + /// + public static ConfigurationSource? GetIsTableExcludedFromMigrationsConfigurationSource( + this IConventionEntityType entityType, + in StoreObjectIdentifier storeObject) + => entityType.FindMappingFragment(storeObject)?.GetIsTableExcludedFromMigrationsConfigurationSource(); + /// /// Gets the mapping strategy for the derived types. /// /// The entity type. /// The mapping strategy for the derived types. public static string? GetMappingStrategy(this IReadOnlyEntityType entityType) - { - var mappingStrategy = (string?)entityType[RelationalAnnotationNames.MappingStrategy]; - if (mappingStrategy != null) - { - return mappingStrategy; - } - - if (entityType.BaseType != null) - { - return entityType.GetRootType().GetMappingStrategy(); - } - - return null; - } + => (string?)entityType[RelationalAnnotationNames.MappingStrategy] + ?? (entityType.BaseType != null + ? entityType.GetRootType().GetMappingStrategy() + : entityType.GetDiscriminatorPropertyName() != null + ? RelationalAnnotationNames.TphMappingStrategy + : entityType.FindPrimaryKey() == null || !entityType.GetDirectlyDerivedTypes().Any() + ? null + : RelationalAnnotationNames.TptMappingStrategy); #endregion IsTableExcludedFromMigrations @@ -1181,7 +1421,8 @@ public static IConventionTrigger AddTrigger( /// The entityType to find the trigger in. /// The trigger name. /// - /// The removed or if no trigger with the given name was found. + /// The removed or if no trigger with the given name was found + /// or the existing trigger was configured from a higher source. /// public static IConventionTrigger? RemoveTrigger(this IConventionEntityType entityType, string name) => Trigger.RemoveTrigger((IMutableEntityType)entityType, name); diff --git a/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs index 2171450dbe2..5b31d18cea6 100644 --- a/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalForeignKeyExtensions.cs @@ -84,7 +84,7 @@ public static class RelationalForeignKeyExtensions .Append('_') .Append(principalTableName) .Append('_') - .AppendJoin(foreignKey.Properties.Select(p => p.GetColumnBaseName()), "_") + .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 288071d3225..7efc8078c15 100644 --- a/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalIndexExtensions.cs @@ -56,7 +56,7 @@ public static class RelationalIndexExtensions .Append("IX_") .Append(tableName) .Append('_') - .AppendJoin(index.Properties.Select(p => p.GetColumnBaseName()), "_") + .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 b1590465495..0d5381af63b 100644 --- a/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalKeyExtensions.cs @@ -68,7 +68,7 @@ public static class RelationalKeyExtensions .Append("AK_") .Append(tableName) .Append('_') - .AppendJoin(key.Properties.Select(p => p.GetColumnBaseName()), "_") + .AppendJoin(key.Properties.Select(p => p.GetColumnName()), "_") .ToString(); return Uniquifier.Truncate(name, key.DeclaringEntityType.Model.GetMaxIdentifierLength()); diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyBuilderExtensions.cs index c6254297a70..50a4276b20b 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyBuilderExtensions.cs @@ -138,7 +138,8 @@ public static bool CanSetColumnName( in StoreObjectIdentifier storeObject, bool fromDataAnnotation = false) { - var overrides = (RelationalPropertyOverrides?)RelationalPropertyOverrides.Find(propertyBuilder.Metadata, storeObject); + var overrides = (IConventionRelationalPropertyOverrides?)RelationalPropertyOverrides.Find( + propertyBuilder.Metadata, storeObject); return overrides == null || (fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention) .Overrides(overrides.GetColumnNameConfigurationSource()) diff --git a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs index e173ed51b4c..ce5b852aaf3 100644 --- a/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalPropertyExtensions.cs @@ -31,8 +31,17 @@ private static readonly MethodInfo ThrowReadValueExceptionMethod /// /// The property. /// The base name of the column to which the property would be mapped. + [Obsolete("Use GetColumnName")] public static string GetColumnBaseName(this IReadOnlyProperty property) - => (string?)property.FindAnnotation(RelationalAnnotationNames.ColumnName)?.Value ?? property.GetDefaultColumnBaseName(); + => property.GetColumnName(); + + /// + /// Returns the name of the column to which the property would be mapped. + /// + /// The property. + /// The base name of the column to which the property would be mapped. + public static string GetColumnName(this IReadOnlyProperty property) + => (string?)property.FindAnnotation(RelationalAnnotationNames.ColumnName)?.Value ?? property.GetDefaultColumnName(); /// /// Returns the name of the column to which the property is mapped for a particular table. @@ -54,15 +63,21 @@ public static string GetColumnBaseName(this IReadOnlyProperty property) if (property.IsPrimaryKey()) { var tableFound = false; - foreach (var containingType in property.DeclaringEntityType.GetDerivedTypesInclusive()) + if (property.DeclaringEntityType.FindMappingFragment(storeObject) != null) + { + tableFound = true; + } + else { - if (StoreObjectIdentifier.Create(containingType, storeObject.StoreObjectType) == storeObject) + foreach (var containingType in property.DeclaringEntityType.GetDerivedTypesInclusive()) { - tableFound = true; - break; + if (StoreObjectIdentifier.Create(containingType, storeObject.StoreObjectType) == storeObject) + { + tableFound = true; + break; + } } } - if (!tableFound) { return null; @@ -100,6 +115,17 @@ public static string GetColumnBaseName(this IReadOnlyProperty property) return null; } } + else if (property.DeclaringEntityType.GetMappingFragments().Any()) + { + if (overrides == null + && (declaringStoreObject != storeObject + || property.DeclaringEntityType.GetMappingFragments() + .Any(f => property.FindOverrides(f.StoreObject) != null))) + { + + return null; + } + } else if (declaringStoreObject != storeObject) { return null; @@ -121,7 +147,16 @@ public static string GetColumnBaseName(this IReadOnlyProperty property) /// /// The property. /// The default base column name to which the property would be mapped. + [Obsolete("Use GetDefaultColumnName")] public static string GetDefaultColumnBaseName(this IReadOnlyProperty property) + => property.GetDefaultColumnName(); + + /// + /// Returns the default base name of the column to which the property would be mapped + /// + /// The property. + /// The default base column name to which the property would be mapped. + public static string GetDefaultColumnName(this IReadOnlyProperty property) => Uniquifier.Truncate(property.Name, property.DeclaringEntityType.Model.GetMaxIdentifierLength()); /// @@ -198,7 +233,7 @@ public static string GetDefaultColumnName(this IReadOnlyProperty property, in St entityType = ownerType; } - var baseName = property.GetDefaultColumnBaseName(); + var baseName = property.GetDefaultColumnName(); if (builder == null) { return baseName; @@ -250,8 +285,7 @@ public static void SetColumnName( this IMutableProperty property, string? name, in StoreObjectIdentifier storeObject) - => RelationalPropertyOverrides.GetOrCreate(property, storeObject) - .SetColumnName(name, ConfigurationSource.Explicit); + => property.GetOrCreateOverrides(storeObject).ColumnName = name; /// /// Sets the column to which the property is mapped for a particular table-like store object. @@ -266,8 +300,7 @@ public static void SetColumnName( string? name, in StoreObjectIdentifier storeObject, bool fromDataAnnotation = false) - => RelationalPropertyOverrides.GetOrCreate(property, storeObject) - .SetColumnName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + => property.GetOrCreateOverrides(storeObject, fromDataAnnotation).SetColumnName(name, fromDataAnnotation); /// /// Gets the for the column name. @@ -286,7 +319,8 @@ public static void SetColumnName( public static ConfigurationSource? GetColumnNameConfigurationSource( this IConventionProperty property, in StoreObjectIdentifier storeObject) - => ((RelationalPropertyOverrides?)RelationalPropertyOverrides.Find(property, storeObject))?.GetColumnNameConfigurationSource(); + => ((IConventionRelationalPropertyOverrides?)RelationalPropertyOverrides.Find(property, storeObject)) + ?.GetColumnNameConfigurationSource(); /// /// Returns the order of the column this property is mapped to. @@ -1419,7 +1453,7 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope /// /// - /// Returns the property facet overrides for a particular table-like store object. + /// Returns all the property facet overrides. /// /// /// This method is typically used by database providers (and other extensions). It is generally @@ -1427,14 +1461,13 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope /// /// /// The property. - /// The identifier of the table-like store object containing the column. - /// An object that stores property facet overrides. - public static IReadOnlyAnnotatable? FindOverrides(this IReadOnlyProperty property, in StoreObjectIdentifier storeObject) - => RelationalPropertyOverrides.Find(property, storeObject); + /// The property facet overrides. + public static IEnumerable GetOverrides(this IReadOnlyProperty property) + => RelationalPropertyOverrides.Get(property) ?? Enumerable.Empty(); /// /// - /// Returns the property facet overrides for a particular table-like store object. + /// Returns all the property facet overrides. /// /// /// This method is typically used by database providers (and other extensions). It is generally @@ -1442,10 +1475,10 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope /// /// /// The property. - /// The identifier of the table-like store object containing the column. - /// An object that stores property facet overrides. - public static IMutableAnnotatable? FindOverrides(this IMutableProperty property, in StoreObjectIdentifier storeObject) - => (IMutableAnnotatable?)RelationalPropertyOverrides.Find(property, storeObject); + /// The property facet overrides. + public static IEnumerable GetOverrides(this IProperty property) + => RelationalPropertyOverrides.Get(property)?.Cast() + ?? Enumerable.Empty(); /// /// @@ -1459,8 +1492,10 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope /// The property. /// The identifier of the table-like store object containing the column. /// An object that stores property facet overrides. - public static IConventionAnnotatable? FindOverrides(this IConventionProperty property, in StoreObjectIdentifier storeObject) - => (IConventionAnnotatable?)RelationalPropertyOverrides.Find(property, storeObject); + public static IReadOnlyRelationalPropertyOverrides? FindOverrides( + this IReadOnlyProperty property, + in StoreObjectIdentifier storeObject) + => RelationalPropertyOverrides.Find(property, storeObject); /// /// @@ -1474,8 +1509,10 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope /// The property. /// The identifier of the table-like store object containing the column. /// An object that stores property facet overrides. - public static IAnnotatable? FindOverrides(this IProperty property, in StoreObjectIdentifier storeObject) - => RelationalPropertyOverrides.Find(property, storeObject); + public static IRelationalPropertyOverrides? FindOverrides( + this IProperty property, + in StoreObjectIdentifier storeObject) + => (IRelationalPropertyOverrides?)RelationalPropertyOverrides.Find(property, storeObject); /// /// @@ -1489,10 +1526,10 @@ public static RelationalTypeMapping GetRelationalTypeMapping(this IReadOnlyPrope /// The property. /// The identifier of the table-like store object containing the column. /// An object that stores property facet overrides. - public static IMutableAnnotatable GetOrCreateOverrides( + public static IMutableRelationalPropertyOverrides GetOrCreateOverrides( this IMutableProperty property, in StoreObjectIdentifier storeObject) - => RelationalPropertyOverrides.GetOrCreate(property, storeObject); + => RelationalPropertyOverrides.GetOrCreate(property, storeObject, ConfigurationSource.Explicit); /// /// @@ -1505,11 +1542,107 @@ public static IMutableAnnotatable GetOrCreateOverrides( /// /// The property. /// The identifier of the table-like store object containing the column. + /// Indicates whether the configuration was specified using a data annotation. /// An object that stores property facet overrides. - public static IConventionAnnotatable GetOrCreateOverrides( + public static IConventionRelationalPropertyOverrides GetOrCreateOverrides( this IConventionProperty property, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => RelationalPropertyOverrides.GetOrCreate((IMutableProperty)property, storeObject, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// + /// Removes the property facet overrides for a particular table-like store object. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The property. + /// The identifier of a table-like store object. + /// + /// The removed or + /// if no overrides for the given store object were found. + /// + public static IMutableRelationalPropertyOverrides? RemoveOverrides( + this IMutableProperty property, in StoreObjectIdentifier storeObject) - => RelationalPropertyOverrides.GetOrCreate(property, storeObject); + => RelationalPropertyOverrides.Remove(property, storeObject, ConfigurationSource.Explicit); + + /// + /// + /// Removes the property facet overrides for a particular table-like store object. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The property. + /// The identifier of a table-like store object. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The removed or + /// if no overrides for the given store object were found or the existing overrides were configured from a higher source. + /// + public static IConventionRelationalPropertyOverrides? RemoveOverrides( + this IConventionProperty property, + in StoreObjectIdentifier storeObject, + bool fromDataAnnotation = false) + => RelationalPropertyOverrides.Remove((IMutableProperty)property, storeObject, + fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + /// + /// + /// Returns the table-like store objects to which this property is mapped. + /// + /// + /// This method is typically used by database providers (and other extensions). It is generally + /// not used in application code. + /// + /// + /// The property. + /// The type of the store object. + /// The table-like store objects to which this property is mapped. + public static IEnumerable GetMappedStoreObjects( + this IReadOnlyProperty property, + StoreObjectType storeObjectType) + { + var declaringType = property.DeclaringEntityType; + var declaringStoreObject = StoreObjectIdentifier.Create(declaringType, storeObjectType); + if (declaringStoreObject != null + && property.GetColumnName(declaringStoreObject.Value) != null) + { + yield return declaringStoreObject.Value; + } + + if (storeObjectType == StoreObjectType.Function + || storeObjectType == StoreObjectType.SqlQuery) + { + yield break; + } + + foreach (var fragment in declaringType.GetMappingFragments()) + { + if (fragment.StoreObject.StoreObjectType == storeObjectType + && property.GetColumnName(fragment.StoreObject) != null) + { + yield return fragment.StoreObject; + } + } + + foreach (var derivedType in declaringType.GetDerivedTypes()) + { + var derivedStoreObject = StoreObjectIdentifier.Create(derivedType, storeObjectType); + if (derivedStoreObject != null + && property.GetColumnName(derivedStoreObject.Value) != null) + { + yield return derivedStoreObject.Value; + } + } + } /// /// Reads a value for this property from the given . diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index 7a77dfccea6..f4faed9457d 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -49,6 +49,7 @@ public override void Validate(IModel model, IDiagnosticsLogger @@ -497,7 +498,7 @@ protected virtual void ValidateSharedTableCompatibility( comment = nextComment; } - if (isExcluded.Equals(!nextEntityType.IsTableExcludedFromMigrations())) + if (isExcluded.Equals(!nextEntityType.IsTableExcludedFromMigrations(storeObject))) { throw new InvalidOperationException( RelationalStrings.IncompatibleTableExcludedMismatch( @@ -712,7 +713,12 @@ protected virtual void ValidateSharedColumnsCompatibility( foreach (var property in entityType.GetDeclaredProperties()) { - var columnName = property.GetColumnName(storeObject)!; + var columnName = property.GetColumnName(storeObject); + if (columnName == null) + { + continue; + } + missingConcurrencyTokens?.Remove(columnName); if (!propertyMappings.TryGetValue(columnName, out var duplicateProperty)) { @@ -1334,7 +1340,7 @@ protected override void ValidateInheritanceMapping( if (entityType.BaseType != null) { if (mappingStrategy != null - && mappingStrategy != entityType.BaseType.GetMappingStrategy()) + && mappingStrategy != (string?)entityType.BaseType[RelationalAnnotationNames.MappingStrategy]) { throw new InvalidOperationException( RelationalStrings.DerivedStrategy(entityType.DisplayName(), mappingStrategy)); @@ -1374,13 +1380,10 @@ protected override void ValidateInheritanceMapping( logger.TpcStoreGeneratedIdentityWarning(storeGeneratedProperty); } - if (entityType.GetDirectlyDerivedTypes().Any()) + foreach (var fk in entityType.GetDeclaredReferencingForeignKeys()) { - foreach (var fk in entityType.GetDeclaredReferencingForeignKeys()) - { - AssertNonInternal(fk, StoreObjectType.View); - AssertNonInternal(fk, StoreObjectType.Table); - } + AssertNonInternal(fk, StoreObjectType.View); + AssertNonInternal(fk, StoreObjectType.Table); } } else if (primaryKey == null) @@ -1423,8 +1426,9 @@ static void AssertNonInternal(IForeignKey foreignKey, StoreObjectType storeObjec if (!foreignKey.PrincipalKey.IsPrimaryKey() || foreignKey.PrincipalEntityType == foreignKey.DeclaringEntityType || !foreignKey.IsUnique + || foreignKey.DeclaringEntityType.FindPrimaryKey() == null #pragma warning disable EF1001 // Internal EF Core API usage. - || !PropertyListComparer.Instance.Equals(foreignKey.Properties, foreignKey.PrincipalKey.Properties)) + || !PropertyListComparer.Instance.Equals(foreignKey.Properties, foreignKey.DeclaringEntityType.FindPrimaryKey()!.Properties)) #pragma warning restore EF1001 // Internal EF Core API usage. { return; @@ -1545,6 +1549,130 @@ private static void ValidateTphMapping(IEntityType rootEntityType, bool forTable } } + /// + /// Validates the entity type mapping fragments. + /// + /// The model to validate. + /// The logger to use. + protected virtual void ValidateMappingFragments( + IModel model, + IDiagnosticsLogger logger) + { + foreach (var entityType in model.GetEntityTypes()) + { + var fragments = EntityTypeMappingFragment.Get(entityType); + if (fragments == null) + { + continue; + } + + if (entityType.BaseType != null + || entityType.GetDirectlyDerivedTypes().Any()) + { + throw new InvalidOperationException( + RelationalStrings.EntitySplittingHierarchy(entityType.DisplayName(), fragments.First().StoreObject.DisplayName())); + } + + var anyTableFragments = false; + var anyViewFragments = false; + foreach (var fragment in fragments) + { + var mainStoreObject = StoreObjectIdentifier.Create(entityType, fragment.StoreObject.StoreObjectType); + if (mainStoreObject == null) + { + throw new InvalidOperationException( + RelationalStrings.EntitySplittingUnmappedMainFragment( + entityType.DisplayName(), fragment.StoreObject.DisplayName(), fragment.StoreObject.StoreObjectType)); + } + + if (fragment.StoreObject == mainStoreObject) + { + throw new InvalidOperationException( + RelationalStrings.EntitySplittingConflictingMainFragment( + entityType.DisplayName(), fragment.StoreObject.DisplayName())); + } + + var propertiesFound = false; + foreach (var property in entityType.GetProperties()) + { + var columnName = property.GetColumnName(fragment.StoreObject); + if (columnName == null) + { + if (property.IsPrimaryKey()) + { + throw new InvalidOperationException( + RelationalStrings.EntitySplittingMissingPrimaryKey( + entityType.DisplayName(), fragment.StoreObject.DisplayName())); + } + + continue; + } + + if (!property.IsPrimaryKey()) + { + propertiesFound = true; + } + } + + if (!propertiesFound) + { + throw new InvalidOperationException( + RelationalStrings.EntitySplittingMissingProperties( + entityType.DisplayName(), fragment.StoreObject.DisplayName())); + } + + switch (fragment.StoreObject.StoreObjectType) + { + case StoreObjectType.Table: + anyTableFragments = true; + break; + case StoreObjectType.View: + anyViewFragments = true; + break; + } + } + + if (anyTableFragments) + { + ValidateMainMapping(entityType, StoreObjectIdentifier.Create(entityType, StoreObjectType.Table)); + } + + if (anyViewFragments) + { + ValidateMainMapping(entityType, StoreObjectIdentifier.Create(entityType, StoreObjectType.View)); + } + } + + static StoreObjectIdentifier? ValidateMainMapping(IEntityType entityType, StoreObjectIdentifier? mainObject) + { + foreach (var property in entityType.GetProperties()) + { + if (property.IsPrimaryKey()) + { + continue; + } + + if (mainObject != null) + { + var columnName = property.GetColumnName(mainObject.Value); + if (columnName != null) + { + mainObject = null; + } + } + } + + if (mainObject != null) + { + throw new InvalidOperationException( + RelationalStrings.EntitySplittingMissingPropertiesMainFragment( + entityType.DisplayName(), mainObject.Value.DisplayName())); + } + + return mainObject; + } + } + /// /// Validates the table-specific property overrides. /// @@ -1558,68 +1686,149 @@ protected virtual void ValidatePropertyOverrides( { foreach (var property in entityType.GetDeclaredProperties()) { - var tableOverrides = (SortedDictionary?) - property[RelationalAnnotationNames.RelationalOverrides]; - if (tableOverrides == null) + var storeObjectOverrides = RelationalPropertyOverrides.Get(property); + if (storeObjectOverrides == null) { continue; } - foreach (var storeOverride in tableOverrides.Keys) + foreach (var storeObjectOverride in storeObjectOverrides) { - var name = storeOverride.Name; - var schema = storeOverride.Schema; - switch (storeOverride.StoreObjectType) + if (GetAllMappedStoreObjects(property, storeObjectOverride.StoreObject.StoreObjectType) + .Any(o => o == storeObjectOverride.StoreObject)) { - case StoreObjectType.Table: - if (!entityType.GetDerivedTypesInclusive().Any( - d => - d.GetTableName() == name - && d.GetSchema() == schema)) - { - throw new InvalidOperationException( - RelationalStrings.TableOverrideMismatch( - entityType.DisplayName() + "." + property.Name, - (schema == null ? "" : schema + ".") + name)); - } + continue; + } - break; + var storeObject = storeObjectOverride.StoreObject; + switch (storeObject.StoreObjectType) + { + case StoreObjectType.Table: + throw new InvalidOperationException( + RelationalStrings.TableOverrideMismatch( + entityType.DisplayName() + "." + property.Name, + storeObjectOverride.StoreObject.DisplayName())); case StoreObjectType.View: - if (!entityType.GetDerivedTypesInclusive().Any( - d => - d.GetViewName() == name - && d.GetViewSchema() == schema)) - { - throw new InvalidOperationException( - RelationalStrings.ViewOverrideMismatch( - entityType.DisplayName() + "." + property.Name, - (schema == null ? "" : schema + ".") + name)); - } - - break; + throw new InvalidOperationException( + RelationalStrings.ViewOverrideMismatch( + entityType.DisplayName() + "." + property.Name, + storeObjectOverride.StoreObject.DisplayName())); case StoreObjectType.SqlQuery: - if (entityType.GetDerivedTypesInclusive().All(d => d.GetDefaultSqlQueryName() != name)) - { - throw new InvalidOperationException( - RelationalStrings.SqlQueryOverrideMismatch( - entityType.DisplayName() + "." + property.Name, name)); - } - - break; + throw new InvalidOperationException( + RelationalStrings.SqlQueryOverrideMismatch( + entityType.DisplayName() + "." + property.Name, + storeObjectOverride.StoreObject.DisplayName())); case StoreObjectType.Function: - if (entityType.GetDerivedTypesInclusive().All(d => d.GetFunctionName() != name)) - { - throw new InvalidOperationException( - RelationalStrings.FunctionOverrideMismatch( - entityType.DisplayName() + "." + property.Name, name)); - } - - break; + throw new InvalidOperationException( + RelationalStrings.FunctionOverrideMismatch( + entityType.DisplayName() + "." + property.Name, + storeObjectOverride.StoreObject.DisplayName())); default: - throw new NotSupportedException(storeOverride.StoreObjectType.ToString()); + throw new NotSupportedException(storeObject.StoreObjectType.ToString()); + } + } + } + } + } + + private static IEnumerable GetAllMappedStoreObjects( + IReadOnlyProperty property, StoreObjectType storeObjectType) + { + var mappingStrategy = property.DeclaringEntityType.GetMappingStrategy(); + if (property.IsPrimaryKey()) + { + var declaringStoreObject = StoreObjectIdentifier.Create(property.DeclaringEntityType, storeObjectType); + if (declaringStoreObject != null) + { + yield return declaringStoreObject.Value; + } + + if (storeObjectType == StoreObjectType.Function + || storeObjectType == StoreObjectType.SqlQuery) + { + yield break; + } + + foreach (var fragment in property.DeclaringEntityType.GetMappingFragments()) + { + yield return fragment.StoreObject; + } + + foreach (var containingType in property.DeclaringEntityType.GetDerivedTypes()) + { + var storeObject = StoreObjectIdentifier.Create(containingType, storeObjectType); + if (storeObject != null) + { + yield return storeObject.Value; + + if (mappingStrategy == RelationalAnnotationNames.TphMappingStrategy) + { + yield break; + } + } + } + } + else + { + var declaringStoreObject = StoreObjectIdentifier.Create(property.DeclaringEntityType, storeObjectType); + if (storeObjectType == StoreObjectType.Function + || storeObjectType == StoreObjectType.SqlQuery) + { + if (declaringStoreObject != null) + { + yield return declaringStoreObject.Value; + } + yield break; + } + + if (declaringStoreObject != null) + { + if (property.DeclaringEntityType.GetMappingFragments().Any()) + { + foreach (var fragment in property.DeclaringEntityType.GetMappingFragments()) + { + var overrides = RelationalPropertyOverrides.Find(property, fragment.StoreObject); + if (overrides != null) + { + yield return fragment.StoreObject; + yield break; + } } } + + yield return declaringStoreObject.Value; + if (mappingStrategy != RelationalAnnotationNames.TpcMappingStrategy) + { + yield break; + } } + + var tableFound = false; + var queue = new Queue(); + queue.Enqueue(property.DeclaringEntityType); + while (queue.Count > 0 && !tableFound) + { + foreach (var containingType in queue.Dequeue().GetDirectlyDerivedTypes()) + { + var storeObject = StoreObjectIdentifier.Create(containingType, storeObjectType); + if (storeObject != null) + { + yield return storeObject.Value; + tableFound = true; + if (mappingStrategy == RelationalAnnotationNames.TphMappingStrategy) + { + yield break; + } + } + + if (!tableFound + || mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy) + { + queue.Enqueue(containingType); + } + continue; + } + } } } diff --git a/src/EFCore.Relational/Metadata/Builders/CheckConstraintBuilder.cs b/src/EFCore.Relational/Metadata/Builders/CheckConstraintBuilder.cs index 0ef28f86c96..fd9c2fccebc 100644 --- a/src/EFCore.Relational/Metadata/Builders/CheckConstraintBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/CheckConstraintBuilder.cs @@ -63,6 +63,22 @@ public virtual CheckConstraintBuilder HasName(string name) return this; } + /// + /// Adds or updates an annotation on the check constraint. If an annotation with the key specified in + /// already exists, its value will be updated. + /// + /// The key of the annotation to be added or updated. + /// The value to be stored in the annotation. + /// The same builder instance so that multiple configuration calls can be chained. + public virtual CheckConstraintBuilder HasAnnotation(string annotation, object? value) + { + Check.NotEmpty(annotation, nameof(annotation)); + + Builder.HasAnnotation(annotation, value, ConfigurationSource.Explicit); + + return this; + } + #region Hidden System.Object members /// diff --git a/src/EFCore.Relational/Metadata/Builders/ColumnBuilder.cs b/src/EFCore.Relational/Metadata/Builders/ColumnBuilder.cs new file mode 100644 index 00000000000..484e6fd5e7f --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/ColumnBuilder.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +public class ColumnBuilder : IInfrastructure +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ColumnBuilder(in StoreObjectIdentifier storeObject, PropertyBuilder propertyBuilder) + { + Check.DebugAssert(storeObject.StoreObjectType == StoreObjectType.Table, + "StoreObjectType should be Table, not " + storeObject.StoreObjectType); + + InternalOverrides = RelationalPropertyOverrides.GetOrCreate( + propertyBuilder.Metadata, storeObject, ConfigurationSource.Explicit); + PropertyBuilder = propertyBuilder; + } + + /// + /// The table-specific overrides being configured. + /// + public virtual IMutableRelationalPropertyOverrides Overrides => InternalOverrides; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual RelationalPropertyOverrides InternalOverrides { get; } + + private PropertyBuilder PropertyBuilder { get; } + + /// + /// Configures the column that the property maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The name of the column. + /// The same builder instance so that multiple calls can be chained. + public virtual ColumnBuilder HasColumnName(string? name) + { + Check.NullButNotEmpty(name, nameof(name)); + + InternalOverrides.SetColumnName(name, ConfigurationSource.Explicit); + + return this; + } + + PropertyBuilder IInfrastructure.Instance => PropertyBuilder; + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder`.cs b/src/EFCore.Relational/Metadata/Builders/ColumnBuilder`.cs similarity index 50% rename from src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder`.cs rename to src/EFCore.Relational/Metadata/Builders/ColumnBuilder`.cs index af93328a20f..51e666742a8 100644 --- a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder`.cs +++ b/src/EFCore.Relational/Metadata/Builders/ColumnBuilder`.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -7,9 +7,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// Instances of this class are returned from methods when using the API /// and it is not designed to be directly constructed in your application code. /// -/// The entity type being configured. -public class OwnedNavigationTableBuilder : OwnedNavigationTableBuilder - where TEntity : class +public class ColumnBuilder : ColumnBuilder, IInfrastructure> { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -18,19 +16,24 @@ public class OwnedNavigationTableBuilder : OwnedNavigationTableBuilder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public OwnedNavigationTableBuilder(string? name, string? schema, OwnedNavigationBuilder referenceOwnershipBuilder) - : base(name, schema, referenceOwnershipBuilder) + public ColumnBuilder(in StoreObjectIdentifier storeObject, PropertyBuilder propertyBuilder) + : base(storeObject, propertyBuilder) { } + private PropertyBuilder PropertyBuilder + => (PropertyBuilder) ((IInfrastructure)this).Instance; + /// - /// Configures the table to be ignored by migrations. + /// Configures the column that the property maps to when targeting a relational database. /// /// - /// See Database migrations for more information. + /// See Modeling entity types and relationships for more information and examples. /// - /// A value indicating whether the table should be managed by migrations. + /// The name of the column. /// The same builder instance so that multiple calls can be chained. - public new virtual OwnedNavigationTableBuilder ExcludeFromMigrations(bool excluded = true) - => (OwnedNavigationTableBuilder)base.ExcludeFromMigrations(excluded); + public new virtual ColumnBuilder HasColumnName(string? name) + => (ColumnBuilder)base.HasColumnName(name); + + PropertyBuilder IInfrastructure>.Instance => PropertyBuilder; } diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder.cs new file mode 100644 index 00000000000..7e7f7596b1d --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder.cs @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +public class OwnedNavigationSplitTableBuilder : IInfrastructure +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public OwnedNavigationSplitTableBuilder(in StoreObjectIdentifier storeObject, OwnedNavigationBuilder ownedNavigationBuilder) + { + Check.DebugAssert(storeObject.StoreObjectType == StoreObjectType.Table, + "StoreObjectType should be Table, not " + storeObject.StoreObjectType); + + MappingFragment = EntityTypeMappingFragment.GetOrCreate( + ownedNavigationBuilder.OwnedEntityType, storeObject, ConfigurationSource.Explicit); + OwnedNavigationBuilder = ownedNavigationBuilder; + } + + /// + /// The specified table name. + /// + public virtual string Name => MappingFragment.StoreObject.Name; + + /// + /// The specified table schema. + /// + public virtual string? Schema => MappingFragment.StoreObject.Schema; + + /// + /// The mapping fragment being configured. + /// + public virtual IMutableEntityTypeMappingFragment MappingFragment { get; } + + private OwnedNavigationBuilder OwnedNavigationBuilder { get; } + + /// + /// Configures the table to be ignored by migrations. + /// + /// + /// See Database migrations for more information. + /// + /// A value indicating whether the table should be managed by migrations. + /// The same builder instance so that multiple calls can be chained. + public virtual OwnedNavigationSplitTableBuilder ExcludeFromMigrations(bool excluded = true) + { + MappingFragment.IsTableExcludedFromMigrations = excluded; + + return this; + } + + /// + /// Configures a database trigger on the table. + /// + /// The name of the trigger. + /// A builder that can be used to configure the database trigger. + /// + /// See Database triggers for more information and examples. + /// + public virtual TriggerBuilder HasTrigger(string name) + => new((Trigger)InternalTriggerBuilder.HasTrigger( + (IConventionEntityType)MappingFragment.EntityType, + name, + Name, + Schema, + ConfigurationSource.Explicit)!); + + /// + /// Maps the property to a column on the current table and returns an object that can be used + /// to provide table-specific configuration if the property is mapped to more than one table. + /// + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ColumnBuilder Property(string propertyName) + => new(MappingFragment.StoreObject, OwnedNavigationBuilder.Property(propertyName)); + + /// + /// Maps the property to a column on the current table and returns an object that can be used + /// to provide table-specific configuration if the property is mapped to more than one table. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ColumnBuilder Property(string propertyName) + => new(MappingFragment.StoreObject, OwnedNavigationBuilder.Property(propertyName)); + + OwnedNavigationBuilder IInfrastructure.Instance => OwnedNavigationBuilder; + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder``.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder``.cs new file mode 100644 index 00000000000..ee02ff954c5 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder``.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// The entity type owning the relationship. +/// The dependent entity type of the relationship. +public class OwnedNavigationSplitTableBuilder : + OwnedNavigationSplitTableBuilder, + IInfrastructure> + where TOwnerEntity : class + where TDependentEntity : class +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public OwnedNavigationSplitTableBuilder( + in StoreObjectIdentifier storeObject, + OwnedNavigationBuilder ownedNavigationBuilder) + : base(storeObject, ownedNavigationBuilder) + { + } + + private OwnedNavigationBuilder OwnedNavigationBuilder + => (OwnedNavigationBuilder)((IInfrastructure)this) + .GetInfrastructure(); + + /// + /// Configures the table to be ignored by migrations. + /// + /// + /// See Database migrations for more information. + /// + /// A value indicating whether the table should be managed by migrations. + /// The same builder instance so that multiple calls can be chained. + public new virtual OwnedNavigationSplitTableBuilder ExcludeFromMigrations(bool excluded = true) + => (OwnedNavigationSplitTableBuilder)base.ExcludeFromMigrations(excluded); + + /// + /// Maps the property to a column on the current table and returns an object that can be used + /// to provide table-specific configuration if the property is mapped to more than one table. + /// + /// + /// A lambda expression representing the property to be configured (blog => blog.Url). + /// + /// An object that can be used to configure the property. + public virtual ColumnBuilder Property(Expression> propertyExpression) + => new(MappingFragment.StoreObject, OwnedNavigationBuilder.Property(propertyExpression)); + + OwnedNavigationBuilder IInfrastructure>.Instance + => OwnedNavigationBuilder; +} diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitViewBuilder.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitViewBuilder.cs new file mode 100644 index 00000000000..4c02be8b994 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitViewBuilder.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +public class OwnedNavigationSplitViewBuilder : IInfrastructure +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public OwnedNavigationSplitViewBuilder(in StoreObjectIdentifier storeObject, OwnedNavigationBuilder ownedNavigationBuilder) + { + Check.DebugAssert(storeObject.StoreObjectType == StoreObjectType.View, + "StoreObjectType should be View, not " + storeObject.StoreObjectType); + + MappingFragment = EntityTypeMappingFragment.GetOrCreate( + ownedNavigationBuilder.OwnedEntityType, storeObject, ConfigurationSource.Explicit); + OwnedNavigationBuilder = ownedNavigationBuilder; + } + + /// + /// The specified view name. + /// + public virtual string Name => MappingFragment.StoreObject.Name; + + /// + /// The specified view schema. + /// + public virtual string? Schema => MappingFragment.StoreObject.Schema; + + /// + /// The mapping fragment being configured. + /// + public virtual IMutableEntityTypeMappingFragment MappingFragment { get; } + + private OwnedNavigationBuilder OwnedNavigationBuilder { get; } + + /// + /// Maps the property to a column on the current view and returns an object that can be used + /// to provide view-specific configuration if the property is mapped to more than one view. + /// + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ViewColumnBuilder Property(string propertyName) + => new(MappingFragment.StoreObject, OwnedNavigationBuilder.Property(propertyName)); + + /// + /// Maps the property to a column on the current view and returns an object that can be used + /// to provide view-specific configuration if the property is mapped to more than one view. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ViewColumnBuilder Property(string propertyName) + => new(MappingFragment.StoreObject, OwnedNavigationBuilder.Property(propertyName)); + + OwnedNavigationBuilder IInfrastructure.Instance => OwnedNavigationBuilder; + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitViewBuilder``.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitViewBuilder``.cs new file mode 100644 index 00000000000..bb27021503c --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitViewBuilder``.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// The entity type owning the relationship. +/// The dependent entity type of the relationship. +public class OwnedNavigationSplitViewBuilder : + OwnedNavigationSplitViewBuilder, + IInfrastructure> + where TOwnerEntity : class + where TDependentEntity : class +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public OwnedNavigationSplitViewBuilder( + in StoreObjectIdentifier storeObject, + OwnedNavigationBuilder ownedNavigationBuilder) + : base(storeObject, ownedNavigationBuilder) + { + } + + private OwnedNavigationBuilder OwnedNavigationBuilder + => (OwnedNavigationBuilder)((IInfrastructure)this) + .GetInfrastructure(); + + /// + /// Maps the property to a column on the current view and returns an object that can be used + /// to provide view-specific configuration if the property is mapped to more than one view. + /// + /// + /// A lambda expression representing the property to be configured (blog => blog.Url). + /// + /// An object that can be used to configure the property. + public virtual ViewColumnBuilder Property(Expression> propertyExpression) + => new(MappingFragment.StoreObject, OwnedNavigationBuilder.Property(propertyExpression)); + + OwnedNavigationBuilder IInfrastructure>.Instance + => OwnedNavigationBuilder; +} diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder.cs index f8c5214cbe4..243907609a0 100644 --- a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder.cs @@ -1,7 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; +using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -9,7 +10,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// Instances of this class are returned from methods when using the API /// and it is not designed to be directly constructed in your application code. /// -public class OwnedNavigationTableBuilder +public class OwnedNavigationTableBuilder : IInfrastructure { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -18,32 +19,37 @@ public class OwnedNavigationTableBuilder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public OwnedNavigationTableBuilder(string? name, string? schema, OwnedNavigationBuilder ownedNavigationBuilder) + public OwnedNavigationTableBuilder(in StoreObjectIdentifier? storeObject, OwnedNavigationBuilder ownedNavigationBuilder) { - Name = name; - Schema = schema; + StoreObject = storeObject; OwnedNavigationBuilder = ownedNavigationBuilder; } /// /// The specified table name. /// - public virtual string? Name { get; } + public virtual string? Name => StoreObject?.Name; /// /// The specified table schema. /// - public virtual string? Schema { get; } + public virtual string? Schema => StoreObject?.Schema; /// - /// The entity type being configured. + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IMutableEntityType Metadata => OwnedNavigationBuilder.OwnedEntityType; + [EntityFrameworkInternal] + protected virtual StoreObjectIdentifier? StoreObject { get; } /// - /// The entity type builder. + /// The entity type being configured. /// - public virtual OwnedNavigationBuilder OwnedNavigationBuilder { get; } + public virtual IMutableEntityType Metadata => OwnedNavigationBuilder.OwnedEntityType; + + private OwnedNavigationBuilder OwnedNavigationBuilder { get; } /// /// Configures the table to be ignored by migrations. @@ -60,6 +66,53 @@ public virtual OwnedNavigationTableBuilder ExcludeFromMigrations(bool excluded = return this; } + /// + /// Configures a database trigger on the table. + /// + /// The name of the trigger. + /// A builder that can be used to configure the database trigger. + /// + /// See Database triggers for more information and examples. + /// + public virtual TriggerBuilder HasTrigger(string name) + => new((Trigger)InternalTriggerBuilder.HasTrigger( + (IConventionEntityType)Metadata, + name, + Name, + Schema, + ConfigurationSource.Explicit)!); + + /// + /// Maps the property to a column on the current table and returns an object that can be used + /// to provide table-specific configuration if the property is mapped to more than one table. + /// + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ColumnBuilder Property(string propertyName) + => new(GetStoreObjectIdentifier(), OwnedNavigationBuilder.Property(propertyName)); + + /// + /// Maps the property to a column on the current table and returns an object that can be used + /// to provide table-specific configuration if the property is mapped to more than one table. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ColumnBuilder Property(string propertyName) + => new(GetStoreObjectIdentifier(), OwnedNavigationBuilder.Property(propertyName)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual StoreObjectIdentifier GetStoreObjectIdentifier() + => StoreObject ?? throw new InvalidOperationException(RelationalStrings.MappingFragmentMissingName); + + OwnedNavigationBuilder IInfrastructure.Instance => OwnedNavigationBuilder; + #region Hidden System.Object members /// diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder``.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder``.cs new file mode 100644 index 00000000000..ae017286cce --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder``.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// The entity type owning the relationship. +/// The dependent entity type of the relationship. +public class OwnedNavigationTableBuilder : + OwnedNavigationTableBuilder, + IInfrastructure> + where TOwnerEntity : class + where TDependentEntity : class +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public OwnedNavigationTableBuilder( + in StoreObjectIdentifier? storeObject, + OwnedNavigationBuilder ownedNavigationBuilder) + : base(storeObject, ownedNavigationBuilder) + { + } + + private OwnedNavigationBuilder OwnedNavigationBuilder + => (OwnedNavigationBuilder)((IInfrastructure)this) + .GetInfrastructure(); + + /// + /// Configures the table to be ignored by migrations. + /// + /// + /// See Database migrations for more information. + /// + /// A value indicating whether the table should be managed by migrations. + /// The same builder instance so that multiple calls can be chained. + public new virtual OwnedNavigationTableBuilder ExcludeFromMigrations(bool excluded = true) + => (OwnedNavigationTableBuilder)base.ExcludeFromMigrations(excluded); + + /// + /// Maps the property to a column on the current table and returns an object that can be used + /// to provide table-specific configuration if the property is mapped to more than one table. + /// + /// + /// A lambda expression representing the property to be configured (blog => blog.Url). + /// + /// An object that can be used to configure the property. + public virtual ColumnBuilder Property(Expression> propertyExpression) + => new(GetStoreObjectIdentifier(), OwnedNavigationBuilder.Property(propertyExpression)); + + OwnedNavigationBuilder IInfrastructure>.Instance + => OwnedNavigationBuilder; +} diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableValuedFunctionBuilder.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableValuedFunctionBuilder.cs new file mode 100644 index 00000000000..1dd69f117c4 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableValuedFunctionBuilder.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API for configuring a that an entity type is mapped to. +/// +public class OwnedNavigationTableValuedFunctionBuilder : DbFunctionBuilderBase, IInfrastructure +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public OwnedNavigationTableValuedFunctionBuilder( + IMutableDbFunction function, + OwnedNavigationBuilder ownedNavigationBuilder) + : base(function) + { + OwnedNavigationBuilder = ownedNavigationBuilder; + } + + private OwnedNavigationBuilder OwnedNavigationBuilder { get; } + + /// + /// Sets the name of the database function. + /// + /// + /// See Database functions for more information and examples. + /// + /// The name of the function in the database. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual OwnedNavigationTableValuedFunctionBuilder HasName(string name) + => (OwnedNavigationTableValuedFunctionBuilder)base.HasName(name); + + /// + /// Sets the schema of the database function. + /// + /// + /// See Database functions for more information and examples. + /// + /// The schema of the function in the database. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual OwnedNavigationTableValuedFunctionBuilder HasSchema(string? schema) + => (OwnedNavigationTableValuedFunctionBuilder)base.HasSchema(schema); + + /// + /// Marks whether the database function is built-in. + /// + /// + /// See Database functions for more information and examples. + /// + /// The value indicating whether the database function is built-in. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual OwnedNavigationTableValuedFunctionBuilder IsBuiltIn(bool builtIn = true) + => (OwnedNavigationTableValuedFunctionBuilder)base.IsBuiltIn(builtIn); + + OwnedNavigationBuilder IInfrastructure.Instance => OwnedNavigationBuilder; +} diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableValuedFunctionBuilder``.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableValuedFunctionBuilder``.cs new file mode 100644 index 00000000000..47a2041eb0a --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableValuedFunctionBuilder``.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API for configuring a that an entity type is mapped to. +/// +/// The entity type owning the relationship. +/// The dependent entity type of the relationship. +public class OwnedNavigationTableValuedFunctionBuilder : + OwnedNavigationTableValuedFunctionBuilder, + IInfrastructure> + where TOwnerEntity : class + where TDependentEntity : class +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public OwnedNavigationTableValuedFunctionBuilder( + IMutableDbFunction function, + OwnedNavigationBuilder ownedNavigationBuilder) + : base(function, ownedNavigationBuilder) + { + } + + private OwnedNavigationBuilder OwnedNavigationBuilder + => (OwnedNavigationBuilder)((IInfrastructure)this) + .GetInfrastructure(); + + /// + /// Sets the name of the database function. + /// + /// + /// See Database functions for more information and examples. + /// + /// The name of the function in the database. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual OwnedNavigationTableValuedFunctionBuilder HasName(string name) + => (OwnedNavigationTableValuedFunctionBuilder)base.HasName(name); + + /// + /// Sets the schema of the database function. + /// + /// + /// See Database functions for more information and examples. + /// + /// The schema of the function in the database. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual OwnedNavigationTableValuedFunctionBuilder HasSchema(string? schema) + => (OwnedNavigationTableValuedFunctionBuilder)base.HasSchema(schema); + + /// + /// Marks whether the database function is built-in. + /// + /// + /// See Database functions for more information and examples. + /// + /// The value indicating whether the database function is built-in. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual OwnedNavigationTableValuedFunctionBuilder IsBuiltIn(bool builtIn = true) + => (OwnedNavigationTableValuedFunctionBuilder)base.IsBuiltIn(builtIn); + + OwnedNavigationBuilder IInfrastructure>.Instance + => OwnedNavigationBuilder; +} diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationViewBuilder.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationViewBuilder.cs new file mode 100644 index 00000000000..77b1f92a435 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationViewBuilder.cs @@ -0,0 +1,100 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +public class OwnedNavigationViewBuilder : IInfrastructure +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public OwnedNavigationViewBuilder(in StoreObjectIdentifier storeObject, OwnedNavigationBuilder ownedNavigationBuilder) + { + Check.DebugAssert(storeObject.StoreObjectType == StoreObjectType.View, + "StoreObjectType should be View, not " + storeObject.StoreObjectType); + + StoreObject = storeObject; + OwnedNavigationBuilder = ownedNavigationBuilder; + } + + /// + /// The specified view name. + /// + public virtual string Name => StoreObject.Name; + + /// + /// The specified view schema. + /// + public virtual string? Schema => StoreObject.Schema; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual StoreObjectIdentifier StoreObject { get; } + + private OwnedNavigationBuilder OwnedNavigationBuilder { get; } + + /// + /// Maps the property to a column on the current view and returns an object that can be used + /// to provide view-specific configuration if the property is mapped to more than one view. + /// + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ViewColumnBuilder Property(string propertyName) + => new(StoreObject, OwnedNavigationBuilder.Property(propertyName)); + + /// + /// Maps the property to a column on the current view and returns an object that can be used + /// to provide view-specific configuration if the property is mapped to more than one view. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ViewColumnBuilder Property(string propertyName) + => new(StoreObject, OwnedNavigationBuilder.Property(propertyName)); + + OwnedNavigationBuilder IInfrastructure.Instance => OwnedNavigationBuilder; + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationViewBuilder``.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationViewBuilder``.cs new file mode 100644 index 00000000000..e10ac110b68 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationViewBuilder``.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// The entity type owning the relationship. +/// The dependent entity type of the relationship. +public class OwnedNavigationViewBuilder : + OwnedNavigationViewBuilder, + IInfrastructure> + where TOwnerEntity : class + where TDependentEntity : class +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public OwnedNavigationViewBuilder( + in StoreObjectIdentifier storeObject, + OwnedNavigationBuilder ownedNavigationBuilder) + : base(storeObject, ownedNavigationBuilder) + { + } + + private OwnedNavigationBuilder OwnedNavigationBuilder + => (OwnedNavigationBuilder)((IInfrastructure)this) + .GetInfrastructure(); + + /// + /// Maps the property to a column on the current view and returns an object that can be used + /// to provide view-specific configuration if the property is mapped to more than one view. + /// + /// + /// A lambda expression representing the property to be configured (blog => blog.Url). + /// + /// An object that can be used to configure the property. + public virtual ViewColumnBuilder Property(Expression> propertyExpression) + => new(StoreObject, OwnedNavigationBuilder.Property(propertyExpression)); + + OwnedNavigationBuilder IInfrastructure>.Instance + => OwnedNavigationBuilder; +} diff --git a/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder.cs b/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder.cs new file mode 100644 index 00000000000..b9f1cf14db7 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder.cs @@ -0,0 +1,129 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +public class SplitTableBuilder : IInfrastructure +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public SplitTableBuilder(in StoreObjectIdentifier storeObject, EntityTypeBuilder entityTypeBuilder) + { + Check.DebugAssert(storeObject.StoreObjectType == StoreObjectType.Table, + "StoreObjectType should be Table, not " + storeObject.StoreObjectType); + + MappingFragment = EntityTypeMappingFragment.GetOrCreate( + entityTypeBuilder.Metadata, storeObject, ConfigurationSource.Explicit); + EntityTypeBuilder = entityTypeBuilder; + } + + /// + /// The specified table name. + /// + public virtual string Name => MappingFragment.StoreObject.Name; + + /// + /// The specified table schema. + /// + public virtual string? Schema => MappingFragment.StoreObject.Schema; + + /// + /// The mapping fragment being configured. + /// + public virtual IMutableEntityTypeMappingFragment MappingFragment { get; } + + private EntityTypeBuilder EntityTypeBuilder { get; } + + /// + /// Configures the table to be ignored by migrations. + /// + /// + /// See Database migrations for more information and examples. + /// + /// A value indicating whether the table should be managed by migrations. + /// The same builder instance so that multiple calls can be chained. + public virtual SplitTableBuilder ExcludeFromMigrations(bool excluded = true) + { + MappingFragment.IsTableExcludedFromMigrations = excluded; + + return this; + } + + /// + /// Configures a database trigger on the table. + /// + /// The name of the trigger. + /// A builder that can be used to configure the database trigger. + /// + /// See Database triggers for more information and examples. + /// + public virtual TriggerBuilder HasTrigger(string name) + => new((Trigger)InternalTriggerBuilder.HasTrigger( + (IConventionEntityType)MappingFragment.EntityType, + name, + Name, + Schema, + ConfigurationSource.Explicit)!); + + /// + /// Maps the property to a column on the current table and returns an object that can be used + /// to provide table-specific configuration if the property is mapped to more than one table. + /// + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ColumnBuilder Property(string propertyName) + => new(MappingFragment.StoreObject, EntityTypeBuilder.Property(propertyName)); + + /// + /// Maps the property to a column on the current table and returns an object that can be used + /// to provide table-specific configuration if the property is mapped to more than one table. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ColumnBuilder Property(string propertyName) + => new(MappingFragment.StoreObject, EntityTypeBuilder.Property(propertyName)); + + EntityTypeBuilder IInfrastructure.Instance => EntityTypeBuilder; + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder`.cs b/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder`.cs new file mode 100644 index 00000000000..6d02e378b27 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder`.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// The entity type being configured. +public class SplitTableBuilder : SplitTableBuilder, IInfrastructure> + where TEntity : class +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public SplitTableBuilder(in StoreObjectIdentifier storeObject, EntityTypeBuilder entityTypeBuilder) + : base(storeObject, entityTypeBuilder) + { + } + private EntityTypeBuilder EntityTypeBuilder + => (EntityTypeBuilder)((IInfrastructure)this).GetInfrastructure(); + + /// + /// Configures the table to be ignored by migrations. + /// + /// + /// See Database migrations for more information and examples. + /// + /// A value indicating whether the table should be managed by migrations. + /// The same builder instance so that multiple calls can be chained. + public new virtual SplitTableBuilder ExcludeFromMigrations(bool excluded = true) + => (SplitTableBuilder)base.ExcludeFromMigrations(excluded); + + /// + /// Maps the property to a column on the current table and returns an object that can be used + /// to provide table-specific configuration if the property is mapped to more than one table. + /// + /// + /// A lambda expression representing the property to be configured (blog => blog.Url). + /// + /// An object that can be used to configure the property. + public virtual ColumnBuilder Property(Expression> propertyExpression) + => new(MappingFragment.StoreObject, EntityTypeBuilder.Property(propertyExpression)); + + EntityTypeBuilder IInfrastructure>.Instance => EntityTypeBuilder; +} diff --git a/src/EFCore.Relational/Metadata/Builders/SplitViewBuilder.cs b/src/EFCore.Relational/Metadata/Builders/SplitViewBuilder.cs new file mode 100644 index 00000000000..00e9f14a992 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/SplitViewBuilder.cs @@ -0,0 +1,98 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +public class SplitViewBuilder : IInfrastructure +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public SplitViewBuilder(in StoreObjectIdentifier storeObject, EntityTypeBuilder entityTypeBuilder) + { + Check.DebugAssert(storeObject.StoreObjectType == StoreObjectType.View, + "StoreObjectType should be View, not " + storeObject.StoreObjectType); + + MappingFragment = EntityTypeMappingFragment.GetOrCreate( + entityTypeBuilder.Metadata, storeObject, ConfigurationSource.Explicit); + EntityTypeBuilder = entityTypeBuilder; + } + + /// + /// The specified view name. + /// + public virtual string Name => MappingFragment.StoreObject.Name; + + /// + /// The specified view schema. + /// + public virtual string? Schema => MappingFragment.StoreObject.Schema; + + /// + /// The mapping fragment being configured. + /// + public virtual IMutableEntityTypeMappingFragment MappingFragment { get; } + + private EntityTypeBuilder EntityTypeBuilder { get; } + + /// + /// Maps the property to a column on the current view and returns an object that can be used + /// to provide view-specific configuration if the property is mapped to more than one view. + /// + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ViewColumnBuilder Property(string propertyName) + => new(MappingFragment.StoreObject, EntityTypeBuilder.Property(propertyName)); + + /// + /// Maps the property to a column on the current view and returns an object that can be used + /// to provide view-specific configuration if the property is mapped to more than one view. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ViewColumnBuilder Property(string propertyName) + => new(MappingFragment.StoreObject, EntityTypeBuilder.Property(propertyName)); + + EntityTypeBuilder IInfrastructure.Instance => EntityTypeBuilder; + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore.Relational/Metadata/Builders/SplitViewBuilder`.cs b/src/EFCore.Relational/Metadata/Builders/SplitViewBuilder`.cs new file mode 100644 index 00000000000..d260bd10490 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/SplitViewBuilder`.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// The entity type being configured. +public class SplitViewBuilder : SplitViewBuilder, IInfrastructure> + where TEntity : class +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public SplitViewBuilder(in StoreObjectIdentifier storeObject, EntityTypeBuilder entityTypeBuilder) + : base(storeObject, entityTypeBuilder) + { + } + + private EntityTypeBuilder EntityTypeBuilder + => (EntityTypeBuilder)((IInfrastructure)this).GetInfrastructure(); + + /// + /// Maps the property to a column on the current view and returns an object that can be used + /// to provide view-specific configuration if the property is mapped to more than one view. + /// + /// + /// A lambda expression representing the property to be configured (blog => blog.Url). + /// + /// An object that can be used to configure the property. + public virtual ViewColumnBuilder Property(Expression> propertyExpression) + => new(MappingFragment.StoreObject, EntityTypeBuilder.Property(propertyExpression)); + + EntityTypeBuilder IInfrastructure>.Instance => EntityTypeBuilder; +} diff --git a/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs b/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs index 9dd2053f25b..57169a80b55 100644 --- a/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs @@ -10,7 +10,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// Instances of this class are returned from methods when using the API /// and it is not designed to be directly constructed in your application code. /// -public class TableBuilder +public class TableBuilder : IInfrastructure { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -19,32 +19,37 @@ public class TableBuilder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public TableBuilder(string? name, string? schema, EntityTypeBuilder entityTypeBuilder) + public TableBuilder(in StoreObjectIdentifier? storeObject, EntityTypeBuilder entityTypeBuilder) { - Name = name; - Schema = schema; + StoreObject = storeObject; EntityTypeBuilder = entityTypeBuilder; } /// /// The specified table name. /// - public virtual string? Name { get; } + public virtual string? Name => StoreObject?.Name; /// /// The specified table schema. /// - public virtual string? Schema { get; } + public virtual string? Schema => StoreObject?.Schema; /// - /// The entity type being configured. + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual IMutableEntityType Metadata => EntityTypeBuilder.Metadata; + [EntityFrameworkInternal] + protected virtual StoreObjectIdentifier? StoreObject { get; } /// - /// The entity type builder. + /// The entity type being configured. /// - public virtual EntityTypeBuilder EntityTypeBuilder { get; } + public virtual IMutableEntityType Metadata => EntityTypeBuilder.Metadata; + + private EntityTypeBuilder EntityTypeBuilder { get; } /// /// Configures the table to be ignored by migrations. @@ -77,6 +82,37 @@ public virtual TriggerBuilder HasTrigger(string name) Schema, ConfigurationSource.Explicit)!); + /// + /// Maps the property to a column on the current table and returns an object that can be used + /// to provide table-specific configuration if the property is mapped to more than one table. + /// + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ColumnBuilder Property(string propertyName) + => new(GetStoreObjectIdentifier(), EntityTypeBuilder.Property(propertyName)); + + /// + /// Maps the property to a column on the current table and returns an object that can be used + /// to provide table-specific configuration if the property is mapped to more than one table. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ColumnBuilder Property(string propertyName) + => new(GetStoreObjectIdentifier(), EntityTypeBuilder.Property(propertyName)); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual StoreObjectIdentifier GetStoreObjectIdentifier() + => StoreObject ?? throw new InvalidOperationException(RelationalStrings.MappingFragmentMissingName); + + EntityTypeBuilder IInfrastructure.Instance => EntityTypeBuilder; + #region Hidden System.Object members /// diff --git a/src/EFCore.Relational/Metadata/Builders/TableBuilder`.cs b/src/EFCore.Relational/Metadata/Builders/TableBuilder`.cs index c02dd30e4f5..df3f91f09a8 100644 --- a/src/EFCore.Relational/Metadata/Builders/TableBuilder`.cs +++ b/src/EFCore.Relational/Metadata/Builders/TableBuilder`.cs @@ -8,7 +8,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// and it is not designed to be directly constructed in your application code. /// /// The entity type being configured. -public class TableBuilder : TableBuilder +public class TableBuilder : TableBuilder, IInfrastructure> where TEntity : class { /// @@ -18,11 +18,14 @@ public class TableBuilder : TableBuilder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public TableBuilder(string? name, string? schema, EntityTypeBuilder entityTypeBuilder) - : base(name, schema, entityTypeBuilder) + public TableBuilder(in StoreObjectIdentifier? storeObject, EntityTypeBuilder entityTypeBuilder) + : base(storeObject, entityTypeBuilder) { } + private EntityTypeBuilder EntityTypeBuilder + => (EntityTypeBuilder)((IInfrastructure)this).Instance; + /// /// Configures the table to be ignored by migrations. /// @@ -33,4 +36,17 @@ public TableBuilder(string? name, string? schema, EntityTypeBuilder entityTypeBu /// The same builder instance so that multiple calls can be chained. public new virtual TableBuilder ExcludeFromMigrations(bool excluded = true) => (TableBuilder)base.ExcludeFromMigrations(excluded); + + /// + /// Maps the property to a column on the current table and returns an object that can be used + /// to provide table-specific configuration if the property is mapped to more than one table. + /// + /// + /// A lambda expression representing the property to be configured (blog => blog.Url). + /// + /// An object that can be used to configure the property. + public virtual ColumnBuilder Property(Expression> propertyExpression) + => new(GetStoreObjectIdentifier(), EntityTypeBuilder.Property(propertyExpression)); + + EntityTypeBuilder IInfrastructure>.Instance => EntityTypeBuilder; } diff --git a/src/EFCore.Relational/Metadata/Builders/TableValuedFunctionBuilder.cs b/src/EFCore.Relational/Metadata/Builders/TableValuedFunctionBuilder.cs index 4068cf37151..502431374d7 100644 --- a/src/EFCore.Relational/Metadata/Builders/TableValuedFunctionBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/TableValuedFunctionBuilder.cs @@ -4,9 +4,9 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// -/// Provides a simple API for configuring a . +/// Provides a simple API for configuring a that an entity type is mapped to. /// -public class TableValuedFunctionBuilder : DbFunctionBuilderBase +public class TableValuedFunctionBuilder : DbFunctionBuilderBase, IInfrastructure { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -15,11 +15,14 @@ public class TableValuedFunctionBuilder : DbFunctionBuilderBase /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public TableValuedFunctionBuilder(IMutableDbFunction function) + public TableValuedFunctionBuilder(IMutableDbFunction function, EntityTypeBuilder entityTypeBuilder) : base(function) { + EntityTypeBuilder = entityTypeBuilder; } + private EntityTypeBuilder EntityTypeBuilder { get; } + /// /// Sets the name of the database function. /// @@ -41,4 +44,17 @@ public TableValuedFunctionBuilder(IMutableDbFunction function) /// The same builder instance so that multiple configuration calls can be chained. public new virtual TableValuedFunctionBuilder HasSchema(string? schema) => (TableValuedFunctionBuilder)base.HasSchema(schema); + + /// + /// Marks whether the database function is built-in. + /// + /// + /// See Database functions for more information and examples. + /// + /// The value indicating whether the database function is built-in. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual TableValuedFunctionBuilder IsBuiltIn(bool builtIn = true) + => (TableValuedFunctionBuilder)base.IsBuiltIn(builtIn); + + EntityTypeBuilder IInfrastructure.Instance => EntityTypeBuilder; } diff --git a/src/EFCore.Relational/Metadata/Builders/TableValuedFunctionBuilder`.cs b/src/EFCore.Relational/Metadata/Builders/TableValuedFunctionBuilder`.cs new file mode 100644 index 00000000000..013f8633fcb --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/TableValuedFunctionBuilder`.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Provides a simple API for configuring a that an entity type is mapped to. +/// +/// The entity type being configured. +public class TableValuedFunctionBuilder : TableValuedFunctionBuilder, IInfrastructure> + where TEntity : class +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public TableValuedFunctionBuilder(IMutableDbFunction function, EntityTypeBuilder entityTypeBuilder) + : base(function, entityTypeBuilder) + { + } + + private EntityTypeBuilder EntityTypeBuilder + => (EntityTypeBuilder)((IInfrastructure)this).Instance; + + /// + /// Sets the name of the database function. + /// + /// + /// See Database functions for more information and examples. + /// + /// The name of the function in the database. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual TableValuedFunctionBuilder HasName(string name) + => (TableValuedFunctionBuilder)base.HasName(name); + + /// + /// Sets the schema of the database function. + /// + /// + /// See Database functions for more information and examples. + /// + /// The schema of the function in the database. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual TableValuedFunctionBuilder HasSchema(string? schema) + => (TableValuedFunctionBuilder)base.HasSchema(schema); + + /// + /// Marks whether the database function is built-in. + /// + /// + /// See Database functions for more information and examples. + /// + /// The value indicating whether the database function is built-in. + /// The same builder instance so that multiple configuration calls can be chained. + public new virtual TableValuedFunctionBuilder IsBuiltIn(bool builtIn = true) + => (TableValuedFunctionBuilder)base.IsBuiltIn(builtIn); + + EntityTypeBuilder IInfrastructure>.Instance => EntityTypeBuilder; +} diff --git a/src/EFCore.Relational/Metadata/Builders/ViewBuilder.cs b/src/EFCore.Relational/Metadata/Builders/ViewBuilder.cs new file mode 100644 index 00000000000..fa1c6c6bad2 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/ViewBuilder.cs @@ -0,0 +1,100 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +public class ViewBuilder : IInfrastructure +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ViewBuilder(in StoreObjectIdentifier storeObject, EntityTypeBuilder entityTypeBuilder) + { + Check.DebugAssert(storeObject.StoreObjectType == StoreObjectType.View, + "StoreObjectType should be View, not " + storeObject.StoreObjectType); + + StoreObject = storeObject; + EntityTypeBuilder = entityTypeBuilder; + } + + /// + /// The specified view name. + /// + public virtual string Name => StoreObject.Name; + + /// + /// The specified view schema. + /// + public virtual string? Schema => StoreObject.Schema; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual StoreObjectIdentifier StoreObject { get; } + + private EntityTypeBuilder EntityTypeBuilder { get; } + + /// + /// Maps the property to a column on the current view and returns an object that can be used + /// to provide view-specific configuration if the property is mapped to more than one view. + /// + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ViewColumnBuilder Property(string propertyName) + => new(StoreObject, EntityTypeBuilder.Property(propertyName)); + + /// + /// Maps the property to a column on the current view and returns an object that can be used + /// to provide view-specific configuration if the property is mapped to more than one view. + /// + /// The type of the property to be configured. + /// The name of the property to be configured. + /// An object that can be used to configure the property. + public virtual ViewColumnBuilder Property(string propertyName) + => new(StoreObject, EntityTypeBuilder.Property(propertyName)); + + EntityTypeBuilder IInfrastructure.Instance => EntityTypeBuilder; + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore.Relational/Metadata/Builders/ViewBuilder`.cs b/src/EFCore.Relational/Metadata/Builders/ViewBuilder`.cs new file mode 100644 index 00000000000..3819da09337 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/ViewBuilder`.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +/// The entity type being configured. +public class ViewBuilder : ViewBuilder, IInfrastructure> + where TEntity : class +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ViewBuilder(in StoreObjectIdentifier storeObject, EntityTypeBuilder entityTypeBuilder) + : base(storeObject, entityTypeBuilder) + { + } + + private EntityTypeBuilder EntityTypeBuilder + => (EntityTypeBuilder)((IInfrastructure)this).GetInfrastructure(); + + /// + /// Maps the property to a column on the current view and returns an object that can be used + /// to provide view-specific configuration if the property is mapped to more than one view. + /// + /// + /// A lambda expression representing the property to be configured (blog => blog.Url). + /// + /// An object that can be used to configure the property. + public virtual ViewColumnBuilder Property(Expression> propertyExpression) + => new(StoreObject, EntityTypeBuilder.Property(propertyExpression)); + + EntityTypeBuilder IInfrastructure>.Instance => EntityTypeBuilder; +} diff --git a/src/EFCore.Relational/Metadata/Builders/ViewColumnBuilder.cs b/src/EFCore.Relational/Metadata/Builders/ViewColumnBuilder.cs new file mode 100644 index 00000000000..dcf250de03e --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/ViewColumnBuilder.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +public class ViewColumnBuilder : IInfrastructure +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ViewColumnBuilder(in StoreObjectIdentifier storeObject, PropertyBuilder propertyBuilder) + { + Check.DebugAssert(storeObject.StoreObjectType == StoreObjectType.View, + "StoreObjectType should be View, not " + storeObject.StoreObjectType); + + InternalOverrides = RelationalPropertyOverrides.GetOrCreate( + propertyBuilder.Metadata, storeObject, ConfigurationSource.Explicit); + PropertyBuilder = propertyBuilder; + } + + /// + /// The view-specific overrides being configured. + /// + public virtual IMutableRelationalPropertyOverrides Overrides => InternalOverrides; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + protected virtual RelationalPropertyOverrides InternalOverrides { get; } + + private PropertyBuilder PropertyBuilder { get; } + + /// + /// Configures the column that the property maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The name of the column. + /// The same builder instance so that multiple calls can be chained. + public virtual ViewColumnBuilder HasColumnName(string? name) + { + Check.NullButNotEmpty(name, nameof(name)); + + InternalOverrides.SetColumnName(name, ConfigurationSource.Explicit); + + return this; + } + + PropertyBuilder IInfrastructure.Instance => PropertyBuilder; + + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string? ToString() + => base.ToString(); + + /// + /// Determines whether the specified object is equal to the current object. + /// + /// The object to compare with the current object. + /// if the specified object is equal to the current object; otherwise, . + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object? obj) + => base.Equals(obj); + + /// + /// Serves as the default hash function. + /// + /// A hash code for the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() + => base.GetHashCode(); + + #endregion +} diff --git a/src/EFCore.Relational/Metadata/Builders/ViewColumnBuilder`.cs b/src/EFCore.Relational/Metadata/Builders/ViewColumnBuilder`.cs new file mode 100644 index 00000000000..7617aaca701 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/ViewColumnBuilder`.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Builders; + +/// +/// Instances of this class are returned from methods when using the API +/// and it is not designed to be directly constructed in your application code. +/// +public class ViewColumnBuilder : ViewColumnBuilder, IInfrastructure> +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public ViewColumnBuilder(in StoreObjectIdentifier storeObject, PropertyBuilder propertyBuilder) + : base(storeObject, propertyBuilder) + { + } + + private PropertyBuilder PropertyBuilder + => (PropertyBuilder)((IInfrastructure)this).Instance; + + /// + /// Configures the column that the property maps to when targeting a relational database. + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The name of the column. + /// The same builder instance so that multiple calls can be chained. + public new virtual ViewColumnBuilder HasColumnName(string? name) + => (ViewColumnBuilder)base.HasColumnName(name); + + PropertyBuilder IInfrastructure>.Instance => PropertyBuilder; +} diff --git a/src/EFCore.Relational/Metadata/Conventions/EntityTypeHierarchyMappingConvention.cs b/src/EFCore.Relational/Metadata/Conventions/EntityTypeHierarchyMappingConvention.cs index 85530ab9ce7..0e5d5ef3eb4 100644 --- a/src/EFCore.Relational/Metadata/Conventions/EntityTypeHierarchyMappingConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/EntityTypeHierarchyMappingConvention.cs @@ -49,7 +49,12 @@ public virtual void ProcessModelFinalizing( continue; } - var mappingStrategy = entityType.GetMappingStrategy(); + var mappingStrategy = (string?)entityType[RelationalAnnotationNames.MappingStrategy]; + if (mappingStrategy == null) + { + mappingStrategy = (string?)entityType.GetRootType()[RelationalAnnotationNames.MappingStrategy]; + } + if (mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy) { nonTphRoots.Add(entityType.GetRootType()); diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs index 3c8dd8dd923..2f59b82bf21 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs @@ -138,6 +138,23 @@ protected override void ProcessEntityTypeAnnotations( annotations[RelationalAnnotationNames.SqlQuery] = entityType.GetSqlQuery(); annotations[RelationalAnnotationNames.FunctionName] = entityType.GetFunctionName(); + if (annotations.TryGetValue(RelationalAnnotationNames.MappingFragments, out var mappingFragments)) + { + var entityTypeMappingFragment = (IReadOnlyStoreObjectDictionary)mappingFragments!; + var runtimeEntityTypeMappingFragment = new StoreObjectDictionary(); + foreach (var fragment in entityTypeMappingFragment.GetValues()) + { + var runtimeMappingFragment = Create(fragment, runtimeEntityType); + runtimeEntityTypeMappingFragment.Add(fragment.StoreObject, runtimeMappingFragment); + + CreateAnnotations(fragment, runtimeMappingFragment, + static (convention, annotations, source, target, runtime) => + convention.ProcessEntityTypeMappingFragmentAnnotations(annotations, source, target, runtime)); + } + + annotations[RelationalAnnotationNames.MappingFragments] = runtimeEntityTypeMappingFragment; + } + if (annotations.TryGetValue(RelationalAnnotationNames.Triggers, out var triggers)) { var runtimeTriggers = new SortedDictionary(StringComparer.Ordinal); @@ -157,6 +174,29 @@ protected override void ProcessEntityTypeAnnotations( } } + private static RuntimeEntityTypeMappingFragment Create( + IEntityTypeMappingFragment entityTypeMappingFragment, + RuntimeEntityType runtimeEntityType) + => new( + runtimeEntityType, + entityTypeMappingFragment.StoreObject, + entityTypeMappingFragment.IsTableExcludedFromMigrations); + + /// + /// Updates the relational property overrides annotations that will be set on the read-only object. + /// + /// The annotations to be processed. + /// The source relational property overrides. + /// The target relational property overrides that will contain the annotations. + /// Indicates whether the given annotations are runtime annotations. + protected virtual void ProcessEntityTypeMappingFragmentAnnotations( + Dictionary annotations, + IEntityTypeMappingFragment entityTypeMappingFragment, + RuntimeEntityTypeMappingFragment runtimeEntityTypeMappingFragment, + bool runtime) + { + } + private void CreateAnnotations( TSource source, TTarget target, @@ -283,21 +323,21 @@ protected override void ProcessPropertyAnnotations( annotations.Remove(RelationalAnnotationNames.Comment); annotations.Remove(RelationalAnnotationNames.Collation); - if (annotations.TryGetValue(RelationalAnnotationNames.RelationalOverrides, out var overrides)) + if (annotations.TryGetValue(RelationalAnnotationNames.RelationalOverrides, out var relationalOverrides)) { - var runtimePropertyOverrides = new SortedDictionary(); - foreach (var (storeObjectIdentifier, value) in (SortedDictionary?)overrides!) + var tableOverrides = (IReadOnlyStoreObjectDictionary)relationalOverrides!; + var runtimeTableOverrides = new StoreObjectDictionary(); + foreach (var overrides in tableOverrides.GetValues()) { - var runtimeOverrides = Create((IRelationalPropertyOverrides)value, runtimeProperty); - runtimePropertyOverrides[storeObjectIdentifier] = runtimeOverrides; + var runtimeOverrides = Create(overrides, runtimeProperty); + runtimeTableOverrides.Add(overrides.StoreObject, runtimeOverrides); - CreateAnnotations( - (IRelationalPropertyOverrides)value, runtimeOverrides, + CreateAnnotations(overrides, runtimeOverrides, static (convention, annotations, source, target, runtime) => convention.ProcessPropertyOverridesAnnotations(annotations, source, target, runtime)); } - annotations[RelationalAnnotationNames.RelationalOverrides] = runtimePropertyOverrides; + annotations[RelationalAnnotationNames.RelationalOverrides] = runtimeTableOverrides; } } } @@ -307,6 +347,7 @@ private static RuntimeRelationalPropertyOverrides Create( RuntimeProperty runtimeProperty) => new( runtimeProperty, + propertyOverrides.StoreObject, propertyOverrides.ColumnNameOverridden, propertyOverrides.ColumnName); diff --git a/src/EFCore.Relational/Metadata/Conventions/StoreGenerationConvention.cs b/src/EFCore.Relational/Metadata/Conventions/StoreGenerationConvention.cs index d3a29d8945a..ce19b66e115 100644 --- a/src/EFCore.Relational/Metadata/Conventions/StoreGenerationConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/StoreGenerationConvention.cs @@ -103,10 +103,15 @@ public virtual void ProcessModelFinalizing( continue; } - var schema = entityType.GetSchema(); foreach (var declaredProperty in entityType.GetDeclaredProperties()) { - Validate(declaredProperty, StoreObjectIdentifier.Table(tableName, schema)); + var declaringTable = declaredProperty.GetMappedStoreObjects(StoreObjectType.Table).FirstOrDefault(); + if (declaringTable.Name == null) + { + continue; + } + + Validate(declaredProperty, declaringTable); } } } diff --git a/src/EFCore.Relational/Metadata/IConventionEntityTypeMappingFragment.cs b/src/EFCore.Relational/Metadata/IConventionEntityTypeMappingFragment.cs new file mode 100644 index 00000000000..fb7553795f4 --- /dev/null +++ b/src/EFCore.Relational/Metadata/IConventionEntityTypeMappingFragment.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents entity type mapping for a particular table-like store object. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IConventionEntityTypeMappingFragment : IReadOnlyEntityTypeMappingFragment, IConventionAnnotatable +{ + /// + /// Gets the entity type for which the fragment is defined. + /// + new IConventionEntityType EntityType { get; } + + /// + /// Returns the configuration source for this fragment. + /// + /// The configuration source. + ConfigurationSource GetConfigurationSource(); + + /// + /// Sets a value indicating whether the associated table is ignored by Migrations. + /// + /// A value indicating whether the associated table is ignored by Migrations. + /// Indicates whether the configuration was specified using a data annotation. + bool? SetIsTableExcludedFromMigrations(bool? excluded, bool fromDataAnnotation = false); + + /// + /// Gets the for . + /// + /// The for . + ConfigurationSource? GetIsTableExcludedFromMigrationsConfigurationSource(); +} diff --git a/src/EFCore.Relational/Metadata/IConventionRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/IConventionRelationalPropertyOverrides.cs new file mode 100644 index 00000000000..4122aa05172 --- /dev/null +++ b/src/EFCore.Relational/Metadata/IConventionRelationalPropertyOverrides.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents property facet overrides for a particular table-like store object. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IConventionRelationalPropertyOverrides : IReadOnlyRelationalPropertyOverrides, IConventionAnnotatable +{ + /// + /// Gets the property that the overrides are for. + /// + new IConventionProperty Property { get; } + + /// + /// Returns the configuration source for these overrides. + /// + /// The configuration source. + ConfigurationSource GetConfigurationSource(); + + /// + /// Sets the column that the property maps to when targeting the specified table-like store object. + /// + /// The column name. + /// Indicates whether the configuration was specified using a data annotation. + /// The configured value. + string? SetColumnName(string? name, bool fromDataAnnotation = false); + + /// + /// Removes the column name override. + /// + /// Indicates whether the configuration was specified using a data annotation. + /// A value indicating whether the column name override was removed. + bool RemoveColumnNameOverride(bool fromDataAnnotation = false); + + /// + /// Returns the configuration source for . + /// + /// The configuration source for . + ConfigurationSource? GetColumnNameConfigurationSource(); +} diff --git a/src/EFCore.Relational/Metadata/IEntityTypeMappingFragment.cs b/src/EFCore.Relational/Metadata/IEntityTypeMappingFragment.cs new file mode 100644 index 00000000000..37f22bdcbe3 --- /dev/null +++ b/src/EFCore.Relational/Metadata/IEntityTypeMappingFragment.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents entity type mapping for a particular table-like store object. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IEntityTypeMappingFragment : IReadOnlyEntityTypeMappingFragment, IAnnotatable +{ + /// + /// Gets the entity type for which the fragment is defined. + /// + new IEntityType EntityType { get; } +} diff --git a/src/EFCore.Relational/Metadata/IMutableEntityTypeMappingFragment.cs b/src/EFCore.Relational/Metadata/IMutableEntityTypeMappingFragment.cs new file mode 100644 index 00000000000..9a88f1a8365 --- /dev/null +++ b/src/EFCore.Relational/Metadata/IMutableEntityTypeMappingFragment.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents entity type mapping for a particular table-like store object. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IMutableEntityTypeMappingFragment : IReadOnlyEntityTypeMappingFragment, IMutableAnnotatable +{ + /// + /// Gets the entity type for which the fragment is defined. + /// + new IMutableEntityType EntityType { get; } + + /// + /// Gets or sets a value indicating whether the associated table is ignored by Migrations. + /// + /// A value indicating whether the associated table is ignored by Migrations. + new bool? IsTableExcludedFromMigrations { get; set; } +} diff --git a/src/EFCore.Relational/Metadata/IMutableRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/IMutableRelationalPropertyOverrides.cs new file mode 100644 index 00000000000..45063bdce75 --- /dev/null +++ b/src/EFCore.Relational/Metadata/IMutableRelationalPropertyOverrides.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents property facet overrides for a particular table-like store object. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IMutableRelationalPropertyOverrides : IReadOnlyRelationalPropertyOverrides, IMutableAnnotatable +{ + /// + /// Gets the property that the overrides are for. + /// + new IMutableProperty Property { get; } + + /// + /// Gets or sets the column that the property maps to when targeting the specified table-like store object. + /// + new string? ColumnName { get; set; } + + /// + /// Removes the column name override. + /// + void RemoveColumnNameOverride(); +} diff --git a/src/EFCore.Relational/Metadata/IReadOnlyEntityTypeMappingFragment.cs b/src/EFCore.Relational/Metadata/IReadOnlyEntityTypeMappingFragment.cs new file mode 100644 index 00000000000..ae79f90cfac --- /dev/null +++ b/src/EFCore.Relational/Metadata/IReadOnlyEntityTypeMappingFragment.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents entity type mapping for a particular table-like store object. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IReadOnlyEntityTypeMappingFragment : IReadOnlyAnnotatable +{ + /// + /// Gets the entity type for which the fragment is defined. + /// + IReadOnlyEntityType EntityType { get; } + + /// + /// Gets store object for which the configuration is applied. + /// + StoreObjectIdentifier StoreObject { get; } + + /// + /// Gets a value indicating whether the associated table is ignored by Migrations. + /// + /// A value indicating whether the associated table is ignored by Migrations. + bool? IsTableExcludedFromMigrations { get; } +} diff --git a/src/EFCore.Relational/Metadata/IReadOnlyRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/IReadOnlyRelationalPropertyOverrides.cs new file mode 100644 index 00000000000..76f806bcf44 --- /dev/null +++ b/src/EFCore.Relational/Metadata/IReadOnlyRelationalPropertyOverrides.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents property facet overrides for a particular table-like store object. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IReadOnlyRelationalPropertyOverrides : IReadOnlyAnnotatable +{ + /// + /// Gets the property that the overrides are for. + /// + IReadOnlyProperty Property { get; } + + /// + /// The id of the table-like store object that these overrides are for. + /// + StoreObjectIdentifier StoreObject { get; } + + /// + /// Gets the column that the property maps to when targeting the specified table-like store object. + /// + string? ColumnName { get; } + + /// + /// Gets a value indicating whether the column name is overriden. + /// + bool ColumnNameOverridden { get; } +} diff --git a/src/EFCore.Relational/Metadata/IReadOnlyStoreObjectDictionary.cs b/src/EFCore.Relational/Metadata/IReadOnlyStoreObjectDictionary.cs new file mode 100644 index 00000000000..fba2b1c4ef6 --- /dev/null +++ b/src/EFCore.Relational/Metadata/IReadOnlyStoreObjectDictionary.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata +{ + /// + /// Represents a lookup based on keys. + /// + /// The values type. + public interface IReadOnlyStoreObjectDictionary + where T : class + { + /// + /// Gets the value associated with the specified key. + /// + /// The key of the value to get. + /// + /// if the collection contains an element with the specified key; + /// otherwise, . + /// + T? Find(in StoreObjectIdentifier storeObject); + + /// + /// Gets a collection containing the values from this collection. + /// + /// A collection containing the values from this collection. + IEnumerable GetValues(); + } +} diff --git a/src/EFCore.Relational/Metadata/IRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/IRelationalPropertyOverrides.cs new file mode 100644 index 00000000000..65bc22ccc93 --- /dev/null +++ b/src/EFCore.Relational/Metadata/IRelationalPropertyOverrides.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents property facet overrides for a particular table-like store object. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public interface IRelationalPropertyOverrides : IReadOnlyRelationalPropertyOverrides, IAnnotatable +{ + /// + /// Gets the property that the overrides are for. + /// + new IProperty Property { get; } +} diff --git a/src/EFCore.Relational/Metadata/Internal/EntityTypeMappingFragment.cs b/src/EFCore.Relational/Metadata/Internal/EntityTypeMappingFragment.cs new file mode 100644 index 00000000000..fbe0f4e2141 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/EntityTypeMappingFragment.cs @@ -0,0 +1,233 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata.Internal; + +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class EntityTypeMappingFragment : + ConventionAnnotatable, + IEntityTypeMappingFragment, + IMutableEntityTypeMappingFragment, + IConventionEntityTypeMappingFragment +{ + private bool? _isTableExcludedFromMigrations; + + private ConfigurationSource _configurationSource; + private ConfigurationSource? _isTableExcludedFromMigrationsConfigurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public EntityTypeMappingFragment( + IReadOnlyEntityType entityType, + in StoreObjectIdentifier storeObject, + ConfigurationSource configurationSource) + { + EntityType = entityType; + StoreObject = storeObject; + _configurationSource = configurationSource; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual IReadOnlyEntityType EntityType { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual StoreObjectIdentifier StoreObject { get; } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override bool IsReadOnly + => ((Annotatable)EntityType).IsReadOnly; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource GetConfigurationSource() + => _configurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void UpdateConfigurationSource(ConfigurationSource configurationSource) + => _configurationSource = configurationSource.Max(_configurationSource); + + /// + public virtual bool? IsTableExcludedFromMigrations + { + get => _isTableExcludedFromMigrations; + set => SetIsTableExcludedFromMigrations(value, ConfigurationSource.Explicit); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool? SetIsTableExcludedFromMigrations(bool? excluded, ConfigurationSource configurationSource) + { + if (!configurationSource.Overrides(_isTableExcludedFromMigrationsConfigurationSource)) + { + return null; + } + + _isTableExcludedFromMigrations = excluded; + _isTableExcludedFromMigrationsConfigurationSource = + excluded == null + ? null + : configurationSource.Max(_isTableExcludedFromMigrationsConfigurationSource); + return excluded; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource? GetIsTableExcludedFromMigrationsConfigurationSource() + => _isTableExcludedFromMigrationsConfigurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static IReadOnlyEntityTypeMappingFragment? Find( + IReadOnlyEntityType entityType, + in StoreObjectIdentifier storeObject) + => ((IReadOnlyStoreObjectDictionary?)entityType[RelationalAnnotationNames.MappingFragments]) + ?.Find(storeObject); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static IEnumerable? Get(IReadOnlyEntityType entityType) + => ((IReadOnlyStoreObjectDictionary?)entityType[RelationalAnnotationNames.MappingFragments]) + ?.GetValues(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static EntityTypeMappingFragment GetOrCreate( + IMutableEntityType entityType, + in StoreObjectIdentifier storeObject, + ConfigurationSource configurationSource) + { + var fragments = (StoreObjectDictionary?) + entityType[RelationalAnnotationNames.MappingFragments]; + if (fragments == null) + { + fragments = new(); + entityType[RelationalAnnotationNames.MappingFragments] = fragments; + } + + var fragment = fragments.Find(storeObject); + if (fragment == null) + { + fragment = new (entityType, storeObject, configurationSource); + fragments.Add(storeObject, fragment); + } + else + { + fragment.UpdateConfigurationSource(configurationSource); + } + + return fragment; + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static EntityTypeMappingFragment? Remove( + IMutableEntityType entityType, + in StoreObjectIdentifier storeObject, + ConfigurationSource configurationSource) + { + var fragments = (StoreObjectDictionary?) + entityType[RelationalAnnotationNames.MappingFragments]; + if (fragments == null) + { + return null; + } + + var fragment = fragments.Find(storeObject); + if (fragment == null) + { + return null; + } + + if (configurationSource.Overrides(fragment.GetConfigurationSource())) + { + fragments.Remove(storeObject); + + return fragment; + } + + return null; + } + + /// + IEntityType IEntityTypeMappingFragment.EntityType + { + [DebuggerStepThrough] + get => (IEntityType)EntityType; + } + + /// + IMutableEntityType IMutableEntityTypeMappingFragment.EntityType + { + [DebuggerStepThrough] + get => (IMutableEntityType)EntityType; + } + + /// + IConventionEntityType IConventionEntityTypeMappingFragment.EntityType + { + [DebuggerStepThrough] + get => (IConventionEntityType)EntityType; + } + + bool? IConventionEntityTypeMappingFragment.SetIsTableExcludedFromMigrations(bool? excluded, bool fromDataAnnotation) + => SetIsTableExcludedFromMigrations(excluded, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + bool? IReadOnlyEntityTypeMappingFragment.IsTableExcludedFromMigrations => IsTableExcludedFromMigrations; +} diff --git a/src/EFCore.Relational/Metadata/Internal/IRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/Internal/IRelationalPropertyOverrides.cs deleted file mode 100644 index 7d11d6db339..00000000000 --- a/src/EFCore.Relational/Metadata/Internal/IRelationalPropertyOverrides.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Metadata.Internal; - -/// -/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to -/// the same compatibility standards as public APIs. It may be changed or removed without notice in -/// any release. You should only use it directly in your code with extreme caution and knowing that -/// doing so can result in application failures when updating to a new Entity Framework Core release. -/// -public interface IRelationalPropertyOverrides : IAnnotatable -{ - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - IProperty Property { get; } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - string? ColumnName { get; } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - bool ColumnNameOverridden { get; } -} diff --git a/src/EFCore.Relational/Metadata/Internal/InternalTriggerBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalTriggerBuilder.cs index 247cffa8b28..2a1850f0602 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalTriggerBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalTriggerBuilder.cs @@ -78,7 +78,6 @@ public virtual bool CanSetName(string? name, ConfigurationSource configurationSo } entityType.RemoveTrigger(name); - trigger = null; } else { diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index 7b266507996..52fdf3dc224 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -283,7 +283,7 @@ private static void AddDefaultMappings(RelationalModel databaseModel, IEntityTyp foreach (var property in entityType.GetProperties()) { var columnName = property.IsPrimaryKey() || isTpc || isTph || property.DeclaringEntityType == mappedType - ? property.GetColumnBaseName() + ? property.GetColumnName() : null; if (columnName == null) { diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs index b6d60e29198..8058c2ab173 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs @@ -9,10 +9,15 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class RelationalPropertyOverrides : ConventionAnnotatable, IRelationalPropertyOverrides +public class RelationalPropertyOverrides : + ConventionAnnotatable, + IMutableRelationalPropertyOverrides, + IConventionRelationalPropertyOverrides, + IRelationalPropertyOverrides { private string? _columnName; + private ConfigurationSource _configurationSource; private ConfigurationSource? _columnNameConfigurationSource; /// @@ -21,9 +26,14 @@ public class RelationalPropertyOverrides : ConventionAnnotatable, IRelationalPro /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public RelationalPropertyOverrides(IReadOnlyProperty property) + public RelationalPropertyOverrides( + IReadOnlyProperty property, + in StoreObjectIdentifier storeObject, + ConfigurationSource configurationSource) { Property = property; + StoreObject = storeObject; + _configurationSource = configurationSource; } /// @@ -34,6 +44,14 @@ public RelationalPropertyOverrides(IReadOnlyProperty property) /// public virtual IReadOnlyProperty Property { get; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual StoreObjectIdentifier StoreObject { get; } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -43,6 +61,24 @@ public RelationalPropertyOverrides(IReadOnlyProperty property) public override bool IsReadOnly => ((Annotatable)Property).IsReadOnly; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual ConfigurationSource GetConfigurationSource() + => _configurationSource; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void UpdateConfigurationSource(ConfigurationSource configurationSource) + => _configurationSource = configurationSource.Max(_configurationSource); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -80,6 +116,26 @@ public virtual string? ColumnName public virtual bool ColumnNameOverridden => _columnNameConfigurationSource != null; + private bool RemoveColumnNameOverride(ConfigurationSource configurationSource) + { + if (!ColumnNameOverridden) + { + return true; + } + + if (!_columnNameConfigurationSource.Overrides(configurationSource)) + { + return false; + } + + EnsureMutable(); + + _columnName = null; + _columnNameConfigurationSource = null; + + return true; + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -95,15 +151,19 @@ public virtual bool ColumnNameOverridden /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public static IRelationalPropertyOverrides? Find(IReadOnlyProperty property, in StoreObjectIdentifier storeObject) - { - var tableOverrides = (SortedDictionary?) - property[RelationalAnnotationNames.RelationalOverrides]; - return tableOverrides != null - && tableOverrides.TryGetValue(storeObject, out var overrides) - ? (IRelationalPropertyOverrides)overrides - : null; - } + public static IReadOnlyRelationalPropertyOverrides? Find(IReadOnlyProperty property, in StoreObjectIdentifier storeObject) + => ((IReadOnlyStoreObjectDictionary?)property[RelationalAnnotationNames.RelationalOverrides]) + ?.Find(storeObject); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static IEnumerable? Get(IReadOnlyProperty property) + => ((IReadOnlyStoreObjectDictionary?)property[RelationalAnnotationNames.RelationalOverrides]) + ?.GetValues(); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -113,23 +173,29 @@ public virtual bool ColumnNameOverridden /// public static RelationalPropertyOverrides GetOrCreate( IMutableProperty property, - in StoreObjectIdentifier storeObject) + in StoreObjectIdentifier storeObject, + ConfigurationSource configurationSource) { - var tableOverrides = (SortedDictionary?) + var tableOverrides = (StoreObjectDictionary?) property[RelationalAnnotationNames.RelationalOverrides]; if (tableOverrides == null) { - tableOverrides = new SortedDictionary(); + tableOverrides = new (); property[RelationalAnnotationNames.RelationalOverrides] = tableOverrides; } - if (!tableOverrides.TryGetValue(storeObject, out var overrides)) + var overrides = tableOverrides.Find(storeObject); + if (overrides == null) { - overrides = new RelationalPropertyOverrides(property); + overrides = new (property, storeObject, configurationSource); tableOverrides.Add(storeObject, overrides); } + else + { + overrides.UpdateConfigurationSource(configurationSource); + } - return (RelationalPropertyOverrides)overrides; + return overrides; } /// @@ -138,10 +204,33 @@ public static RelationalPropertyOverrides GetOrCreate( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public static RelationalPropertyOverrides GetOrCreate( - IConventionProperty property, - in StoreObjectIdentifier storeObject) - => GetOrCreate((IMutableProperty)property, storeObject); + public static RelationalPropertyOverrides? Remove( + IMutableProperty property, + in StoreObjectIdentifier storeObject, + ConfigurationSource configurationSource) + { + var tableOverrides = (StoreObjectDictionary?) + property[RelationalAnnotationNames.RelationalOverrides]; + if (tableOverrides == null) + { + return null; + } + + var overrides = tableOverrides.Find(storeObject); + if (overrides == null) + { + return null; + } + + if (configurationSource.Overrides(overrides.GetConfigurationSource())) + { + tableOverrides.Remove(storeObject); + + return overrides; + } + + return null; + } /// IProperty IRelationalPropertyOverrides.Property @@ -149,4 +238,27 @@ IProperty IRelationalPropertyOverrides.Property [DebuggerStepThrough] get => (IProperty)Property; } + + /// + IMutableProperty IMutableRelationalPropertyOverrides.Property + { + [DebuggerStepThrough] + get => (IMutableProperty)Property; + } + + /// + IConventionProperty IConventionRelationalPropertyOverrides.Property + { + [DebuggerStepThrough] + get => (IConventionProperty)Property; + } + + string? IConventionRelationalPropertyOverrides.SetColumnName(string? name, bool fromDataAnnotation) + => SetColumnName(name, fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); + + void IMutableRelationalPropertyOverrides.RemoveColumnNameOverride() + => RemoveColumnNameOverride(ConfigurationSource.Explicit); + + bool IConventionRelationalPropertyOverrides.RemoveColumnNameOverride(bool fromDataAnnotation) + => RemoveColumnNameOverride(fromDataAnnotation ? ConfigurationSource.DataAnnotation : ConfigurationSource.Convention); } diff --git a/src/EFCore.Relational/Metadata/Internal/Table.cs b/src/EFCore.Relational/Metadata/Internal/Table.cs index 7fc96c7b31c..cbca1366762 100644 --- a/src/EFCore.Relational/Metadata/Internal/Table.cs +++ b/src/EFCore.Relational/Metadata/Internal/Table.cs @@ -142,7 +142,7 @@ public virtual UniqueConstraint? PrimaryKey /// public virtual bool IsExcludedFromMigrations - => EntityTypeMappings.First().EntityType.IsTableExcludedFromMigrations(); + => EntityTypeMappings.First().EntityType.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table(Name, Schema)); /// public override IColumnBase? FindColumn(IProperty property) diff --git a/src/EFCore.Relational/Metadata/Internal/TableBase.cs b/src/EFCore.Relational/Metadata/Internal/TableBase.cs index 6c735468904..a1671ab8c1c 100644 --- a/src/EFCore.Relational/Metadata/Internal/TableBase.cs +++ b/src/EFCore.Relational/Metadata/Internal/TableBase.cs @@ -123,7 +123,8 @@ public virtual bool IsOptional(IEntityType entityType) } return !OptionalEntityTypes.TryGetValue(entityType, out var optional) - ? throw new InvalidOperationException(RelationalStrings.TableNotMappedEntityType(entityType.DisplayName(), Name)) + ? throw new InvalidOperationException( + RelationalStrings.TableNotMappedEntityType(entityType.DisplayName(), ((ITableBase)this).SchemaQualifiedName)) : optional; } @@ -131,7 +132,8 @@ private void CheckMappedEntityType(IEntityType entityType) { if (EntityTypeMappings.All(m => m.EntityType != entityType)) { - throw new InvalidOperationException(RelationalStrings.TableNotMappedEntityType(entityType.DisplayName(), Name)); + throw new InvalidOperationException( + RelationalStrings.TableNotMappedEntityType(entityType.DisplayName(), ((ITableBase)this).SchemaQualifiedName)); } } diff --git a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs index 2bcc0a903c9..ef098b94f76 100644 --- a/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs +++ b/src/EFCore.Relational/Metadata/RelationalAnnotationNames.cs @@ -244,6 +244,11 @@ public static class RelationalAnnotationNames /// public const string UniqueConstraintMappings = Prefix + "UniqueConstraintMappings"; + /// + /// The name for the annotation that contains entity type mapping fragments. + /// + public const string MappingFragments = Prefix + "MappingFragments"; + /// /// The name for the annotation that contains table-specific facet overrides. /// diff --git a/src/EFCore.Relational/Metadata/RuntimeEntityTypeMappingFragment.cs b/src/EFCore.Relational/Metadata/RuntimeEntityTypeMappingFragment.cs new file mode 100644 index 00000000000..ccd5d231505 --- /dev/null +++ b/src/EFCore.Relational/Metadata/RuntimeEntityTypeMappingFragment.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents entity type mapping for a particular table-like store object. +/// +/// +/// See Modeling entity types and relationships for more information and examples. +/// +public class RuntimeEntityTypeMappingFragment : AnnotatableBase, IEntityTypeMappingFragment +{ + /// + /// Initializes a new instance of the class. + /// + /// The entity type for which the fragment is defined. + /// The store object for which the configuration is applied. + /// + /// A value indicating whether the associated table is ignored by Migrations. + /// + public RuntimeEntityTypeMappingFragment( + RuntimeEntityType entityType, + in StoreObjectIdentifier storeObject, + bool? isTableExcludedFromMigrations) + { + EntityType = entityType; + StoreObject = storeObject; + if (isTableExcludedFromMigrations != null) + { + SetAnnotation(RelationalAnnotationNames.IsTableExcludedFromMigrations, isTableExcludedFromMigrations.Value); + } + } + + /// + /// Gets the entity type for which the fragment is defined. + /// + public virtual RuntimeEntityType EntityType { get; } + + /// + public virtual StoreObjectIdentifier StoreObject { get; } + + /// + public virtual bool? IsTableExcludedFromMigrations => (bool?)this[RelationalAnnotationNames.IsTableExcludedFromMigrations]; + + /// + IEntityType IEntityTypeMappingFragment.EntityType + { + [DebuggerStepThrough] + get => EntityType; + } + + /// + IReadOnlyEntityType IReadOnlyEntityTypeMappingFragment.EntityType + { + [DebuggerStepThrough] + get => EntityType; + } +} diff --git a/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs index 835f880b28c..224554159eb 100644 --- a/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs +++ b/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.EntityFrameworkCore.Metadata.Internal; - namespace Microsoft.EntityFrameworkCore.Metadata; /// @@ -17,14 +15,17 @@ public class RuntimeRelationalPropertyOverrides : AnnotatableBase, IRelationalPr /// Initializes a new instance of the class. /// /// The property for which the overrides are applied. + /// The store object for which the configuration is applied. /// Whether the column name is overridden. /// The column name. public RuntimeRelationalPropertyOverrides( RuntimeProperty property, + in StoreObjectIdentifier storeObject, bool columnNameOverridden, string? columnName) { Property = property; + StoreObject = storeObject; if (columnNameOverridden) { SetAnnotation(RelationalAnnotationNames.ColumnName, columnName); @@ -36,6 +37,9 @@ public RuntimeRelationalPropertyOverrides( /// public virtual RuntimeProperty Property { get; } + /// + public virtual StoreObjectIdentifier StoreObject { get; } + /// IProperty IRelationalPropertyOverrides.Property { @@ -44,14 +48,21 @@ IProperty IRelationalPropertyOverrides.Property } /// - string? IRelationalPropertyOverrides.ColumnName + IReadOnlyProperty IReadOnlyRelationalPropertyOverrides.Property + { + [DebuggerStepThrough] + get => Property; + } + + /// + string? IReadOnlyRelationalPropertyOverrides.ColumnName { [DebuggerStepThrough] get => (string?)this[RelationalAnnotationNames.ColumnName]; } /// - bool IRelationalPropertyOverrides.ColumnNameOverridden + bool IReadOnlyRelationalPropertyOverrides.ColumnNameOverridden { [DebuggerStepThrough] get => FindAnnotation(RelationalAnnotationNames.ColumnName) != null; diff --git a/src/EFCore.Relational/Metadata/StoreObjectDictionary.cs b/src/EFCore.Relational/Metadata/StoreObjectDictionary.cs new file mode 100644 index 00000000000..8824d80216e --- /dev/null +++ b/src/EFCore.Relational/Metadata/StoreObjectDictionary.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.Metadata; + +/// +/// Represents a lookup based on keys. +/// +/// The values type. +public class StoreObjectDictionary : IReadOnlyStoreObjectDictionary where T : class +{ + private readonly Dictionary _dictionary = new(); + + /// + public virtual T? Find(in StoreObjectIdentifier storeObject) + => _dictionary.TryGetValue(storeObject, out var value) + ? value + : null; + + /// + public virtual IEnumerable GetValues() + => _dictionary.OrderBy(pair => pair.Key.Name, StringComparer.Ordinal).Select(pair => pair.Value); + + /// + /// Adds the specified key and value to the dictionary. + /// + /// The store object. + /// The value to store. + public virtual void Add(in StoreObjectIdentifier storeObject, T value) + => _dictionary.Add(storeObject, value); + + /// + /// Removes the value with the specified key from the collection + /// and returns it if successful. + /// + /// The key of the element to remove. + /// The removed value. + public virtual T? Remove(in StoreObjectIdentifier storeObject) + => _dictionary.Remove(storeObject, out var value) + ? value + : null; +} diff --git a/src/EFCore.Relational/Migrations/HistoryRepository.cs b/src/EFCore.Relational/Migrations/HistoryRepository.cs index 50a7aca7c01..2f223db976c 100644 --- a/src/EFCore.Relational/Migrations/HistoryRepository.cs +++ b/src/EFCore.Relational/Migrations/HistoryRepository.cs @@ -77,7 +77,7 @@ protected virtual string MigrationIdColumnName => _migrationIdColumnName ??= EnsureModel() .FindEntityType(typeof(HistoryRow))! .FindProperty(nameof(HistoryRow.MigrationId))! - .GetColumnBaseName(); + .GetColumnName(); private IModel EnsureModel() { @@ -111,7 +111,7 @@ protected virtual string ProductVersionColumnName => _productVersionColumnName ??= EnsureModel() .FindEntityType(typeof(HistoryRow))! .FindProperty(nameof(HistoryRow.ProductVersion))! - .GetColumnBaseName(); + .GetColumnName(); /// /// Overridden by database providers to generate SQL that tests for existence of the history table. diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index d140c6c8953..f6c7df8f626 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -605,6 +605,62 @@ public static string EitherOfTwoValuesMustBeNull(object? param1, object? param2) GetString("EitherOfTwoValuesMustBeNull", nameof(param1), nameof(param2)), param1, param2); + /// + /// The short name for '{entityType1}' is '{discriminatorValue}' which is the same for '{entityType2}'. Every concrete entity type in the hierarchy must have a unique short name. Either rename one of the types or call modelBuilder.Entity<TEntity>().Metadata.SetDiscriminatorValue("NewShortName"). + /// + public static string EntityShortNameNotUnique(object? entityType1, object? discriminatorValue, object? entityType2) + => string.Format( + GetString("EntityShortNameNotUnique", nameof(entityType1), nameof(discriminatorValue), nameof(entityType2)), + entityType1, discriminatorValue, entityType2); + + /// + /// Entity type '{entityType}' has a split mapping for '{storeObject}', but is it also mapped to the same object. Split mappings should not duplicate the main mapping. + /// + public static string EntitySplittingConflictingMainFragment(object? entityType, object? storeObject) + => string.Format( + GetString("EntitySplittingConflictingMainFragment", nameof(entityType), nameof(storeObject)), + entityType, storeObject); + + /// + /// Entity type '{entityType}' has a split mapping for '{storeObject}', but it also participates in an entity type hierarchy. Split mappings are not supported for hierarchies. + /// + public static string EntitySplittingHierarchy(object? entityType, object? storeObject) + => string.Format( + GetString("EntitySplittingHierarchy", nameof(entityType), nameof(storeObject)), + entityType, storeObject); + + /// + /// Entity type '{entityType}' has a split mapping for '{storeObject}', but the primary key properties aren't fully mapped. Map all primary key properties to columns on '{storeObject}'. + /// + public static string EntitySplittingMissingPrimaryKey(object? entityType, object? storeObject) + => string.Format( + GetString("EntitySplittingMissingPrimaryKey", nameof(entityType), nameof(storeObject)), + entityType, storeObject); + + /// + /// Entity type '{entityType}' has a split mapping for '{storeObject}', but it doesn't map any non-primary key property to it. Map at least one non-primary key property to a column on '{storeObject}'. + /// + public static string EntitySplittingMissingProperties(object? entityType, object? storeObject) + => string.Format( + GetString("EntitySplittingMissingProperties", nameof(entityType), nameof(storeObject)), + entityType, storeObject); + + /// + /// Entity type '{entityType}' has a split mapping, but it doesn't map any non-primary key property to the main store object. Keep at least one non-primary key property mapped to a column on '{storeObject}'. + /// + public static string EntitySplittingMissingPropertiesMainFragment(object? entityType, object? storeObject) + => string.Format( + GetString("EntitySplittingMissingPropertiesMainFragment", nameof(entityType), nameof(storeObject)), + entityType, storeObject); + + /// + /// Entity type '{entityType}' has a split mapping for '{storeObject}', but it doesn't have a main mapping of the same type. Map '{entityType}' to '{storeObjectType}'. + /// + public static string EntitySplittingUnmappedMainFragment(object? entityType, object? storeObject, object? storeObjectType) + => string.Format( + GetString("EntitySplittingUnmappedMainFragment", nameof(entityType), nameof(storeObject), nameof(storeObjectType)), + entityType, storeObject, storeObjectType); + /// /// An error occurred while reading a database value for property '{entityType}.{property}'. See the inner exception for more information. /// @@ -871,6 +927,12 @@ public static string MappedFunctionNotFound(object? entityType, object? function GetString("MappedFunctionNotFound", nameof(entityType), nameof(functionName)), entityType, functionName); + /// + /// Table name must be specified to configure a mapping fragment. + /// + public static string MappingFragmentMissingName + => GetString("MappingFragmentMissingName"); + /// /// Using '{methodName}' on DbSet of '{entityType}' is not supported since '{entityType}' is part of hierarchy and does not contain a discriminator property. /// @@ -1005,6 +1067,14 @@ public static string NonScalarFunctionParameterCannotPropagatesNullability(objec GetString("NonScalarFunctionParameterCannotPropagatesNullability", nameof(parameterName), nameof(functionName)), parameterName, functionName); + /// + /// The specified discriminator value '{value}' for '{entityType}' is not a string. Configure a string discriminator value instead. + /// + public static string NonTphDiscriminatorValueNotString(object? value, object? entityType) + => string.Format( + GetString("NonTphDiscriminatorValueNotString", nameof(value), nameof(entityType)), + value, entityType); + /// /// The mapping strategy '{mappingStrategy}' specified on '{entityType}' is not supported for entity types with a discriminator. /// @@ -1405,22 +1475,6 @@ public static string ViewOverrideMismatch(object? propertySpecification, object? public static string VisitChildrenMustBeOverridden => GetString("VisitChildrenMustBeOverridden"); - /// - /// The short name for '{entityType1}' is '{discriminatorValue}' which is the same for '{entityType2}'. Every concrete entity type in the hierarchy must have a unique short name. Either rename one of the types or call entityTypeBuilder.Metadata.SetDiscriminatorValue("NewShortName"). - /// - public static string EntityShortNameNotUnique(object? entityType1, object? discriminatorValue, object? entityType2) - => string.Format( - GetString("EntityShortNameNotUnique", nameof(entityType1), nameof(discriminatorValue), nameof(entityType2)), - entityType1, discriminatorValue, entityType2); - - /// - /// The specified discriminator value '{value}' for '{entityType}' is not a string. Configure a string discriminator value instead. - /// - public static string NonTphDiscriminatorValueNotString(object? value, object? entityType) - => string.Format( - GetString("NonTphDiscriminatorValueNotString", nameof(value), nameof(entityType)), - value, entityType); - private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name)!; diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index 40b82f8b9cf..472df3e9f01 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -343,6 +343,24 @@ The short name for '{entityType1}' is '{discriminatorValue}' which is the same for '{entityType2}'. Every concrete entity type in the hierarchy must have a unique short name. Either rename one of the types or call modelBuilder.Entity<TEntity>().Metadata.SetDiscriminatorValue("NewShortName"). + + Entity type '{entityType}' has a split mapping for '{storeObject}', but is it also mapped to the same object. Split mappings should not duplicate the main mapping. + + + Entity type '{entityType}' has a split mapping for '{storeObject}', but it also participates in an entity type hierarchy. Split mappings are not supported for hierarchies. + + + Entity type '{entityType}' has a split mapping for '{storeObject}', but the primary key properties aren't fully mapped. Map all primary key properties to columns on '{storeObject}'. + + + Entity type '{entityType}' has a split mapping for '{storeObject}', but it doesn't map any non-primary key property to it. Map at least one non-primary key property to a column on '{storeObject}'. + + + Entity type '{entityType}' has a split mapping, but it doesn't map any non-primary key property to the main store object. Keep at least one non-primary key property mapped to a column on '{storeObject}'. + + + Entity type '{entityType}' has a split mapping for '{storeObject}', but it doesn't have a main mapping of the same type. Map '{entityType}' to '{storeObjectType}'. + An error occurred while reading a database value for property '{entityType}.{property}'. See the inner exception for more information. @@ -709,6 +727,9 @@ 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. + + Table name must be specified to configure a mapping fragment. + Using '{methodName}' on DbSet of '{entityType}' is not supported since '{entityType}' is part of hierarchy and does not contain a discriminator property. diff --git a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs index 01f28507f40..541a5c8e954 100644 --- a/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs @@ -94,16 +94,7 @@ internal SelectExpression(SqlExpression? projection) internal SelectExpression(IEntityType entityType, ISqlExpressionFactory sqlExpressionFactory) : base(null) { - var mappingStrategy = entityType.GetMappingStrategy(); - if (mappingStrategy == null - && (entityType.BaseType != null || entityType.GetDirectlyDerivedTypes().Any())) - { - // Contains hierarchy so there will be an implicit mapping strategy - mappingStrategy = entityType.FindDiscriminatorProperty() != null - ? RelationalAnnotationNames.TphMappingStrategy : RelationalAnnotationNames.TptMappingStrategy; - } - - switch (mappingStrategy) + switch (entityType.GetMappingStrategy()) { case RelationalAnnotationNames.TptMappingStrategy: { diff --git a/src/EFCore.SqlServer/Extensions/SqlServerTableBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerTableBuilderExtensions.cs index 3afdd1ccf4a..ed44547fb7d 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerTableBuilderExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerTableBuilderExtensions.cs @@ -25,7 +25,7 @@ public static TemporalTableBuilder IsTemporal( { tableBuilder.Metadata.SetIsTemporal(temporal); - return new TemporalTableBuilder(tableBuilder.EntityTypeBuilder); + return new TemporalTableBuilder(tableBuilder.GetInfrastructure()); } /// @@ -44,7 +44,7 @@ public static TableBuilder IsTemporal( { tableBuilder.Metadata.SetIsTemporal(true); - buildAction(new TemporalTableBuilder(tableBuilder.EntityTypeBuilder)); + buildAction(new TemporalTableBuilder(tableBuilder.GetInfrastructure())); return tableBuilder; } @@ -67,7 +67,7 @@ public static TemporalTableBuilder IsTemporal( { tableBuilder.Metadata.SetIsTemporal(temporal); - return new TemporalTableBuilder(tableBuilder.EntityTypeBuilder); + return new TemporalTableBuilder(tableBuilder.GetInfrastructure>()); } /// @@ -87,7 +87,7 @@ public static TableBuilder IsTemporal( where TEntity : class { tableBuilder.Metadata.SetIsTemporal(true); - buildAction(new TemporalTableBuilder(tableBuilder.EntityTypeBuilder)); + buildAction(new TemporalTableBuilder(tableBuilder.GetInfrastructure>())); return tableBuilder; } @@ -108,7 +108,7 @@ public static OwnedNavigationTemporalTableBuilder IsTemporal( { tableBuilder.Metadata.SetIsTemporal(temporal); - return new OwnedNavigationTemporalTableBuilder(tableBuilder.OwnedNavigationBuilder); + return new OwnedNavigationTemporalTableBuilder(tableBuilder.GetInfrastructure()); } /// @@ -127,7 +127,7 @@ public static OwnedNavigationTableBuilder IsTemporal( { tableBuilder.Metadata.SetIsTemporal(true); - buildAction(new OwnedNavigationTemporalTableBuilder(tableBuilder.OwnedNavigationBuilder)); + buildAction(new OwnedNavigationTemporalTableBuilder(tableBuilder.GetInfrastructure())); return tableBuilder; } @@ -139,18 +139,20 @@ public static OwnedNavigationTableBuilder IsTemporal( /// See Using SQL Server temporal tables with EF Core /// for more information. /// - /// The entity type being configured. + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. /// The builder for the table being configured. /// A value indicating whether the table is temporal. /// An object that can be used to configure the temporal table. - public static OwnedNavigationTemporalTableBuilder IsTemporal( - this OwnedNavigationTableBuilder tableBuilder, + public static OwnedNavigationTemporalTableBuilder IsTemporal( + this OwnedNavigationTableBuilder tableBuilder, bool temporal = true) - where TEntity : class + where TOwnerEntity : class + where TDependentEntity : class { tableBuilder.Metadata.SetIsTemporal(temporal); - return new OwnedNavigationTemporalTableBuilder(tableBuilder.OwnedNavigationBuilder); + return new (tableBuilder.GetInfrastructure>()); } /// @@ -160,17 +162,19 @@ public static OwnedNavigationTemporalTableBuilder IsTemporal( /// See Using SQL Server temporal tables with EF Core /// for more information. /// - /// The entity type being configured. + /// The entity type owning the relationship. + /// The dependent entity type of the relationship. /// The builder for the table being configured. /// An action that performs configuration of the temporal table. /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationTableBuilder IsTemporal( - this OwnedNavigationTableBuilder tableBuilder, - Action> buildAction) - where TEntity : class + public static OwnedNavigationTableBuilder IsTemporal( + this OwnedNavigationTableBuilder tableBuilder, + Action> buildAction) + where TOwnerEntity : class + where TDependentEntity : class { tableBuilder.Metadata.SetIsTemporal(true); - buildAction(new OwnedNavigationTemporalTableBuilder(tableBuilder.OwnedNavigationBuilder)); + buildAction(new (tableBuilder.GetInfrastructure>())); return tableBuilder; } diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs index 3d066e9c478..529f038944f 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs @@ -411,14 +411,14 @@ protected override void ValidateSharedColumnsCompatibility( foreach (var property in mappedTypes.SelectMany(et => et.GetDeclaredProperties())) { + var columnName = property.GetColumnName(storeObject); + if (columnName == null) + { + continue; + } + if (property.GetValueGenerationStrategy(storeObject) == SqlServerValueGenerationStrategy.IdentityColumn) { - var columnName = property.GetColumnName(storeObject); - if (columnName == null) - { - continue; - } - identityColumns[columnName] = property; } } diff --git a/src/EFCore.SqlServer/Metadata/Builders/OwnedNavigationTemporalPeriodPropertyBuilder.cs b/src/EFCore.SqlServer/Metadata/Builders/OwnedNavigationTemporalPeriodPropertyBuilder.cs index edb6d450ef8..8f57830a236 100644 --- a/src/EFCore.SqlServer/Metadata/Builders/OwnedNavigationTemporalPeriodPropertyBuilder.cs +++ b/src/EFCore.SqlServer/Metadata/Builders/OwnedNavigationTemporalPeriodPropertyBuilder.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; diff --git a/src/EFCore.SqlServer/Metadata/Builders/OwnedNavigationTemporalTableBuilder.cs b/src/EFCore.SqlServer/Metadata/Builders/OwnedNavigationTemporalTableBuilder.cs index c067f3a93de..f3b5835b7bd 100644 --- a/src/EFCore.SqlServer/Metadata/Builders/OwnedNavigationTemporalTableBuilder.cs +++ b/src/EFCore.SqlServer/Metadata/Builders/OwnedNavigationTemporalTableBuilder.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; diff --git a/src/EFCore.SqlServer/Metadata/Builders/OwnedNavigationTemporalTableBuilder`.cs b/src/EFCore.SqlServer/Metadata/Builders/OwnedNavigationTemporalTableBuilder``.cs similarity index 68% rename from src/EFCore.SqlServer/Metadata/Builders/OwnedNavigationTemporalTableBuilder`.cs rename to src/EFCore.SqlServer/Metadata/Builders/OwnedNavigationTemporalTableBuilder``.cs index 2eb7cc7a7f1..5569c0196e6 100644 --- a/src/EFCore.SqlServer/Metadata/Builders/OwnedNavigationTemporalTableBuilder`.cs +++ b/src/EFCore.SqlServer/Metadata/Builders/OwnedNavigationTemporalTableBuilder``.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -7,9 +7,11 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// Instances of this class are returned from methods when using the API /// and it is not designed to be directly constructed in your application code. /// -/// The entity type being configured. -public class OwnedNavigationTemporalTableBuilder : OwnedNavigationTemporalTableBuilder - where TEntity : class +/// The entity type owning the relationship. +/// The dependent entity type of the relationship. +public class OwnedNavigationTemporalTableBuilder : OwnedNavigationTemporalTableBuilder + where TOwnerEntity : class + where TDependentEntity : class { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -32,8 +34,8 @@ public OwnedNavigationTemporalTableBuilder(OwnedNavigationBuilder referenceOwner /// /// The name of the history table. /// The same builder instance so that multiple calls can be chained. - public new virtual OwnedNavigationTemporalTableBuilder UseHistoryTable(string name) - => (OwnedNavigationTemporalTableBuilder)base.UseHistoryTable(name); + public new virtual OwnedNavigationTemporalTableBuilder UseHistoryTable(string name) + => (OwnedNavigationTemporalTableBuilder)base.UseHistoryTable(name); /// /// Configures a history table for the entity mapped to a temporal table. @@ -45,6 +47,6 @@ public OwnedNavigationTemporalTableBuilder(OwnedNavigationBuilder referenceOwner /// The name of the history table. /// The schema of the history table. /// The same builder instance so that multiple calls can be chained. - public new virtual OwnedNavigationTemporalTableBuilder UseHistoryTable(string name, string? schema) - => (OwnedNavigationTemporalTableBuilder)base.UseHistoryTable(name, schema); + public new virtual OwnedNavigationTemporalTableBuilder UseHistoryTable(string name, string? schema) + => (OwnedNavigationTemporalTableBuilder)base.UseHistoryTable(name, schema); } diff --git a/src/EFCore.SqlServer/Metadata/Builders/TemporalPeriodPropertyBuilder.cs b/src/EFCore.SqlServer/Metadata/Builders/TemporalPeriodPropertyBuilder.cs index a87e514ed13..5846cd045b4 100644 --- a/src/EFCore.SqlServer/Metadata/Builders/TemporalPeriodPropertyBuilder.cs +++ b/src/EFCore.SqlServer/Metadata/Builders/TemporalPeriodPropertyBuilder.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; diff --git a/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder.cs b/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder.cs index a62bfc54250..35c49eda896 100644 --- a/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder.cs +++ b/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; diff --git a/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder`.cs b/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder`.cs index 8afbd9aab12..cbaa5b76a66 100644 --- a/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder`.cs +++ b/src/EFCore.SqlServer/Metadata/Builders/TemporalTableBuilder`.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. namespace Microsoft.EntityFrameworkCore.Metadata.Builders; diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationConvention.cs index 100047dc011..8e890a7f710 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationConvention.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationConvention.cs @@ -96,16 +96,13 @@ public override void ProcessEntityTypeAnnotationChanged( /// The store value generation strategy to set for the given property. protected override ValueGenerated? GetValueGenerated(IConventionProperty property) { - var tableName = property.DeclaringEntityType.GetTableName(); - if (tableName == null) + var declaringTable = property.GetMappedStoreObjects(StoreObjectType.Table).FirstOrDefault(); + if (declaringTable.Name == null) { return null; } - return GetValueGenerated( - property, - StoreObjectIdentifier.Table(tableName, property.DeclaringEntityType.GetSchema()), - Dependencies.TypeMappingSource); + return GetValueGenerated(property, declaringTable, Dependencies.TypeMappingSource); } /// diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationStrategyConvention.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationStrategyConvention.cs index 2d213f9cc53..1bb3572b80d 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationStrategyConvention.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerValueGenerationStrategyConvention.cs @@ -60,26 +60,24 @@ public virtual void ProcessModelFinalizing( foreach (var property in entityType.GetDeclaredProperties()) { SqlServerValueGenerationStrategy? strategy = null; - var table = entityType.GetTableName(); - if (table != null) + var declaringTable = property.GetMappedStoreObjects(StoreObjectType.Table).FirstOrDefault(); + if (declaringTable.Name != null) { - var storeObject = StoreObjectIdentifier.Table(table, entityType.GetSchema()); - strategy = property.GetValueGenerationStrategy(storeObject, Dependencies.TypeMappingSource); + strategy = property.GetValueGenerationStrategy(declaringTable, Dependencies.TypeMappingSource); if (strategy == SqlServerValueGenerationStrategy.None - && !IsStrategyNoneNeeded(property, storeObject)) + && !IsStrategyNoneNeeded(property, declaringTable)) { strategy = null; } } else { - var view = entityType.GetViewName(); - if (view != null) + var declaringView = property.GetMappedStoreObjects(StoreObjectType.View).FirstOrDefault(); + if (declaringView.Name != null) { - var storeObject = StoreObjectIdentifier.View(view, entityType.GetViewSchema()); - strategy = property.GetValueGenerationStrategy(storeObject, Dependencies.TypeMappingSource); + strategy = property.GetValueGenerationStrategy(declaringView, Dependencies.TypeMappingSource); if (strategy == SqlServerValueGenerationStrategy.None - && !IsStrategyNoneNeeded(property, storeObject)) + && !IsStrategyNoneNeeded(property, declaringView)) { strategy = null; } diff --git a/src/EFCore/Infrastructure/IInfrastructure.cs b/src/EFCore/Infrastructure/IInfrastructure.cs index dc7c49c726e..85063f47bcb 100644 --- a/src/EFCore/Infrastructure/IInfrastructure.cs +++ b/src/EFCore/Infrastructure/IInfrastructure.cs @@ -18,7 +18,7 @@ namespace Microsoft.EntityFrameworkCore.Infrastructure; /// for more information and examples. /// /// The type of the property being hidden. -public interface IInfrastructure +public interface IInfrastructure { /// /// Gets the value of the property being hidden. diff --git a/src/EFCore/Metadata/IProperty.cs b/src/EFCore/Metadata/IProperty.cs index 6e6edd84b1e..212e3135853 100644 --- a/src/EFCore/Metadata/IProperty.cs +++ b/src/EFCore/Metadata/IProperty.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections; using Microsoft.EntityFrameworkCore.Internal; namespace Microsoft.EntityFrameworkCore.Metadata; diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index c9ab45f67c6..444a365cc41 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -203,6 +203,7 @@ public void Test_new_annotations_handled_for_properties() RelationalAnnotationNames.ForeignKeyMappings, RelationalAnnotationNames.TableIndexMappings, RelationalAnnotationNames.UniqueConstraintMappings, + RelationalAnnotationNames.MappingFragments, RelationalAnnotationNames.Name, RelationalAnnotationNames.Sequences, #pragma warning disable CS0618 // Type or member is obsolete @@ -906,9 +907,11 @@ private class EntityWithEveryPrimitive [Flags] public enum Enum1 { +#pragma warning disable SA1602 // Enumeration items should be documented Default = 0, One = 1, Two = 2 +#pragma warning restore SA1602 // Enumeration items should be documented } private ModelSnapshot CompileModelSnapshot(string modelSnapshotCode, string modelSnapshotTypeName) diff --git a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs index 61ba71b3773..1932ffc4ce8 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/SnapshotModelProcessorTest.cs @@ -261,6 +261,7 @@ private void AssertAnnotations(IMutableAnnotatable element) && a != RelationalAnnotationNames.TableIndexMappings && a != RelationalAnnotationNames.UniqueConstraintMappings && a != RelationalAnnotationNames.RelationalOverrides + && a != RelationalAnnotationNames.MappingFragments #pragma warning disable CS0618 // Type or member is obsolete && a != RelationalAnnotationNames.SequencePrefix #pragma warning restore CS0618 // Type or member is obsolete diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs index b4d6c0afd5f..3310b6f2641 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs @@ -1029,9 +1029,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) model => { var entitType = model.FindEntityType("TestNamespace.Entity"); - Assert.Equal("propertyA", entitType.GetProperty("A").GetColumnBaseName()); + Assert.Equal("propertyA", entitType.GetProperty("A").GetColumnName()); Assert.Equal("nchar(10)", entitType.GetProperty("B").GetColumnType()); - Assert.Equal("random", entitType.GetProperty("C").GetColumnBaseName()); + Assert.Equal("random", entitType.GetProperty("C").GetColumnName()); Assert.Equal("varchar(200)", entitType.GetProperty("C").GetColumnType()); }); diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 128850130ce..0496f1e09b6 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -1093,12 +1093,13 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), valueGenerated: ValueGenerated.OnAdd, afterSaveBehavior: PropertySaveBehavior.Throw); - var overrides = new SortedDictionary(); + var overrides = new StoreObjectDictionary(); var idPrincipalDerived = new RuntimeRelationalPropertyOverrides( id, + StoreObjectIdentifier.Table(""PrincipalDerived"", null), true, ""DerivedId""); - overrides[StoreObjectIdentifier.Table(""PrincipalDerived"", null)] = idPrincipalDerived; + overrides.Add(StoreObjectIdentifier.Table(""PrincipalDerived"", null), idPrincipalDerived); id.AddAnnotation(""Relational:RelationalOverrides"", overrides); id.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); @@ -1217,6 +1218,31 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba afterSaveBehavior: PropertySaveBehavior.Throw); principalBaseAlternateId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + var details = runtimeEntityType.AddProperty( + ""Details"", + typeof(string), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty(""Details"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField(""
k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyAccessMode: PropertyAccessMode.Field, + nullable: true); + var overrides = new StoreObjectDictionary(); + var detailsDetails = new RuntimeRelationalPropertyOverrides( + details, + StoreObjectIdentifier.Table(""Details"", null), + false, + null); + overrides.Add(StoreObjectIdentifier.Table(""Details"", null), detailsDetails); + details.AddAnnotation(""Relational:RelationalOverrides"", overrides); + details.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var number = runtimeEntityType.AddProperty( + ""Number"", + typeof(int), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty(""Number"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyAccessMode: PropertyAccessMode.Field); + number.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + var context = runtimeEntityType.AddServiceProperty( ""Context"", propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty(""Context"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)); @@ -1253,6 +1279,13 @@ public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEnt public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) { + var fragments = new StoreObjectDictionary(); + var detailsFragment = new RuntimeEntityTypeMappingFragment( + runtimeEntityType, + StoreObjectIdentifier.Table(""Details"", null), + null); + fragments.Add(StoreObjectIdentifier.Table(""Details"", null), detailsFragment); + runtimeEntityType.AddAnnotation(""Relational:MappingFragments"", fragments); runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); runtimeEntityType.AddAnnotation(""Relational:Schema"", ""mySchema""); runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); @@ -1311,6 +1344,21 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? ba afterSaveBehavior: PropertySaveBehavior.Throw); id.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + var details = runtimeEntityType.AddProperty( + ""Details"", + typeof(string), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty(""Details"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField(""
k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + nullable: true); + details.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + + var number = runtimeEntityType.AddProperty( + ""Number"", + typeof(int), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty(""Number"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + number.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); + var context = runtimeEntityType.AddServiceProperty( ""Context"", propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty(""Context"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)); @@ -1618,9 +1666,13 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Equal( CoreStrings.RuntimeModelMissingData, Assert.Throws(() => model.GetCollation()).Message); - Assert.Null(model[RelationalAnnotationNames.Collation]); + Assert.Equal(new[] + { + RelationalAnnotationNames.MaxIdentifierLength, + SqlServerAnnotationNames.ValueGenerationStrategy + }, + model.GetAnnotations().Select(a => a.Name)); Assert.Equal(SqlServerValueGenerationStrategy.IdentityColumn, model.GetValueGenerationStrategy()); - Assert.Null(model[CoreAnnotationNames.PropertyAccessMode]); Assert.Equal( CoreStrings.RuntimeModelMissingData, Assert.Throws(() => model.GetPropertyAccessMode()).Message); @@ -1663,6 +1715,12 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Throws(() => principalBase.GetSeedData()).Message); var principalId = principalBase.FindProperty(nameof(PrincipalBase.Id)); + Assert.Equal(new[] + { + RelationalAnnotationNames.RelationalOverrides, + SqlServerAnnotationNames.ValueGenerationStrategy + }, + principalId.GetAnnotations().Select(a => a.Name)); Assert.Equal(typeof(long?), principalId.ClrType); Assert.Equal(typeof(long?), principalId.PropertyInfo.PropertyType); Assert.Equal(typeof(long?), principalId.FieldInfo.FieldType); @@ -1672,7 +1730,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Equal(PropertySaveBehavior.Save, principalId.GetBeforeSaveBehavior()); Assert.Null(principalId[CoreAnnotationNames.BeforeSaveBehavior]); Assert.Null(principalId[CoreAnnotationNames.AfterSaveBehavior]); - Assert.Equal("Id", principalId.GetColumnBaseName()); + Assert.Equal("Id", principalId.GetColumnName()); Assert.Equal("Id", principalId.GetColumnName(StoreObjectIdentifier.Table("PrincipalBase", "mySchema"))); Assert.Equal("DerivedId", principalId.GetColumnName(StoreObjectIdentifier.Table("PrincipalDerived"))); Assert.Equal("bigint", principalId.GetColumnType()); @@ -1697,7 +1755,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Equal(typeof(Point), principalAlternateId.ClrType); Assert.False(principalAlternateId.IsNullable); Assert.Equal(ValueGenerated.OnAdd, principalAlternateId.ValueGenerated); - Assert.Equal("AlternateId", principalAlternateId.GetColumnBaseName()); + Assert.Equal("AlternateId", principalAlternateId.GetColumnName()); Assert.Equal("geometry", principalAlternateId.GetColumnType()); Assert.Equal(0, ((Point)principalAlternateId.GetDefaultValue()).SRID); Assert.IsType>(principalAlternateId.GetValueConverter()); @@ -1713,6 +1771,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Equal(2, principalBase.GetIndexes().Count()); var compositeIndex = principalBase.GetIndexes().First(); + Assert.Empty(compositeIndex.GetAnnotations()); Assert.Equal(new[] { principalAlternateId, principalId }, compositeIndex.Properties); Assert.False(compositeIndex.IsUnique); Assert.Null(compositeIndex.Name); @@ -1754,6 +1813,11 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Equal("AK_PrincipalBase_Id", principalAlternateKey.GetName()); var principalKey = principalBase.GetKeys().Last(); + Assert.Equal(new[] + { + RelationalAnnotationNames.Name + }, + principalKey.GetAnnotations().Select(a => a.Name)); Assert.Equal(new[] { principalId, principalAlternateId }, principalKey.Properties); Assert.True(principalKey.IsPrimaryKey()); Assert.Equal("PK", principalKey.GetName()); @@ -1765,6 +1829,11 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Equal(new[] { principalAlternateKey, principalKey }, principalId.GetContainingKeys()); var referenceOwnedNavigation = principalBase.GetNavigations().Single(); + Assert.Equal(new[] + { + CoreAnnotationNames.EagerLoaded + }, + referenceOwnedNavigation.GetAnnotations().Select(a => a.Name)); Assert.Equal(nameof(PrincipalBase.Owned), referenceOwnedNavigation.Name); Assert.False(referenceOwnedNavigation.IsCollection); Assert.True(referenceOwnedNavigation.IsEagerLoaded); @@ -1800,7 +1869,14 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) CoreStrings.RuntimeModelMissingData, Assert.Throws(() => referenceOwnedType.GetNavigationAccessMode()).Message); + var ownedFragment = referenceOwnedType.GetMappingFragments().Single(); + Assert.Equal(nameof(OwnedType.Details), + referenceOwnedType.FindProperty(nameof(OwnedType.Details)).GetColumnName(ownedFragment.StoreObject)); + Assert.Null(referenceOwnedType.FindProperty(nameof(OwnedType.Details)) + .GetColumnName(StoreObjectIdentifier.Create(referenceOwnedType, StoreObjectType.Table).Value)); + var referenceOwnership = referenceOwnedNavigation.ForeignKey; + Assert.Empty(referenceOwnership.GetAnnotations()); Assert.Same(referenceOwnership, referenceOwnedType.FindOwnership()); Assert.True(referenceOwnership.IsOwnership); Assert.True(referenceOwnership.IsRequired); @@ -1813,6 +1889,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Same(principalKey, referenceOwnership.PrincipalKey); var ownedServiceProperty = referenceOwnedType.GetServiceProperties().Single(); + Assert.Empty(ownedServiceProperty.GetAnnotations()); Assert.Equal(typeof(DbContext), ownedServiceProperty.ClrType); Assert.Equal(typeof(DbContext), ownedServiceProperty.PropertyInfo.PropertyType); Assert.Null(ownedServiceProperty.FieldInfo); @@ -1938,7 +2015,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.False(rowid.IsShadowProperty()); Assert.True(rowid.IsConcurrencyToken); Assert.Equal(ValueGenerated.OnAddOrUpdate, rowid.ValueGenerated); - Assert.Equal("rowid", rowid.GetColumnBaseName()); + Assert.Equal("rowid", rowid.GetColumnName()); Assert.Equal("rowversion", rowid.GetColumnType()); Assert.Null(rowid[RelationalAnnotationNames.Comment]); Assert.Equal( @@ -1996,7 +2073,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.False(dependentData.IsShadowProperty()); Assert.False(dependentData.IsConcurrencyToken); Assert.Equal(ValueGenerated.Never, dependentData.ValueGenerated); - Assert.Equal("Data", dependentData.GetColumnBaseName()); + Assert.Equal("Data", dependentData.GetColumnName()); Assert.Equal("char(20)", dependentData.GetColumnType()); Assert.Equal(20, dependentData.GetMaxLength()); Assert.False(dependentData.IsUnicode()); @@ -2013,7 +2090,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.True(dependentMoney.IsShadowProperty()); Assert.False(dependentMoney.IsConcurrencyToken); Assert.Equal(ValueGenerated.Never, dependentMoney.ValueGenerated); - Assert.Equal("Money", dependentMoney.GetColumnBaseName()); + Assert.Equal("Money", dependentMoney.GetColumnName()); Assert.Equal("decimal(9,3)", dependentMoney.GetColumnType()); Assert.Null(dependentMoney.GetMaxLength()); Assert.Null(dependentMoney.IsUnicode()); @@ -2103,6 +2180,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { ob.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); ob.UsePropertyAccessMode(PropertyAccessMode.Field); + + ob.SplitToTable("Details", s => s.Property(e => e.Details)); }); eb.Navigation(e => e.Owned).IsRequired().HasField("_ownedField") @@ -2672,6 +2751,9 @@ public DbContext Context } } + public int Number { get; set; } + public string Details { get; set; } + public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangingEventHandler PropertyChanging; } @@ -3853,7 +3935,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Equal(typeof(Point), point.ClrType); Assert.True(point.IsNullable); Assert.Equal(ValueGenerated.Never, point.ValueGenerated); - Assert.Equal("Point", point.GetColumnBaseName()); + Assert.Equal("Point", point.GetColumnName()); Assert.Equal("POINT", point.GetColumnType()); Assert.Null(point.GetValueConverter()); Assert.IsType>(point.GetValueComparer()); diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs index 2741df760e7..bc35f0c1a74 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs @@ -228,7 +228,7 @@ public void Loads_column_types() }, col1 => { - Assert.Equal("created", col1.GetColumnBaseName()); + Assert.Equal("created", col1.GetColumnName()); Assert.Equal(ValueGenerated.OnAdd, col1.ValueGenerated); }, col2 => @@ -239,12 +239,12 @@ public void Loads_column_types() }, col3 => { - Assert.Equal("modified", col3.GetColumnBaseName()); + Assert.Equal("modified", col3.GetColumnName()); Assert.Equal(ValueGenerated.OnAddOrUpdate, col3.ValueGenerated); }, col4 => { - Assert.Equal("occupation", col4.GetColumnBaseName()); + Assert.Equal("occupation", col4.GetColumnName()); Assert.Equal(typeof(string), col4.ClrType); Assert.False(col4.IsColumnNullable()); Assert.Null(col4.GetMaxLength()); @@ -516,7 +516,7 @@ public void Primary_key(string[] keyProps, int length) var model = (EntityType)_factory.Create(info, new ModelReverseEngineerOptions()).GetEntityTypes().Single(); Assert.Equal("MyPk", model.FindPrimaryKey().GetName()); - Assert.Equal(keyProps, model.FindPrimaryKey().Properties.Select(p => p.GetColumnBaseName()).ToArray()); + Assert.Equal(keyProps, model.FindPrimaryKey().Properties.Select(p => p.GetColumnName()).ToArray()); } [ConditionalFact] @@ -1542,12 +1542,12 @@ public void Unique_names() s1 => { Assert.Equal("SanItized", s1.Name); - Assert.Equal("San itized", s1.GetColumnBaseName()); + Assert.Equal("San itized", s1.GetColumnName()); }, s2 => { Assert.Equal("SanItized1", s2.Name); - Assert.Equal("San+itized", s2.GetColumnBaseName()); + Assert.Equal("San+itized", s2.GetColumnName()); }); }, ef2 => @@ -1556,7 +1556,7 @@ public void Unique_names() Assert.Equal("EF1", ef2.Name); var id = Assert.Single(ef2.GetProperties()); Assert.Equal("Id", id.Name); - Assert.Equal("Id", id.GetColumnBaseName()); + Assert.Equal("Id", id.GetColumnName()); }); } @@ -2202,7 +2202,7 @@ public void UseDatabaseNames_and_NoPluralize_work_together( new ModelReverseEngineerOptions { UseDatabaseNames = useDatabaseNames, NoPluralize = noPluralize }); var user = Assert.Single(model.GetEntityTypes().Where(e => e.GetTableName() == userTableName)); - var id = Assert.Single(user.GetProperties().Where(p => p.GetColumnBaseName() == "id")); + var id = Assert.Single(user.GetProperties().Where(p => p.GetColumnName() == "id")); var foreignKey = Assert.Single(user.GetReferencingForeignKeys()); if (useDatabaseNames && noPluralize) { diff --git a/test/EFCore.InMemory.FunctionalTests/DataAnnotationInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/DataAnnotationInMemoryTest.cs index aaebbd2a2ad..ddcf2ca101a 100644 --- a/test/EFCore.InMemory.FunctionalTests/DataAnnotationInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/DataAnnotationInMemoryTest.cs @@ -10,6 +10,8 @@ public DataAnnotationInMemoryTest(DataAnnotationInMemoryFixture fixture) { } + protected override TestHelpers TestHelpers => InMemoryTestHelpers.Instance; + public override void ConcurrencyCheckAttribute_throws_if_value_in_database_changed() { using var context = CreateContext(); diff --git a/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs b/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs index 511be5626ba..82923d7d274 100644 --- a/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs +++ b/test/EFCore.Relational.Specification.Tests/TestUtilities/TestSqlLoggerFactory.cs @@ -19,8 +19,6 @@ public class TestSqlLoggerFactory : ListLoggerFactory private static readonly string _eol = Environment.NewLine; private static readonly object _queryBaselineFileLock = new(); - private static readonly HashSet _overriddenMethods = new(); - private static readonly object _queryBaselineRewritingLock = new(); private static readonly ConcurrentDictionary _queryBaselineRewritingFileInfos = new(); public TestSqlLoggerFactory() @@ -149,17 +147,6 @@ public void AssertBaseline(string[] expected, bool assertOrder = true) lock (_queryBaselineFileLock) { File.AppendAllText(logFile, contents); - - // if (!_overriddenMethods.Any()) - // { - // File.Delete(logFile); - // } - // - // if (!_overriddenMethods.Contains(methodName)) - // { - // File.AppendAllText(logFile, overrideString); - // _overriddenMethods.Add(methodName); - // } } throw; diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index b430e23a33e..c3fe92aeb28 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -625,6 +625,104 @@ public virtual void Detect_partially_excluded_shared_table() modelBuilder); } + [ConditionalFact] + public virtual void Detects_entity_splitting_on_base_type() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().ToView("Animal").SplitToView("AnimalDetails", s => s.Property(a => a.Name)); + modelBuilder.Entity().ToView("Cat"); + + VerifyError( + RelationalStrings.EntitySplittingHierarchy(nameof(Animal), "AnimalDetails"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_entity_splitting_on_derived_type() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().ToTable("Animal"); + modelBuilder.Entity().ToTable("Cat").SplitToTable("CatDetails", s => s.Property(a => a.Name)); + + VerifyError( + RelationalStrings.EntitySplittingHierarchy(nameof(Cat), "CatDetails"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_entity_splitting_with_unmapped_main() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().SplitToView("AnimalDetails", s => s.Property(a => a.Name)); + + VerifyError( + RelationalStrings.EntitySplittingUnmappedMainFragment(nameof(Animal), "AnimalDetails", "View"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_entity_splitting_to_with_conflicting_main() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().ToTable("Animal").SplitToTable("Animal", s => s.Property(a => a.Name)); + + VerifyError( + RelationalStrings.EntitySplittingConflictingMainFragment(nameof(Animal), "Animal"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_entity_splitting_with_unmapped_PK() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().SplitToTable("AnimalDetails", s => s.Property(a => a.Id).HasColumnName(null)); + + VerifyError( + RelationalStrings.EntitySplittingMissingPrimaryKey(nameof(Animal), "AnimalDetails"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_entity_splitting_without_properties() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().SplitToTable("AnimalDetails", s => { }); + + VerifyError( + RelationalStrings.EntitySplittingMissingProperties(nameof(Animal), "AnimalDetails"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_entity_splitting_to_table_with_all_properties() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().SplitToTable("AnimalDetails", s => + { + s.Property(a => a.Name); + s.Property("FavoritePersonId"); + }); + + VerifyError( + RelationalStrings.EntitySplittingMissingPropertiesMainFragment(nameof(Animal), "Animal"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_entity_splitting_to_view_with_all_properties() + { + var modelBuilder = CreateConventionalModelBuilder(); + modelBuilder.Entity().ToView("Animal").SplitToView("AnimalDetails", s => + { + s.Property(a => a.Name); + s.Property("FavoritePersonId"); + }); + + VerifyError( + RelationalStrings.EntitySplittingMissingPropertiesMainFragment(nameof(Animal), "Animal"), + modelBuilder); + } + [ConditionalFact] public virtual void Detects_duplicate_column_names() { @@ -2043,7 +2141,8 @@ public virtual void Detects_unmapped_foreign_keys_in_TPC() public virtual void Passes_for_valid_table_overrides() { var modelBuilder = CreateConventionalModelBuilder(); - var property = modelBuilder.Entity().Property(a => a.Name).GetInfrastructure(); + modelBuilder.Entity(); + var property = modelBuilder.Entity().Property(a => a.Identity).GetInfrastructure(); modelBuilder.Entity().ToTable("Dog"); property.HasColumnName("DogName", StoreObjectIdentifier.Table("Dog")); @@ -2054,11 +2153,27 @@ public virtual void Passes_for_valid_table_overrides() public virtual void Detects_invalid_table_overrides() { var modelBuilder = CreateConventionalModelBuilder(); - var property = modelBuilder.Entity().Property(a => a.Name).GetInfrastructure(); + modelBuilder.Entity(); + var property = modelBuilder.Entity().Property(a => a.Identity).GetInfrastructure(); property.HasColumnName("DogName", StoreObjectIdentifier.Table("Dog")); VerifyError( - RelationalStrings.TableOverrideMismatch("Animal.Name", "Dog"), + RelationalStrings.TableOverrideMismatch("Dog.Identity", "Dog"), + modelBuilder); + } + + [ConditionalFact] + public virtual void Detects_column_override_on_an_inherited_property_with_TPT() + { + var modelBuilder = CreateConventionalModelBuilder(); + + modelBuilder.Entity() + .Ignore(a => a.FavoritePerson); + + modelBuilder.Entity().ToTable("Cat", b => b.Property(c => c.Name).HasColumnName("Name")); + + VerifyError( + RelationalStrings.TableOverrideMismatch("Animal.Name", "Cat"), modelBuilder); } @@ -2066,7 +2181,8 @@ public virtual void Detects_invalid_table_overrides() public virtual void Passes_for_valid_view_overrides() { var modelBuilder = CreateConventionalModelBuilder(); - var property = modelBuilder.Entity().ToView("Animal").Property(a => a.Name).GetInfrastructure(); + modelBuilder.Entity(); + var property = modelBuilder.Entity().Property(a => a.Identity).GetInfrastructure(); modelBuilder.Entity().ToView("Dog"); property.HasColumnName("DogName", StoreObjectIdentifier.View("Dog")); @@ -2077,11 +2193,12 @@ public virtual void Passes_for_valid_view_overrides() public virtual void Detects_invalid_view_overrides() { var modelBuilder = CreateConventionalModelBuilder(); - var property = modelBuilder.Entity().Property(a => a.Name).GetInfrastructure(); + modelBuilder.Entity(); + var property = modelBuilder.Entity().Property(a => a.Identity).GetInfrastructure(); property.HasColumnName("DogName", StoreObjectIdentifier.View("Dog")); VerifyError( - RelationalStrings.ViewOverrideMismatch("Animal.Name", "Dog"), + RelationalStrings.ViewOverrideMismatch("Dog.Identity", "Dog"), modelBuilder); } diff --git a/test/EFCore.Relational.Tests/Metadata/Conventions/TableSharingConcurrencyTokenConventionTest.cs b/test/EFCore.Relational.Tests/Metadata/Conventions/TableSharingConcurrencyTokenConventionTest.cs index de1010c548b..2ed3b5c64d7 100644 --- a/test/EFCore.Relational.Tests/Metadata/Conventions/TableSharingConcurrencyTokenConventionTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/Conventions/TableSharingConcurrencyTokenConventionTest.cs @@ -29,7 +29,7 @@ public virtual void Missing_concurrency_token_property_is_created_on_the_base_ty var concurrencyProperty = animal.FindProperty("_TableSharingConcurrencyTokenConvention_Version"); Assert.True(concurrencyProperty.IsConcurrencyToken); Assert.True(concurrencyProperty.IsShadowProperty()); - Assert.Equal("Version", concurrencyProperty.GetColumnBaseName()); + Assert.Equal("Version", concurrencyProperty.GetColumnName()); Assert.Equal(ValueGenerated.OnAddOrUpdate, concurrencyProperty.ValueGenerated); } @@ -114,7 +114,7 @@ public virtual void Missing_concurrency_token_properties_are_created_on_the_base var concurrencyProperty = animal.FindProperty("_TableSharingConcurrencyTokenConvention_Version"); Assert.True(concurrencyProperty.IsConcurrencyToken); Assert.True(concurrencyProperty.IsShadowProperty()); - Assert.Equal("Version", concurrencyProperty.GetColumnBaseName()); + Assert.Equal("Version", concurrencyProperty.GetColumnName()); Assert.Equal(ValueGenerated.OnUpdate, concurrencyProperty.ValueGenerated); var cat = model.FindEntityType(typeof(Cat)); @@ -124,7 +124,7 @@ public virtual void Missing_concurrency_token_properties_are_created_on_the_base concurrencyProperty = animalHouse.FindProperty("_TableSharingConcurrencyTokenConvention_Version"); Assert.True(concurrencyProperty.IsConcurrencyToken); Assert.True(concurrencyProperty.IsShadowProperty()); - Assert.Equal("Version", concurrencyProperty.GetColumnBaseName()); + Assert.Equal("Version", concurrencyProperty.GetColumnName()); Assert.Equal(ValueGenerated.OnUpdate, concurrencyProperty.ValueGenerated); var theMovie = model.FindEntityType(typeof(TheMovie)); @@ -148,7 +148,7 @@ public virtual void Missing_concurrency_token_property_is_created_on_the_sharing var concurrencyProperty = personEntityType.FindProperty("_TableSharingConcurrencyTokenConvention_Version"); Assert.True(concurrencyProperty.IsConcurrencyToken); Assert.True(concurrencyProperty.IsShadowProperty()); - Assert.Equal("Version", concurrencyProperty.GetColumnBaseName()); + Assert.Equal("Version", concurrencyProperty.GetColumnName()); Assert.Equal(ValueGenerated.OnAddOrUpdate, concurrencyProperty.ValueGenerated); } diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs index 5a5878377ad..30d27f5130d 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs @@ -44,7 +44,7 @@ public void Can_set_column_name() var property = modelBuilder.Model.FindEntityType(typeof(Customer)).FindProperty("Name"); Assert.Equal("Name", property.Name); - Assert.Equal("Eman", property.GetColumnBaseName()); + Assert.Equal("Eman", property.GetColumnName()); } [ConditionalFact] @@ -1420,7 +1420,7 @@ public void Can_access_property() Assert.NotNull(propertyBuilder.IsFixedLength(true)); Assert.True(propertyBuilder.Metadata.IsFixedLength()); Assert.NotNull(propertyBuilder.HasColumnName("Splew")); - Assert.Equal("Splew", propertyBuilder.Metadata.GetColumnBaseName()); + Assert.Equal("Splew", propertyBuilder.Metadata.GetColumnName()); Assert.NotNull(propertyBuilder.HasColumnType("int")); Assert.Equal("int", propertyBuilder.Metadata.GetColumnType()); Assert.NotNull(propertyBuilder.HasDefaultValue(1)); @@ -1437,7 +1437,7 @@ public void Can_access_property() Assert.False(propertyBuilder.Metadata.IsFixedLength()); Assert.NotNull(propertyBuilder.HasColumnName("Splow", fromDataAnnotation: true)); Assert.Null(propertyBuilder.HasColumnName("Splod")); - Assert.Equal("Splow", propertyBuilder.Metadata.GetColumnBaseName()); + Assert.Equal("Splow", propertyBuilder.Metadata.GetColumnName()); Assert.NotNull(propertyBuilder.HasColumnType("varchar", fromDataAnnotation: true)); Assert.Null(propertyBuilder.HasColumnType("int")); Assert.Equal("varchar", propertyBuilder.Metadata.GetColumnType()); diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs index 3b7b9ff52f5..4561c23cf1c 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalMetadataExtensionsTest.cs @@ -58,16 +58,16 @@ public void Can_get_and_set_column_name() .Property(e => e.Name) .Metadata; - Assert.Equal("Name", property.GetColumnBaseName()); + Assert.Equal("Name", property.GetColumnName()); property.SetColumnName("Eman"); Assert.Equal("Name", property.Name); - Assert.Equal("Eman", property.GetColumnBaseName()); + Assert.Equal("Eman", property.GetColumnName()); property.SetColumnName(null); - Assert.Equal("Name", property.GetColumnBaseName()); + Assert.Equal("Name", property.GetColumnName()); } [ConditionalFact] diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index e03720f7505..68929a90759 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -278,17 +278,17 @@ private static void AssertViews(IRelationalModel model, Mapping mapping) Assert.Equal( ordersView.GetReferencingRowInternalForeignKeys(orderType), ordersView.GetRowInternalForeignKeys(orderDetailsType)); Assert.Equal( - RelationalStrings.TableNotMappedEntityType(nameof(SpecialCustomer), ordersView.Name), + RelationalStrings.TableNotMappedEntityType(nameof(SpecialCustomer), ordersView.SchemaQualifiedName), Assert.Throws( () => ordersView.GetReferencingRowInternalForeignKeys(specialCustomerType)).Message); Assert.Equal( - RelationalStrings.TableNotMappedEntityType(nameof(SpecialCustomer), ordersView.Name), + RelationalStrings.TableNotMappedEntityType(nameof(SpecialCustomer), ordersView.SchemaQualifiedName), Assert.Throws( () => ordersView.GetRowInternalForeignKeys(specialCustomerType)).Message); Assert.False(ordersView.IsOptional(orderType)); Assert.True(ordersView.IsOptional(orderDetailsType)); Assert.Equal( - RelationalStrings.TableNotMappedEntityType(nameof(SpecialCustomer), ordersView.Name), + RelationalStrings.TableNotMappedEntityType(nameof(SpecialCustomer), ordersView.SchemaQualifiedName), Assert.Throws( () => ordersView.IsOptional(specialCustomerType)).Message); @@ -309,7 +309,7 @@ private static void AssertViews(IRelationalModel model, Mapping mapping) if (mapping == Mapping.TPC) { Assert.Equal( - RelationalStrings.TableNotMappedEntityType(nameof(SpecialCustomer), customerView.Name), + RelationalStrings.TableNotMappedEntityType(nameof(SpecialCustomer), customerView.SchemaQualifiedName), Assert.Throws( () => customerView.IsOptional(specialCustomerType)).Message); } @@ -578,7 +578,7 @@ private static void AssertTables(IRelationalModel model, Mapping mapping) Assert.Same(orderPkConstraint, orderDetailsPk.GetMappedConstraints().Single()); var orderDetailsPkProperty = orderDetailsPk.Properties.Single(); - Assert.Equal("OrderId", orderDetailsPkProperty.GetColumnBaseName()); + Assert.Equal("OrderId", orderDetailsPkProperty.GetColumnName()); var billingAddressOwnership = orderDetailsType.FindNavigation(nameof(OrderDetails.BillingAddress)).ForeignKey; var billingAddressType = billingAddressOwnership.DeclaringEntityType; diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalPropertyAttributeConventionTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalPropertyAttributeConventionTest.cs index ce00ae727c7..3507b167d20 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalPropertyAttributeConventionTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalPropertyAttributeConventionTest.cs @@ -17,7 +17,7 @@ public void ColumnAttribute_sets_column_name_and_type_with_conventional_builder( var entityBuilder = modelBuilder.Entity(); - Assert.Equal("Post Name", entityBuilder.Property(e => e.Name).Metadata.GetColumnBaseName()); + Assert.Equal("Post Name", entityBuilder.Property(e => e.Name).Metadata.GetColumnName()); Assert.Equal("DECIMAL", entityBuilder.Property(e => e.Name).Metadata.GetColumnType()); Assert.Equal(1, entityBuilder.Property(e => e.Name).Metadata.GetColumnOrder()); } @@ -39,7 +39,7 @@ public void ColumnAttribute_on_field_sets_column_name_and_type_with_conventional var entityBuilder = modelBuilder.Entity(); - Assert.Equal("Post Name", entityBuilder.Property(nameof(F.Name)).Metadata.GetColumnBaseName()); + Assert.Equal("Post Name", entityBuilder.Property(nameof(F.Name)).Metadata.GetColumnName()); Assert.Equal("DECIMAL", entityBuilder.Property(nameof(F.Name)).Metadata.GetColumnType()); Assert.Equal(1, entityBuilder.Property(nameof(F.Name)).Metadata.GetColumnOrder()); } @@ -68,7 +68,7 @@ public void ColumnAttribute_overrides_configuration_from_convention_source() RunConvention(propertyBuilder); - Assert.Equal("Post Name", propertyBuilder.Metadata.GetColumnBaseName()); + Assert.Equal("Post Name", propertyBuilder.Metadata.GetColumnName()); Assert.Equal("DECIMAL", propertyBuilder.Metadata.GetColumnType()); Assert.Equal(1, propertyBuilder.Metadata.GetColumnOrder()); Assert.Equal("Test column comment", propertyBuilder.Metadata.GetComment()); @@ -102,7 +102,7 @@ public void ColumnAttribute_does_not_override_configuration_from_explicit_source RunConvention(propertyBuilder); - Assert.Equal("ExplicitName", propertyBuilder.Metadata.GetColumnBaseName()); + Assert.Equal("ExplicitName", propertyBuilder.Metadata.GetColumnName()); Assert.Equal("BYTE", propertyBuilder.Metadata.GetColumnType()); Assert.Equal(2, propertyBuilder.Metadata.GetColumnOrder()); Assert.Equal("ExplicitComment", propertyBuilder.Metadata.GetComment()); diff --git a/test/EFCore.Relational.Tests/Metadata/TriggerTest.cs b/test/EFCore.Relational.Tests/Metadata/TriggerTest.cs index d4c5c3dc7c3..19accbc96e3 100644 --- a/test/EFCore.Relational.Tests/Metadata/TriggerTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/TriggerTest.cs @@ -52,7 +52,8 @@ public void Create_trigger_on_unmapped_entity_type_throws() var exception = Assert.Throws(() => modelBuilder .Entity() - .ToTable(null, tb => tb.HasTrigger("Customer_Trigger"))); + .ToTable((string)null) + .ToTable(tb => tb.HasTrigger("Customer_Trigger"))); Assert.Equal(RelationalStrings.TriggerOnUnmappedEntityType("Customer_Trigger", "Customer"), exception.Message); } diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs index 6ff38786a8d..908599ab78a 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs @@ -8,6 +8,166 @@ namespace Microsoft.EntityFrameworkCore.ModelBuilding; public class RelationalModelBuilderTest : ModelBuilderTest { + public abstract class RelationalNonRelationshipTestBase : NonRelationshipTestBase + { + [ConditionalFact] + public virtual void Can_use_table_splitting() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity().SplitToTable("OrderDetails", s => + { + s.ExcludeFromMigrations(); + var propertyBuilder = s.Property(o => o.CustomerId); + var columnBuilder = propertyBuilder.HasColumnName("id"); + if (columnBuilder is IInfrastructure> genericBuilder) + { + Assert.IsType>(genericBuilder.Instance.GetInfrastructure>()); + Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides); + } + else + { + var nonGenericBuilder = (IInfrastructure)columnBuilder; + Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure()); + Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides); + } + }); + modelBuilder.Ignore(); + modelBuilder.Ignore(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Order))!; + + Assert.False(entity.IsTableExcludedFromMigrations()); + Assert.False(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order"))); + Assert.True(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("OrderDetails"))); + Assert.Same(entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.Table("OrderDetails"))); + + var customerId = entity.FindProperty(nameof(Order.CustomerId))!; + Assert.Equal("CustomerId", customerId.GetColumnName()); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.Table("Order"))); + Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.Table("OrderDetails"))); + Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.Table("OrderDetails"))); + } + + [ConditionalFact] + public virtual void Can_use_table_splitting_with_schema() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity().ToTable("Order", "dbo") + .SplitToTable("OrderDetails", "sch", s => + s.ExcludeFromMigrations() + .Property(o => o.CustomerId).HasColumnName("id")); + modelBuilder.Ignore(); + modelBuilder.Ignore(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Order))!; + + Assert.False(entity.IsTableExcludedFromMigrations()); + Assert.False(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order", "dbo"))); + Assert.True(entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("OrderDetails", "sch"))); + Assert.Same(entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.Table("OrderDetails", "sch"))); + Assert.Equal(RelationalStrings.TableNotMappedEntityType(nameof(Order), "Order"), + Assert.Throws(() => entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.Table("Order"))).Message); + + var customerId = entity.FindProperty(nameof(Order.CustomerId))!; + Assert.Equal("CustomerId", customerId.GetColumnName()); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.Table("Order", "dbo"))); + Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.Table("OrderDetails", "sch"))); + Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.Table("OrderDetails", "sch"))); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.Table("Order"))); + } + + [ConditionalFact] + public virtual void Can_use_view_splitting() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity().ToView("Order") + .SplitToView("OrderDetails", s => + { + var propertyBuilder = s.Property(o => o.CustomerId); + var columnBuilder = propertyBuilder.HasColumnName("id"); + if (columnBuilder is IInfrastructure> genericBuilder) + { + Assert.IsType>(genericBuilder.Instance.GetInfrastructure>()); + Assert.IsAssignableFrom(genericBuilder.GetInfrastructure().Overrides); + } + else + { + var nonGenericBuilder = (IInfrastructure)columnBuilder; + Assert.IsAssignableFrom(nonGenericBuilder.Instance.GetInfrastructure()); + Assert.IsAssignableFrom(nonGenericBuilder.Instance.Overrides); + } + }); + modelBuilder.Ignore(); + modelBuilder.Ignore(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Order))!; + + Assert.Same(entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.View("OrderDetails"))); + + var customerId = entity.FindProperty(nameof(Order.CustomerId))!; + Assert.Equal("CustomerId", customerId.GetColumnName()); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.View("Order"))); + Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.View("OrderDetails"))); + Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.View("OrderDetails"))); + } + + [ConditionalFact] + public virtual void Can_use_view_splitting_with_schema() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity().ToView("Order", "dbo") + .SplitToView("OrderDetails", "sch", s => + s.Property(o => o.CustomerId).HasColumnName("id")); + modelBuilder.Ignore(); + modelBuilder.Ignore(); + + var model = modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Order))!; + + Assert.Same(entity.GetMappingFragments().Single(), entity.FindMappingFragment(StoreObjectIdentifier.View("OrderDetails", "sch"))); + Assert.Equal(RelationalStrings.TableNotMappedEntityType(nameof(Order), "Order"), + Assert.Throws(() => entity.IsTableExcludedFromMigrations(StoreObjectIdentifier.View("Order"))).Message); + + var customerId = entity.FindProperty(nameof(Order.CustomerId))!; + Assert.Equal("CustomerId", customerId.GetColumnName()); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.View("Order", "dbo"))); + Assert.Equal("id", customerId.GetColumnName(StoreObjectIdentifier.View("OrderDetails", "sch"))); + Assert.Same(customerId.GetOverrides().Single(), customerId.FindOverrides(StoreObjectIdentifier.View("OrderDetails", "sch"))); + Assert.Null(customerId.GetColumnName(StoreObjectIdentifier.View("Order"))); + } + } + + public abstract class RelationalInheritanceTestBase : InheritanceTestBase + { + } + + public abstract class RelationalOneToManyTestBase : OneToManyTestBase + { + } + + public abstract class RelationalManyToOneTestBase : ManyToOneTestBase + { + } + + public abstract class RelationalOneToOneTestBase : OneToOneTestBase + { + } + + public abstract class RelationalManyToManyTestBase : ManyToManyTestBase + { + } + + public abstract class RelationalOwnedTypesTestBase : OwnedTypesTestBase + { + } + public abstract class TestTableBuilder where TEntity : class { @@ -16,6 +176,12 @@ public abstract class TestTableBuilder public abstract string? Schema { get; } public abstract TestTableBuilder ExcludeFromMigrations(bool excluded = true); + + public abstract TestTriggerBuilder HasTrigger(string name); + + public abstract TestColumnBuilder Property(string propertyName); + + public abstract TestColumnBuilder Property(Expression> propertyExpression); } public class GenericTestTableBuilder : TestTableBuilder, IInfrastructure> @@ -42,6 +208,15 @@ protected virtual TestTableBuilder Wrap(TableBuilder tableBuil public override TestTableBuilder ExcludeFromMigrations(bool excluded = true) => Wrap(TableBuilder.ExcludeFromMigrations(excluded)); + + public override TestTriggerBuilder HasTrigger(string name) + => new NonGenericTestTriggerBuilder(TableBuilder.HasTrigger(name)); + + public override TestColumnBuilder Property(string propertyName) + => new NonGenericTestColumnBuilder(TableBuilder.Property(propertyName)); + + public override TestColumnBuilder Property(Expression> propertyExpression) + => new GenericTestColumnBuilder(TableBuilder.Property(propertyExpression)); } public class NonGenericTestTableBuilder : TestTableBuilder, IInfrastructure @@ -68,29 +243,44 @@ protected virtual TestTableBuilder Wrap(TableBuilder tableBuilder) public override TestTableBuilder ExcludeFromMigrations(bool excluded = true) => Wrap(TableBuilder.ExcludeFromMigrations(excluded)); + + public override TestTriggerBuilder HasTrigger(string name) + => new NonGenericTestTriggerBuilder(TableBuilder.HasTrigger(name)); + + public override TestColumnBuilder Property(string propertyName) + => new NonGenericTestColumnBuilder(TableBuilder.Property(propertyName)); + + public override TestColumnBuilder Property(Expression> propertyExpression) + => new GenericTestColumnBuilder(TableBuilder.Property(propertyExpression.GetPropertyAccess().Name)); } - public abstract class TestOwnedNavigationTableBuilder - where TEntity : class + public abstract class TestOwnedNavigationTableBuilder + where TOwnerEntity : class + where TDependentEntity : class { public abstract string? Name { get; } public abstract string? Schema { get; } - public abstract TestOwnedNavigationTableBuilder ExcludeFromMigrations(bool excluded = true); + public abstract TestOwnedNavigationTableBuilder ExcludeFromMigrations(bool excluded = true); + + public abstract TestColumnBuilder Property(string propertyName); + + public abstract TestColumnBuilder Property(Expression> propertyExpression); } - public class GenericTestOwnedNavigationTableBuilder : - TestOwnedNavigationTableBuilder, - IInfrastructure> - where TEntity : class + public class GenericTestOwnedNavigationTableBuilder : + TestOwnedNavigationTableBuilder, + IInfrastructure> + where TOwnerEntity : class + where TDependentEntity : class { - public GenericTestOwnedNavigationTableBuilder(OwnedNavigationTableBuilder tableBuilder) + public GenericTestOwnedNavigationTableBuilder(OwnedNavigationTableBuilder tableBuilder) { TableBuilder = tableBuilder; } - private OwnedNavigationTableBuilder TableBuilder { get; } + private OwnedNavigationTableBuilder TableBuilder { get; } public override string? Name => TableBuilder.Name; @@ -98,18 +288,27 @@ public override string? Name public override string? Schema => TableBuilder.Schema; - OwnedNavigationTableBuilder IInfrastructure>.Instance + OwnedNavigationTableBuilder IInfrastructure>.Instance => TableBuilder; - protected virtual TestOwnedNavigationTableBuilder Wrap(OwnedNavigationTableBuilder tableBuilder) - => new GenericTestOwnedNavigationTableBuilder(tableBuilder); + protected virtual TestOwnedNavigationTableBuilder Wrap( + OwnedNavigationTableBuilder tableBuilder) + => new GenericTestOwnedNavigationTableBuilder(tableBuilder); - public override TestOwnedNavigationTableBuilder ExcludeFromMigrations(bool excluded = true) + public override TestOwnedNavigationTableBuilder ExcludeFromMigrations(bool excluded = true) => Wrap(TableBuilder.ExcludeFromMigrations(excluded)); + + public override TestColumnBuilder Property(string propertyName) + => new NonGenericTestColumnBuilder(TableBuilder.Property(propertyName)); + + public override TestColumnBuilder Property(Expression> propertyExpression) + => new GenericTestColumnBuilder(TableBuilder.Property(propertyExpression)); } - public class NonGenericTestOwnedNavigationTableBuilder : TestOwnedNavigationTableBuilder, IInfrastructure - where TEntity : class + public class NonGenericTestOwnedNavigationTableBuilder : + TestOwnedNavigationTableBuilder, IInfrastructure + where TOwnerEntity : class + where TDependentEntity : class { public NonGenericTestOwnedNavigationTableBuilder(OwnedNavigationTableBuilder tableBuilder) { @@ -127,16 +326,603 @@ public override string? Schema OwnedNavigationTableBuilder IInfrastructure.Instance => TableBuilder; - protected virtual TestOwnedNavigationTableBuilder Wrap(OwnedNavigationTableBuilder tableBuilder) - => new NonGenericTestOwnedNavigationTableBuilder(tableBuilder); + protected virtual TestOwnedNavigationTableBuilder Wrap(OwnedNavigationTableBuilder tableBuilder) + => new NonGenericTestOwnedNavigationTableBuilder(tableBuilder); - public override TestOwnedNavigationTableBuilder ExcludeFromMigrations(bool excluded = true) + public override TestOwnedNavigationTableBuilder ExcludeFromMigrations(bool excluded = true) => Wrap(TableBuilder.ExcludeFromMigrations(excluded)); + + public override TestColumnBuilder Property(string propertyName) + => new NonGenericTestColumnBuilder(TableBuilder.Property(propertyName)); + + public override TestColumnBuilder Property(Expression> propertyExpression) + => new GenericTestColumnBuilder(TableBuilder.Property(propertyExpression.GetPropertyAccess().Name)); + } + + public abstract class TestSplitTableBuilder + where TEntity : class + { + public abstract string? Name { get; } + + public abstract string? Schema { get; } + + public abstract TestSplitTableBuilder ExcludeFromMigrations(bool excluded = true); + + public abstract TestTriggerBuilder HasTrigger(string name); + + public abstract TestColumnBuilder Property(string propertyName); + + public abstract TestColumnBuilder Property(Expression> propertyExpression); + } + + public class GenericTestSplitTableBuilder : TestSplitTableBuilder, IInfrastructure> + where TEntity : class + { + public GenericTestSplitTableBuilder(SplitTableBuilder tableBuilder) + { + TableBuilder = tableBuilder; + } + + private SplitTableBuilder TableBuilder { get; } + + public override string? Name + => TableBuilder.Name; + + public override string? Schema + => TableBuilder.Schema; + + SplitTableBuilder IInfrastructure>.Instance + => TableBuilder; + + protected virtual TestSplitTableBuilder Wrap(SplitTableBuilder tableBuilder) + => new GenericTestSplitTableBuilder(tableBuilder); + + public override TestSplitTableBuilder ExcludeFromMigrations(bool excluded = true) + => Wrap(TableBuilder.ExcludeFromMigrations(excluded)); + + public override TestTriggerBuilder HasTrigger(string name) + => new NonGenericTestTriggerBuilder(TableBuilder.HasTrigger(name)); + + public override TestColumnBuilder Property(string propertyName) + => new NonGenericTestColumnBuilder(TableBuilder.Property(propertyName)); + + public override TestColumnBuilder Property(Expression> propertyExpression) + => new GenericTestColumnBuilder(TableBuilder.Property(propertyExpression)); + } + + public class NonGenericTestSplitTableBuilder : TestSplitTableBuilder, IInfrastructure + where TEntity : class + { + public NonGenericTestSplitTableBuilder(SplitTableBuilder tableBuilder) + { + TableBuilder = tableBuilder; + } + + private SplitTableBuilder TableBuilder { get; } + + public override string? Name + => TableBuilder.Name; + + public override string? Schema + => TableBuilder.Schema; + + SplitTableBuilder IInfrastructure.Instance + => TableBuilder; + + protected virtual TestSplitTableBuilder Wrap(SplitTableBuilder tableBuilder) + => new NonGenericTestSplitTableBuilder(tableBuilder); + + public override TestSplitTableBuilder ExcludeFromMigrations(bool excluded = true) + => Wrap(TableBuilder.ExcludeFromMigrations(excluded)); + + public override TestTriggerBuilder HasTrigger(string name) + => new NonGenericTestTriggerBuilder(TableBuilder.HasTrigger(name)); + + public override TestColumnBuilder Property(string propertyName) + => new NonGenericTestColumnBuilder(TableBuilder.Property(propertyName)); + + public override TestColumnBuilder Property(Expression> propertyExpression) + => new NonGenericTestColumnBuilder(TableBuilder.Property(propertyExpression.GetPropertyAccess().Name)); + } + + public abstract class TestOwnedNavigationSplitTableBuilder + where TOwnerEntity : class + where TDependentEntity : class + { + public abstract string? Name { get; } + + public abstract string? Schema { get; } + + public abstract TestOwnedNavigationSplitTableBuilder ExcludeFromMigrations(bool excluded = true); + + public abstract TestColumnBuilder Property(string propertyName); + + public abstract TestColumnBuilder Property(Expression> propertyExpression); + } + + public class GenericTestOwnedNavigationSplitTableBuilder : + TestOwnedNavigationSplitTableBuilder, + IInfrastructure> + where TOwnerEntity : class + where TDependentEntity : class + { + public GenericTestOwnedNavigationSplitTableBuilder(OwnedNavigationSplitTableBuilder tableBuilder) + { + TableBuilder = tableBuilder; + } + + private OwnedNavigationSplitTableBuilder TableBuilder { get; } + + public override string? Name + => TableBuilder.Name; + + public override string? Schema + => TableBuilder.Schema; + + OwnedNavigationSplitTableBuilder IInfrastructure>.Instance + => TableBuilder; + + protected virtual TestOwnedNavigationSplitTableBuilder Wrap( + OwnedNavigationSplitTableBuilder tableBuilder) + => new GenericTestOwnedNavigationSplitTableBuilder(tableBuilder); + + public override TestOwnedNavigationSplitTableBuilder ExcludeFromMigrations(bool excluded = true) + => Wrap(TableBuilder.ExcludeFromMigrations(excluded)); + + public override TestColumnBuilder Property(string propertyName) + => new NonGenericTestColumnBuilder(TableBuilder.Property(propertyName)); + + public override TestColumnBuilder Property(Expression> propertyExpression) + => new GenericTestColumnBuilder(TableBuilder.Property(propertyExpression)); + } + + public class NonGenericTestOwnedNavigationSplitTableBuilder : + TestOwnedNavigationSplitTableBuilder, IInfrastructure + where TOwnerEntity : class + where TDependentEntity : class + { + public NonGenericTestOwnedNavigationSplitTableBuilder(OwnedNavigationSplitTableBuilder tableBuilder) + { + TableBuilder = tableBuilder; + } + + private OwnedNavigationSplitTableBuilder TableBuilder { get; } + + public override string? Name + => TableBuilder.Name; + + public override string? Schema + => TableBuilder.Schema; + + OwnedNavigationSplitTableBuilder IInfrastructure.Instance + => TableBuilder; + + protected virtual TestOwnedNavigationSplitTableBuilder Wrap(OwnedNavigationSplitTableBuilder tableBuilder) + => new NonGenericTestOwnedNavigationSplitTableBuilder(tableBuilder); + + public override TestOwnedNavigationSplitTableBuilder ExcludeFromMigrations(bool excluded = true) + => Wrap(TableBuilder.ExcludeFromMigrations(excluded)); + + public override TestColumnBuilder Property(string propertyName) + => new NonGenericTestColumnBuilder(TableBuilder.Property(propertyName)); + + public override TestColumnBuilder Property(Expression> propertyExpression) + => new GenericTestColumnBuilder(TableBuilder.Property(propertyExpression.GetPropertyAccess().Name)); + } + + public abstract class TestColumnBuilder + { + public abstract TestColumnBuilder HasColumnName(string? name); + } + + public class GenericTestColumnBuilder : TestColumnBuilder, IInfrastructure> + { + public GenericTestColumnBuilder(ColumnBuilder columnBuilder) + { + ColumnBuilder = columnBuilder; + } + + private ColumnBuilder ColumnBuilder { get; } + + ColumnBuilder IInfrastructure>.Instance + => ColumnBuilder; + + protected virtual TestColumnBuilder Wrap(ColumnBuilder columnBuilder) + => new GenericTestColumnBuilder(columnBuilder); + + public override TestColumnBuilder HasColumnName(string? name) + => Wrap(ColumnBuilder.HasColumnName(name)); + } + + public class NonGenericTestColumnBuilder : TestColumnBuilder, IInfrastructure + { + public NonGenericTestColumnBuilder(ColumnBuilder tableBuilder) + { + ColumnBuilder = tableBuilder; + } + + private ColumnBuilder ColumnBuilder { get; } + + ColumnBuilder IInfrastructure.Instance + => ColumnBuilder; + + protected virtual TestColumnBuilder Wrap(ColumnBuilder tableBuilder) + => new NonGenericTestColumnBuilder(tableBuilder); + + public override TestColumnBuilder HasColumnName(string? name) + => Wrap(ColumnBuilder.HasColumnName(name)); + } + + public abstract class TestViewBuilder + where TEntity : class + { + public abstract string? Name { get; } + + public abstract string? Schema { get; } + + public abstract TestViewColumnBuilder Property(string propertyName); + + public abstract TestViewColumnBuilder Property(Expression> propertyExpression); + } + + public class GenericTestViewBuilder : TestViewBuilder, IInfrastructure> + where TEntity : class + { + public GenericTestViewBuilder(ViewBuilder tableBuilder) + { + ViewBuilder = tableBuilder; + } + + private ViewBuilder ViewBuilder { get; } + + public override string? Name + => ViewBuilder.Name; + + public override string? Schema + => ViewBuilder.Schema; + + ViewBuilder IInfrastructure>.Instance + => ViewBuilder; + + protected virtual TestViewBuilder Wrap(ViewBuilder tableBuilder) + => new GenericTestViewBuilder(tableBuilder); + + public override TestViewColumnBuilder Property(string propertyName) + => new NonGenericTestViewColumnBuilder(ViewBuilder.Property(propertyName)); + + public override TestViewColumnBuilder Property(Expression> propertyExpression) + => new GenericTestViewColumnBuilder(ViewBuilder.Property(propertyExpression)); + } + + public class NonGenericTestViewBuilder : TestViewBuilder, IInfrastructure + where TEntity : class + { + public NonGenericTestViewBuilder(ViewBuilder tableBuilder) + { + ViewBuilder = tableBuilder; + } + + private ViewBuilder ViewBuilder { get; } + + public override string? Name + => ViewBuilder.Name; + + public override string? Schema + => ViewBuilder.Schema; + + ViewBuilder IInfrastructure.Instance + => ViewBuilder; + + protected virtual TestViewBuilder Wrap(ViewBuilder tableBuilder) + => new NonGenericTestViewBuilder(tableBuilder); + + public override TestViewColumnBuilder Property(string propertyName) + => new NonGenericTestViewColumnBuilder(ViewBuilder.Property(propertyName)); + + public override TestViewColumnBuilder Property(Expression> propertyExpression) + => new GenericTestViewColumnBuilder(ViewBuilder.Property(propertyExpression.GetPropertyAccess().Name)); + } + + public abstract class TestOwnedNavigationViewBuilder + where TOwnerEntity : class + where TDependentEntity : class + { + public abstract string? Name { get; } + + public abstract string? Schema { get; } + + public abstract TestViewColumnBuilder Property(string propertyName); + + public abstract TestViewColumnBuilder Property(Expression> propertyExpression); + } + + public class GenericTestOwnedNavigationViewBuilder : + TestOwnedNavigationViewBuilder, + IInfrastructure> + where TOwnerEntity : class + where TDependentEntity : class + { + public GenericTestOwnedNavigationViewBuilder(OwnedNavigationViewBuilder tableBuilder) + { + ViewBuilder = tableBuilder; + } + + private OwnedNavigationViewBuilder ViewBuilder { get; } + + public override string? Name + => ViewBuilder.Name; + + public override string? Schema + => ViewBuilder.Schema; + + OwnedNavigationViewBuilder IInfrastructure>.Instance + => ViewBuilder; + + protected virtual TestOwnedNavigationViewBuilder Wrap( + OwnedNavigationViewBuilder tableBuilder) + => new GenericTestOwnedNavigationViewBuilder(tableBuilder); + + public override TestViewColumnBuilder Property(string propertyName) + => new NonGenericTestViewColumnBuilder(ViewBuilder.Property(propertyName)); + + public override TestViewColumnBuilder Property(Expression> propertyExpression) + => new GenericTestViewColumnBuilder(ViewBuilder.Property(propertyExpression.GetPropertyAccess().Name)); + } + + public class NonGenericTestOwnedNavigationViewBuilder : + TestOwnedNavigationViewBuilder, IInfrastructure + where TOwnerEntity : class + where TDependentEntity : class + { + public NonGenericTestOwnedNavigationViewBuilder(OwnedNavigationViewBuilder tableBuilder) + { + ViewBuilder = tableBuilder; + } + + private OwnedNavigationViewBuilder ViewBuilder { get; } + + public override string? Name + => ViewBuilder.Name; + + public override string? Schema + => ViewBuilder.Schema; + + OwnedNavigationViewBuilder IInfrastructure.Instance + => ViewBuilder; + + protected virtual TestOwnedNavigationViewBuilder Wrap(OwnedNavigationViewBuilder tableBuilder) + => new NonGenericTestOwnedNavigationViewBuilder(tableBuilder); + + public override TestViewColumnBuilder Property(string propertyName) + => new NonGenericTestViewColumnBuilder(ViewBuilder.Property(propertyName)); + + public override TestViewColumnBuilder Property(Expression> propertyExpression) + => new GenericTestViewColumnBuilder(ViewBuilder.Property(propertyExpression.GetPropertyAccess().Name)); + } + + public abstract class TestSplitViewBuilder + where TEntity : class + { + public abstract string? Name { get; } + + public abstract string? Schema { get; } + + public abstract TestViewColumnBuilder Property(string propertyName); + + public abstract TestViewColumnBuilder Property(Expression> propertyExpression); + } + + public class GenericTestSplitViewBuilder : TestSplitViewBuilder, IInfrastructure> + where TEntity : class + { + public GenericTestSplitViewBuilder(SplitViewBuilder tableBuilder) + { + ViewBuilder = tableBuilder; + } + + private SplitViewBuilder ViewBuilder { get; } + + public override string? Name + => ViewBuilder.Name; + + public override string? Schema + => ViewBuilder.Schema; + + SplitViewBuilder IInfrastructure>.Instance + => ViewBuilder; + + protected virtual TestSplitViewBuilder Wrap(SplitViewBuilder tableBuilder) + => new GenericTestSplitViewBuilder(tableBuilder); + + public override TestViewColumnBuilder Property(string propertyName) + => new NonGenericTestViewColumnBuilder(ViewBuilder.Property(propertyName)); + + public override TestViewColumnBuilder Property(Expression> propertyExpression) + => new GenericTestViewColumnBuilder(ViewBuilder.Property(propertyExpression)); + } + + public class NonGenericTestSplitViewBuilder : TestSplitViewBuilder, IInfrastructure + where TEntity : class + { + public NonGenericTestSplitViewBuilder(SplitViewBuilder tableBuilder) + { + ViewBuilder = tableBuilder; + } + + private SplitViewBuilder ViewBuilder { get; } + + public override string? Name + => ViewBuilder.Name; + + public override string? Schema + => ViewBuilder.Schema; + + SplitViewBuilder IInfrastructure.Instance + => ViewBuilder; + + protected virtual TestSplitViewBuilder Wrap(SplitViewBuilder tableBuilder) + => new NonGenericTestSplitViewBuilder(tableBuilder); + + public override TestViewColumnBuilder Property(string propertyName) + => new NonGenericTestViewColumnBuilder(ViewBuilder.Property(propertyName)); + + public override TestViewColumnBuilder Property(Expression> propertyExpression) + => new NonGenericTestViewColumnBuilder(ViewBuilder.Property(propertyExpression.GetPropertyAccess().Name)); + } + + public abstract class TestOwnedNavigationSplitViewBuilder + where TOwnerEntity : class + where TDependentEntity : class + { + public abstract string? Name { get; } + + public abstract string? Schema { get; } + + public abstract TestViewColumnBuilder Property(string propertyName); + + public abstract TestViewColumnBuilder Property(Expression> propertyExpression); + } + + public class GenericTestOwnedNavigationSplitViewBuilder : + TestOwnedNavigationSplitViewBuilder, + IInfrastructure> + where TOwnerEntity : class + where TDependentEntity : class + { + public GenericTestOwnedNavigationSplitViewBuilder(OwnedNavigationSplitViewBuilder tableBuilder) + { + ViewBuilder = tableBuilder; + } + + private OwnedNavigationSplitViewBuilder ViewBuilder { get; } + + public override string? Name + => ViewBuilder.Name; + + public override string? Schema + => ViewBuilder.Schema; + + OwnedNavigationSplitViewBuilder IInfrastructure>.Instance + => ViewBuilder; + + protected virtual TestOwnedNavigationSplitViewBuilder Wrap( + OwnedNavigationSplitViewBuilder tableBuilder) + => new GenericTestOwnedNavigationSplitViewBuilder(tableBuilder); + + public override TestViewColumnBuilder Property(string propertyName) + => new NonGenericTestViewColumnBuilder(ViewBuilder.Property(propertyName)); + + public override TestViewColumnBuilder Property(Expression> propertyExpression) + => new GenericTestViewColumnBuilder(ViewBuilder.Property(propertyExpression.GetPropertyAccess().Name)); + } + + public class NonGenericTestOwnedNavigationSplitViewBuilder : + TestOwnedNavigationSplitViewBuilder, IInfrastructure + where TOwnerEntity : class + where TDependentEntity : class + { + public NonGenericTestOwnedNavigationSplitViewBuilder(OwnedNavigationSplitViewBuilder tableBuilder) + { + ViewBuilder = tableBuilder; + } + + private OwnedNavigationSplitViewBuilder ViewBuilder { get; } + + public override string? Name + => ViewBuilder.Name; + + public override string? Schema + => ViewBuilder.Schema; + + OwnedNavigationSplitViewBuilder IInfrastructure.Instance + => ViewBuilder; + + protected virtual TestOwnedNavigationSplitViewBuilder Wrap(OwnedNavigationSplitViewBuilder tableBuilder) + => new NonGenericTestOwnedNavigationSplitViewBuilder(tableBuilder); + + public override TestViewColumnBuilder Property(string propertyName) + => new NonGenericTestViewColumnBuilder(ViewBuilder.Property(propertyName)); + + public override TestViewColumnBuilder Property(Expression> propertyExpression) + => new GenericTestViewColumnBuilder(ViewBuilder.Property(propertyExpression.GetPropertyAccess().Name)); + } + + public abstract class TestViewColumnBuilder + { + public abstract TestViewColumnBuilder HasColumnName(string? name); + } + + public class GenericTestViewColumnBuilder : TestViewColumnBuilder, IInfrastructure> + { + public GenericTestViewColumnBuilder(ViewColumnBuilder columnBuilder) + { + ViewColumnBuilder = columnBuilder; + } + + private ViewColumnBuilder ViewColumnBuilder { get; } + + ViewColumnBuilder IInfrastructure>.Instance + => ViewColumnBuilder; + + protected virtual TestViewColumnBuilder Wrap(ViewColumnBuilder columnBuilder) + => new GenericTestViewColumnBuilder(columnBuilder); + + public override TestViewColumnBuilder HasColumnName(string? name) + => Wrap(ViewColumnBuilder.HasColumnName(name)); + } + + public class NonGenericTestViewColumnBuilder : TestViewColumnBuilder, IInfrastructure + { + public NonGenericTestViewColumnBuilder(ViewColumnBuilder tableBuilder) + { + ViewColumnBuilder = tableBuilder; + } + + private ViewColumnBuilder ViewColumnBuilder { get; } + + ViewColumnBuilder IInfrastructure.Instance + => ViewColumnBuilder; + + protected virtual TestViewColumnBuilder Wrap(ViewColumnBuilder tableBuilder) + => new NonGenericTestViewColumnBuilder(tableBuilder); + + public override TestViewColumnBuilder HasColumnName(string? name) + => Wrap(ViewColumnBuilder.HasColumnName(name)); + } + + public abstract class TestTriggerBuilder + { + public abstract TestTriggerBuilder HasName(string name); + public abstract TestTriggerBuilder HasAnnotation(string annotation, object? value); + } + + public class NonGenericTestTriggerBuilder : TestTriggerBuilder, IInfrastructure + { + public NonGenericTestTriggerBuilder(TriggerBuilder triggerBuilder) + { + TriggerBuilder = triggerBuilder; + } + + private TriggerBuilder TriggerBuilder { get; } + + TriggerBuilder IInfrastructure.Instance + => TriggerBuilder; + + protected virtual TestTriggerBuilder Wrap(TriggerBuilder checkConstraintBuilder) + => new NonGenericTestTriggerBuilder(checkConstraintBuilder); + + public override TestTriggerBuilder HasName(string name) + => Wrap(TriggerBuilder.HasName(name)); + + public override TestTriggerBuilder HasAnnotation(string annotation, object? value) + => Wrap(TriggerBuilder.HasAnnotation(annotation, value)); } public abstract class TestCheckConstraintBuilder { public abstract TestCheckConstraintBuilder HasName(string name); + + public abstract TestCheckConstraintBuilder HasAnnotation(string annotation, object? value); } public class NonGenericTestCheckConstraintBuilder : TestCheckConstraintBuilder, IInfrastructure @@ -156,5 +942,8 @@ protected virtual TestCheckConstraintBuilder Wrap(CheckConstraintBuilder checkCo public override TestCheckConstraintBuilder HasName(string name) => Wrap(CheckConstraintBuilder.HasName(name)); + + public override TestCheckConstraintBuilder HasAnnotation(string annotation, object? value) + => Wrap(CheckConstraintBuilder.HasAnnotation(annotation, value)); } } diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs index 58b29e08a5e..093ed314b8d 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs @@ -9,15 +9,15 @@ public static class RelationalTestModelBuilderExtensions { public static ModelBuilderTest.TestPropertyBuilder HasColumnName( this ModelBuilderTest.TestPropertyBuilder builder, - string name) + string? name) { switch (builder) { case IInfrastructure> genericBuilder: genericBuilder.Instance.HasColumnName(name); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.HasColumnName(name); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasColumnName(name); break; } @@ -33,8 +33,8 @@ public static ModelBuilderTest.TestPropertyBuilder HasColumnType> genericBuilder: genericBuilder.Instance.HasColumnType(typeName); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.HasColumnType(typeName); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasColumnType(typeName); break; } @@ -50,8 +50,8 @@ public static ModelBuilderTest.TestPropertyBuilder HasDefaultValueSql case IInfrastructure> genericBuilder: genericBuilder.Instance.HasDefaultValueSql(sql); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.HasDefaultValueSql(sql); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasDefaultValueSql(sql); break; } @@ -67,8 +67,8 @@ public static ModelBuilderTest.TestPropertyBuilder HasComputedColumnS case IInfrastructure> genericBuilder: genericBuilder.Instance.HasComputedColumnSql(sql); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.HasComputedColumnSql(sql); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasComputedColumnSql(sql); break; } @@ -84,8 +84,8 @@ public static ModelBuilderTest.TestPropertyBuilder HasDefaultValue> genericBuilder: genericBuilder.Instance.HasDefaultValue(value); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.HasDefaultValue(value); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasDefaultValue(value); break; } @@ -101,8 +101,8 @@ public static ModelBuilderTest.TestPropertyBuilder IsFixedLength> genericBuilder: genericBuilder.Instance.IsFixedLength(fixedLength); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.IsFixedLength(fixedLength); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.IsFixedLength(fixedLength); break; } @@ -118,8 +118,8 @@ public static ModelBuilderTest.TestEntityTypeBuilder UseTpcMappingStrat case IInfrastructure> genericBuilder: genericBuilder.Instance.UseTpcMappingStrategy(); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.UseTpcMappingStrategy(); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.UseTpcMappingStrategy(); break; } @@ -135,8 +135,8 @@ public static ModelBuilderTest.TestEntityTypeBuilder UseTphMappingStrat case IInfrastructure> genericBuilder: genericBuilder.Instance.UseTphMappingStrategy(); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.UseTphMappingStrategy(); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.UseTphMappingStrategy(); break; } @@ -152,8 +152,8 @@ public static ModelBuilderTest.TestEntityTypeBuilder UseTptMappingStrat case IInfrastructure> genericBuilder: genericBuilder.Instance.UseTptMappingStrategy(); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.UseTptMappingStrategy(); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.UseTptMappingStrategy(); break; } @@ -170,8 +170,8 @@ public static ModelBuilderTest.TestEntityTypeBuilder ToTable( case IInfrastructure> genericBuilder: genericBuilder.Instance.ToTable(name); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.ToTable(name); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToTable(name); break; } @@ -189,8 +189,8 @@ public static ModelBuilderTest.TestEntityTypeBuilder ToTable( case IInfrastructure> genericBuilder: genericBuilder.Instance.ToTable(name, schema); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.ToTable(name, schema); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToTable(name, schema); break; } @@ -207,8 +207,8 @@ public static ModelBuilderTest.TestEntityTypeBuilder ToTable( case IInfrastructure> genericBuilder: genericBuilder.Instance.ToTable(b => buildAction(new RelationalModelBuilderTest.GenericTestTableBuilder(b))); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.ToTable( + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToTable( b => buildAction(new RelationalModelBuilderTest.NonGenericTestTableBuilder(b))); break; } @@ -218,7 +218,7 @@ public static ModelBuilderTest.TestEntityTypeBuilder ToTable( public static ModelBuilderTest.TestEntityTypeBuilder ToTable( this ModelBuilderTest.TestEntityTypeBuilder builder, - string? name, + string name, Action> buildAction) where TEntity : class { @@ -229,8 +229,8 @@ public static ModelBuilderTest.TestEntityTypeBuilder ToTable( name, b => buildAction(new RelationalModelBuilderTest.GenericTestTableBuilder(b))); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.ToTable( + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToTable( name, b => buildAction(new RelationalModelBuilderTest.NonGenericTestTableBuilder(b))); break; @@ -253,8 +253,8 @@ public static ModelBuilderTest.TestEntityTypeBuilder ToTable( name, schema, b => buildAction(new RelationalModelBuilderTest.GenericTestTableBuilder(b))); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.ToTable( + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToTable( name, schema, b => buildAction(new RelationalModelBuilderTest.NonGenericTestTableBuilder(b))); break; @@ -263,109 +263,215 @@ public static ModelBuilderTest.TestEntityTypeBuilder ToTable( return builder; } - public static ModelBuilderTest.TestOwnedNavigationBuilder ToTable( - this ModelBuilderTest.TestOwnedNavigationBuilder builder, + public static ModelBuilderTest.TestOwnedNavigationBuilder ToTable( + this ModelBuilderTest.TestOwnedNavigationBuilder builder, string? name) where TOwnerEntity : class - where TRelatedEntity : class + where TDependentEntity : class { switch (builder) { - case IInfrastructure> genericBuilder: + case IInfrastructure> genericBuilder: genericBuilder.Instance.ToTable(name); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.ToTable(name); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToTable(name); break; } return builder; } - public static ModelBuilderTest.TestOwnedNavigationBuilder ToTable( - this ModelBuilderTest.TestOwnedNavigationBuilder builder, + public static ModelBuilderTest.TestOwnedNavigationBuilder ToTable( + this ModelBuilderTest.TestOwnedNavigationBuilder builder, string name, string? schema) where TOwnerEntity : class - where TRelatedEntity : class + where TDependentEntity : class { switch (builder) { - case IInfrastructure> genericBuilder: + case IInfrastructure> genericBuilder: genericBuilder.Instance.ToTable(name, schema); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.ToTable(name, schema); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToTable(name, schema); break; } return builder; } - public static ModelBuilderTest.TestOwnedNavigationBuilder ToTable( - this ModelBuilderTest.TestOwnedNavigationBuilder builder, - Action> buildAction) + public static ModelBuilderTest.TestOwnedNavigationBuilder ToTable( + this ModelBuilderTest.TestOwnedNavigationBuilder builder, + Action> buildAction) where TOwnerEntity : class - where TRelatedEntity : class + where TDependentEntity : class { switch (builder) { - case IInfrastructure> genericBuilder: + case IInfrastructure> genericBuilder: genericBuilder.Instance.ToTable( - b => buildAction(new RelationalModelBuilderTest.GenericTestOwnedNavigationTableBuilder(b))); + b => buildAction( + new RelationalModelBuilderTest.GenericTestOwnedNavigationTableBuilder(b))); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.ToTable( - b => buildAction(new RelationalModelBuilderTest.NonGenericTestOwnedNavigationTableBuilder(b))); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToTable( + b => buildAction( + new RelationalModelBuilderTest.NonGenericTestOwnedNavigationTableBuilder(b))); break; } return builder; } - public static ModelBuilderTest.TestOwnedNavigationBuilder ToTable( - this ModelBuilderTest.TestOwnedNavigationBuilder builder, - string? name, - Action> buildAction) + public static ModelBuilderTest.TestOwnedNavigationBuilder ToTable( + this ModelBuilderTest.TestOwnedNavigationBuilder builder, + string name, + Action> buildAction) where TOwnerEntity : class - where TRelatedEntity : class + where TDependentEntity : class { switch (builder) { - case IInfrastructure> genericBuilder: + case IInfrastructure> genericBuilder: genericBuilder.Instance.ToTable( name, - b => buildAction(new RelationalModelBuilderTest.GenericTestOwnedNavigationTableBuilder(b))); + b => buildAction( + new RelationalModelBuilderTest.GenericTestOwnedNavigationTableBuilder(b))); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.ToTable( + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToTable( name, - b => buildAction(new RelationalModelBuilderTest.NonGenericTestOwnedNavigationTableBuilder(b))); + b => buildAction( + new RelationalModelBuilderTest.NonGenericTestOwnedNavigationTableBuilder(b))); break; } return builder; } - public static ModelBuilderTest.TestOwnedNavigationBuilder ToTable( - this ModelBuilderTest.TestOwnedNavigationBuilder builder, + public static ModelBuilderTest.TestOwnedNavigationBuilder ToTable( + this ModelBuilderTest.TestOwnedNavigationBuilder builder, string name, string? schema, - Action> buildAction) + Action> buildAction) where TOwnerEntity : class - where TRelatedEntity : class + where TDependentEntity : class { switch (builder) { - case IInfrastructure> genericBuilder: + case IInfrastructure> genericBuilder: genericBuilder.Instance.ToTable( name, schema, - b => buildAction(new RelationalModelBuilderTest.GenericTestOwnedNavigationTableBuilder(b))); + b => buildAction( + new RelationalModelBuilderTest.GenericTestOwnedNavigationTableBuilder(b))); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToTable( + name, schema, + b => buildAction( + new RelationalModelBuilderTest.NonGenericTestOwnedNavigationTableBuilder(b))); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestEntityTypeBuilder SplitToTable( + this ModelBuilderTest.TestEntityTypeBuilder builder, + string name, + Action> buildAction) + where TEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.SplitToTable( + name, + b => buildAction(new RelationalModelBuilderTest.GenericTestSplitTableBuilder(b))); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.SplitToTable( + name, + b => buildAction(new RelationalModelBuilderTest.NonGenericTestSplitTableBuilder(b))); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestEntityTypeBuilder SplitToTable( + this ModelBuilderTest.TestEntityTypeBuilder builder, + string name, + string? schema, + Action> buildAction) + where TEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.SplitToTable( + name, schema, + b => buildAction(new RelationalModelBuilderTest.GenericTestSplitTableBuilder(b))); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.SplitToTable( + name, schema, + b => buildAction(new RelationalModelBuilderTest.NonGenericTestSplitTableBuilder(b))); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestOwnedNavigationBuilder SplitToTable( + this ModelBuilderTest.TestOwnedNavigationBuilder builder, + string name, + Action> buildAction) + where TOwnerEntity : class + where TDependentEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.SplitToTable( + name, + b => buildAction( + new RelationalModelBuilderTest.GenericTestOwnedNavigationSplitTableBuilder(b))); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.SplitToTable( + name, + b => buildAction( + new RelationalModelBuilderTest.NonGenericTestOwnedNavigationSplitTableBuilder(b))); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestOwnedNavigationBuilder SplitToTable( + this ModelBuilderTest.TestOwnedNavigationBuilder builder, + string name, + string? schema, + Action> buildAction) + where TOwnerEntity : class + where TDependentEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.SplitToTable( + name, schema, + b => buildAction( + new RelationalModelBuilderTest.GenericTestOwnedNavigationSplitTableBuilder(b))); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.ToTable( + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.SplitToTable( name, schema, - b => buildAction(new RelationalModelBuilderTest.NonGenericTestOwnedNavigationTableBuilder(b))); + b => buildAction( + new RelationalModelBuilderTest.NonGenericTestOwnedNavigationSplitTableBuilder(b))); break; } @@ -374,16 +480,16 @@ public static ModelBuilderTest.TestOwnedNavigationBuilder ToView( this ModelBuilderTest.TestEntityTypeBuilder builder, - string? schema) + string? name) where TEntity : class { switch (builder) { case IInfrastructure> genericBuilder: - genericBuilder.Instance.ToView(schema); + genericBuilder.Instance.ToView(name); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.ToView(schema); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToView(name); break; } @@ -401,53 +507,241 @@ public static ModelBuilderTest.TestEntityTypeBuilder ToView( case IInfrastructure> genericBuilder: genericBuilder.Instance.ToView(name, schema); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.ToView(name, schema); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToView(name, schema); break; } return builder; } - public static ModelBuilderTest.TestOwnedNavigationBuilder ToView( - this ModelBuilderTest.TestOwnedNavigationBuilder builder, - string? schema) + public static ModelBuilderTest.TestEntityTypeBuilder ToView( + this ModelBuilderTest.TestEntityTypeBuilder builder, + string name, + Action> buildAction) + where TEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.ToView(name, + b => buildAction( + new RelationalModelBuilderTest.GenericTestViewBuilder(b))); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToView(name, + b => buildAction( + new RelationalModelBuilderTest.NonGenericTestViewBuilder(b))); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestEntityTypeBuilder ToView( + this ModelBuilderTest.TestEntityTypeBuilder builder, + string name, + string? schema, + Action> buildAction) + where TEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.ToView(name, schema, + b => buildAction(new RelationalModelBuilderTest.GenericTestViewBuilder(b))); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToView(name, schema, + b => buildAction(new RelationalModelBuilderTest.NonGenericTestViewBuilder(b))); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestOwnedNavigationBuilder ToView( + this ModelBuilderTest.TestOwnedNavigationBuilder builder, + string? name) where TOwnerEntity : class - where TRelatedEntity : class + where TDependentEntity : class { switch (builder) { - case IInfrastructure> genericBuilder: - genericBuilder.Instance.ToView(schema); + case IInfrastructure> genericBuilder: + genericBuilder.Instance.ToView(name); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.ToView(schema); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToView(name); break; } return builder; } - public static ModelBuilderTest.TestOwnedNavigationBuilder ToView( - this ModelBuilderTest.TestOwnedNavigationBuilder builder, + public static ModelBuilderTest.TestOwnedNavigationBuilder ToView( + this ModelBuilderTest.TestOwnedNavigationBuilder builder, string? name, string? schema) where TOwnerEntity : class - where TRelatedEntity : class + where TDependentEntity : class { switch (builder) { - case IInfrastructure> genericBuilder: + case IInfrastructure> genericBuilder: genericBuilder.Instance.ToView(name, schema); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.ToView(name, schema); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToView(name, schema); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestOwnedNavigationBuilder ToView( + this ModelBuilderTest.TestOwnedNavigationBuilder builder, + string name, + Action> buildAction) + where TOwnerEntity : class + where TDependentEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.ToView(name, + b => buildAction( + new RelationalModelBuilderTest.GenericTestOwnedNavigationViewBuilder(b))); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToView(name, + b => buildAction( + new RelationalModelBuilderTest.NonGenericTestOwnedNavigationViewBuilder(b))); break; } return builder; } - + + public static ModelBuilderTest.TestOwnedNavigationBuilder ToView( + this ModelBuilderTest.TestOwnedNavigationBuilder builder, + string name, + string? schema, + Action> buildAction) + where TOwnerEntity : class + where TDependentEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.ToView(name, schema, + b => buildAction( + new RelationalModelBuilderTest.GenericTestOwnedNavigationViewBuilder(b))); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.ToView(name, schema, + b => buildAction( + new RelationalModelBuilderTest.NonGenericTestOwnedNavigationViewBuilder(b))); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestEntityTypeBuilder SplitToView( + this ModelBuilderTest.TestEntityTypeBuilder builder, + string name, + Action> buildAction) + where TEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.SplitToView(name, + b => buildAction( + new RelationalModelBuilderTest.GenericTestSplitViewBuilder(b))); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.SplitToView(name, + b => buildAction( + new RelationalModelBuilderTest.NonGenericTestSplitViewBuilder(b))); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestEntityTypeBuilder SplitToView( + this ModelBuilderTest.TestEntityTypeBuilder builder, + string name, + string? schema, + Action> buildAction) + where TEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.SplitToView(name, schema, + b => buildAction(new RelationalModelBuilderTest.GenericTestSplitViewBuilder(b))); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.SplitToView(name, schema, + b => buildAction(new RelationalModelBuilderTest.NonGenericTestSplitViewBuilder(b))); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestOwnedNavigationBuilder SplitToView( + this ModelBuilderTest.TestOwnedNavigationBuilder builder, + string name, + Action> buildAction) + where TOwnerEntity : class + where TDependentEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.SplitToView(name, + b => buildAction( + new RelationalModelBuilderTest.GenericTestOwnedNavigationSplitViewBuilder(b))); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.SplitToView(name, + b => buildAction( + new RelationalModelBuilderTest.NonGenericTestOwnedNavigationSplitViewBuilder(b))); + break; + } + + return builder; + } + + public static ModelBuilderTest.TestOwnedNavigationBuilder SplitToView( + this ModelBuilderTest.TestOwnedNavigationBuilder builder, + string name, + string? schema, + Action> buildAction) + where TOwnerEntity : class + where TDependentEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.SplitToView(name, schema, + b => buildAction( + new RelationalModelBuilderTest.GenericTestOwnedNavigationSplitViewBuilder(b))); + break; + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.SplitToView(name, schema, + b => buildAction( + new RelationalModelBuilderTest.NonGenericTestOwnedNavigationSplitViewBuilder(b))); + break; + } + + return builder; + } + public static ModelBuilderTest.TestEntityTypeBuilder HasCheckConstraint( this ModelBuilderTest.TestEntityTypeBuilder builder, string name, @@ -459,8 +753,8 @@ public static ModelBuilderTest.TestEntityTypeBuilder HasCheckConstraint case IInfrastructure> genericBuilder: genericBuilder.Instance.HasCheckConstraint(name, sql); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.HasCheckConstraint(name, sql); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasCheckConstraint(name, sql); break; } @@ -481,8 +775,8 @@ public static ModelBuilderTest.TestEntityTypeBuilder HasCheckConstraint name, sql, b => buildAction(new RelationalModelBuilderTest.NonGenericTestCheckConstraintBuilder(b))); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.HasCheckConstraint( + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasCheckConstraint( name, sql, b => buildAction(new RelationalModelBuilderTest.NonGenericTestCheckConstraintBuilder(b))); break; @@ -491,45 +785,45 @@ public static ModelBuilderTest.TestEntityTypeBuilder HasCheckConstraint return builder; } - public static ModelBuilderTest.TestOwnedNavigationBuilder HasCheckConstraint( - this ModelBuilderTest.TestOwnedNavigationBuilder builder, + public static ModelBuilderTest.TestOwnedNavigationBuilder HasCheckConstraint + ( + this ModelBuilderTest.TestOwnedNavigationBuilder builder, string name, string? sql) where TOwnerEntity : class - where TRelatedEntity : class + where TDependentEntity : class { switch (builder) { - case IInfrastructure> genericBuilder: + case IInfrastructure> genericBuilder: genericBuilder.Instance.HasCheckConstraint(name, sql); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.HasCheckConstraint(name, sql); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasCheckConstraint(name, sql); break; } return builder; } - public static ModelBuilderTest.TestOwnedNavigationBuilder HasCheckConstraint( - this ModelBuilderTest.TestOwnedNavigationBuilder builder, + public static ModelBuilderTest.TestOwnedNavigationBuilder HasCheckConstraint + ( + this ModelBuilderTest.TestOwnedNavigationBuilder builder, string name, string sql, Action buildAction) where TOwnerEntity : class - where TRelatedEntity : class + where TDependentEntity : class { switch (builder) { - case IInfrastructure> genericBuilder: + case IInfrastructure> genericBuilder: genericBuilder.Instance.HasCheckConstraint( name, sql, b => buildAction(new RelationalModelBuilderTest.NonGenericTestCheckConstraintBuilder(b))); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.HasCheckConstraint( + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasCheckConstraint( name, sql, b => buildAction(new RelationalModelBuilderTest.NonGenericTestCheckConstraintBuilder(b))); break; @@ -538,59 +832,59 @@ public static ModelBuilderTest.TestEntityTypeBuilder HasCheckConstraint return builder; } - public static ModelBuilderTest.TestOwnershipBuilder HasConstraintName( - this ModelBuilderTest.TestOwnershipBuilder builder, + public static ModelBuilderTest.TestOwnershipBuilder HasConstraintName( + this ModelBuilderTest.TestOwnershipBuilder builder, string name) where TOwnerEntity : class - where TRelatedEntity : class + where TDependentEntity : class { switch (builder) { - case IInfrastructure> genericBuilder: + case IInfrastructure> genericBuilder: genericBuilder.Instance.HasConstraintName(name); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.HasConstraintName(name); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasConstraintName(name); break; } return builder; } - public static ModelBuilderTest.TestReferenceReferenceBuilder HasConstraintName( - this ModelBuilderTest.TestReferenceReferenceBuilder builder, + public static ModelBuilderTest.TestReferenceReferenceBuilder HasConstraintName + ( + this ModelBuilderTest.TestReferenceReferenceBuilder builder, string name) where TOwnerEntity : class - where TRelatedEntity : class + where TDependentEntity : class { switch (builder) { - case IInfrastructure> genericBuilder: + case IInfrastructure> genericBuilder: genericBuilder.Instance.HasConstraintName(name); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.HasConstraintName(name); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasConstraintName(name); break; } return builder; } - public static ModelBuilderTest.TestReferenceCollectionBuilder HasConstraintName( - this ModelBuilderTest.TestReferenceCollectionBuilder builder, + public static ModelBuilderTest.TestReferenceCollectionBuilder HasConstraintName + ( + this ModelBuilderTest.TestReferenceCollectionBuilder builder, string name) where TOwnerEntity : class - where TRelatedEntity : class + where TDependentEntity : class { switch (builder) { - case IInfrastructure> genericBuilder: + case IInfrastructure> genericBuilder: genericBuilder.Instance.HasConstraintName(name); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.HasConstraintName(name); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasConstraintName(name); break; } @@ -599,15 +893,15 @@ public static ModelBuilderTest.TestOwnershipBuilder HasFilter( this ModelBuilderTest.TestIndexBuilder builder, - string filterExpression) + string? filterExpression) { switch (builder) { case IInfrastructure> genericBuilder: genericBuilder.Instance.HasFilter(filterExpression); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.HasFilter(filterExpression); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasFilter(filterExpression); break; } @@ -623,8 +917,8 @@ public static ModelBuilderTest.TestIndexBuilder HasName( case IInfrastructure> genericBuilder: genericBuilder.Instance.HasName(name); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.HasName(name); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasName(name); break; } @@ -640,8 +934,8 @@ public static ModelBuilderTest.TestKeyBuilder HasName( case IInfrastructure> genericBuilder: genericBuilder.Instance.HasName(name); break; - case IInfrastructure nongenericBuilder: - nongenericBuilder.Instance.HasName(name); + case IInfrastructure nonGenericBuilder: + nonGenericBuilder.Instance.HasName(name); break; } diff --git a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs index 63763e91ff7..dcfad442eb5 100644 --- a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs +++ b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs @@ -39,34 +39,53 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase => new() { { - typeof(IReadOnlyDbFunction), (typeof(IMutableDbFunction), + typeof(IReadOnlyDbFunction), + (typeof(IMutableDbFunction), typeof(IConventionDbFunction), typeof(IConventionDbFunctionBuilder), typeof(IDbFunction)) }, { - typeof(IReadOnlyDbFunctionParameter), (typeof(IMutableDbFunctionParameter), + typeof(IReadOnlyDbFunctionParameter), + (typeof(IMutableDbFunctionParameter), typeof(IConventionDbFunctionParameter), typeof(IConventionDbFunctionParameterBuilder), typeof(IDbFunctionParameter)) }, { - typeof(IReadOnlySequence), (typeof(IMutableSequence), + typeof(IReadOnlySequence), + (typeof(IMutableSequence), typeof(IConventionSequence), typeof(IConventionSequenceBuilder), typeof(ISequence)) }, { - typeof(IReadOnlyCheckConstraint), (typeof(IMutableCheckConstraint), + typeof(IReadOnlyCheckConstraint), + (typeof(IMutableCheckConstraint), typeof(IConventionCheckConstraint), typeof(IConventionCheckConstraintBuilder), typeof(ICheckConstraint)) }, { - typeof(IReadOnlyTrigger), (typeof(IMutableTrigger), + typeof(IReadOnlyTrigger), + (typeof(IMutableTrigger), typeof(IConventionTrigger), typeof(IConventionTriggerBuilder), typeof(ITrigger)) + }, + { + typeof(IReadOnlyEntityTypeMappingFragment), + (typeof(IMutableEntityTypeMappingFragment), + typeof(IConventionEntityTypeMappingFragment), + null, + typeof(IEntityTypeMappingFragment)) + }, + { + typeof(IReadOnlyRelationalPropertyOverrides), + (typeof(IMutableRelationalPropertyOverrides), + typeof(IConventionRelationalPropertyOverrides), + null, + typeof(IRelationalPropertyOverrides)) } }; @@ -88,7 +107,9 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase typeof(ITableIndex), typeof(IForeignKeyConstraint), typeof(IUniqueConstraint), - typeof(ITrigger) + typeof(ITrigger), + typeof(IEntityTypeMappingFragment), + typeof(IRelationalPropertyOverrides) }; public override HashSet FluentApiTypes { get; } = new() @@ -103,6 +124,28 @@ public class RelationalApiConsistencyFixture : ApiConsistencyFixtureBase typeof(DbFunctionParameterBuilder), typeof(TableBuilder), typeof(TableBuilder<>), + typeof(OwnedNavigationTableBuilder), + typeof(OwnedNavigationTableBuilder<,>), + typeof(SplitTableBuilder), + typeof(SplitTableBuilder<>), + typeof(OwnedNavigationSplitTableBuilder), + typeof(OwnedNavigationSplitTableBuilder<,>), + typeof(ViewBuilder), + typeof(ViewBuilder<>), + typeof(OwnedNavigationViewBuilder), + typeof(OwnedNavigationViewBuilder<,>), + typeof(SplitViewBuilder), + typeof(SplitViewBuilder<>), + typeof(OwnedNavigationSplitViewBuilder), + typeof(OwnedNavigationSplitViewBuilder<,>), + typeof(TableValuedFunctionBuilder), + typeof(TableValuedFunctionBuilder<>), + typeof(OwnedNavigationTableValuedFunctionBuilder), + typeof(OwnedNavigationTableValuedFunctionBuilder<,>), + typeof(ColumnBuilder), + typeof(ColumnBuilder<>), + typeof(ViewColumnBuilder), + typeof(ViewColumnBuilder<>), typeof(SequenceBuilder), typeof(MigrationBuilder), typeof(AlterOperationBuilder<>), @@ -184,33 +227,18 @@ public override { typeof(RelationalEntityTypeBuilderExtensions).GetMethod( nameof(RelationalEntityTypeBuilderExtensions.ExcludeTableFromMigrations)), - typeof(RelationalEntityTypeBuilderExtensions).GetMethod( - nameof(RelationalEntityTypeBuilderExtensions.CanSetFunction), - new[] { typeof(IConventionEntityTypeBuilder), typeof(MethodInfo), typeof(bool) }), - typeof(RelationalEntityTypeBuilderExtensions).GetMethod( - nameof(RelationalEntityTypeBuilderExtensions.ToFunction), - new[] { typeof(IConventionEntityTypeBuilder), typeof(string), typeof(bool) }), - typeof(RelationalEntityTypeBuilderExtensions).GetMethod( - nameof(RelationalEntityTypeBuilderExtensions.ToTable), - new[] { typeof(EntityTypeBuilder), typeof(Action) }), - typeof(RelationalEntityTypeBuilderExtensions).GetMethod( - nameof(RelationalEntityTypeBuilderExtensions.ToTable), - new[] { typeof(EntityTypeBuilder), typeof(string), typeof(Action) }), - typeof(RelationalEntityTypeBuilderExtensions).GetMethod( - nameof(RelationalEntityTypeBuilderExtensions.ToTable), - new[] { typeof(EntityTypeBuilder), typeof(string), typeof(string), typeof(Action) }), - typeof(RelationalEntityTypeBuilderExtensions).GetMethod( - nameof(RelationalEntityTypeBuilderExtensions.ToTable), - new[] { typeof(OwnedNavigationBuilder), typeof(Action) }), - typeof(RelationalEntityTypeBuilderExtensions).GetMethod( - nameof(RelationalEntityTypeBuilderExtensions.ToTable), - new[] { typeof(OwnedNavigationBuilder), typeof(string), typeof(Action) }), - typeof(RelationalEntityTypeBuilderExtensions).GetMethod( - nameof(RelationalEntityTypeBuilderExtensions.ToTable), - new[] { typeof(OwnedNavigationBuilder), typeof(string), typeof(string), typeof(Action) }), typeof(RelationalIndexBuilderExtensions).GetMethod( nameof(RelationalIndexBuilderExtensions.HasName), - new[] { typeof(IndexBuilder), typeof(string) }) + new[] { typeof(IndexBuilder), typeof(string) }), + typeof(RelationalEntityTypeExtensions).GetMethod( + nameof(RelationalEntityTypeExtensions.GetMappingFragments), + new[] { typeof(IReadOnlyEntityType) }), + typeof(RelationalPropertyExtensions).GetMethod( + nameof(RelationalPropertyExtensions.FindOverrides), + new[] { typeof(IReadOnlyProperty), typeof(StoreObjectIdentifier).MakeByRefType() }), + typeof(RelationalPropertyExtensions).GetMethod( + nameof(RelationalPropertyExtensions.GetOverrides), + new[] { typeof(IReadOnlyProperty) }) }; public override HashSet AsyncMethodExceptions { get; } = new() @@ -261,6 +289,28 @@ protected override void Initialize() } GenericFluentApiTypes.Add(typeof(TableBuilder), typeof(TableBuilder<>)); + GenericFluentApiTypes.Add(typeof(OwnedNavigationTableBuilder), typeof(OwnedNavigationTableBuilder<,>)); + GenericFluentApiTypes.Add(typeof(SplitTableBuilder), typeof(SplitTableBuilder<>)); + GenericFluentApiTypes.Add(typeof(OwnedNavigationSplitTableBuilder), typeof(OwnedNavigationSplitTableBuilder<,>)); + GenericFluentApiTypes.Add(typeof(ViewBuilder), typeof(ViewBuilder<>)); + GenericFluentApiTypes.Add(typeof(OwnedNavigationViewBuilder), typeof(OwnedNavigationViewBuilder<,>)); + GenericFluentApiTypes.Add(typeof(SplitViewBuilder), typeof(SplitViewBuilder<>)); + GenericFluentApiTypes.Add(typeof(OwnedNavigationSplitViewBuilder), typeof(OwnedNavigationSplitViewBuilder<,>)); + GenericFluentApiTypes.Add(typeof(TableValuedFunctionBuilder), typeof(TableValuedFunctionBuilder<>)); + GenericFluentApiTypes.Add(typeof(OwnedNavigationTableValuedFunctionBuilder), typeof(OwnedNavigationTableValuedFunctionBuilder<,>)); + GenericFluentApiTypes.Add(typeof(ColumnBuilder), typeof(ColumnBuilder<>)); + GenericFluentApiTypes.Add(typeof(ViewColumnBuilder), typeof(ViewColumnBuilder<>)); + + MirrorTypes.Add(typeof(TableBuilder), typeof(OwnedNavigationTableBuilder)); + MirrorTypes.Add(typeof(TableBuilder<>), typeof(OwnedNavigationTableBuilder<,>)); + MirrorTypes.Add(typeof(SplitTableBuilder), typeof(OwnedNavigationSplitTableBuilder)); + MirrorTypes.Add(typeof(SplitTableBuilder<>), typeof(OwnedNavigationSplitTableBuilder<,>)); + MirrorTypes.Add(typeof(ViewBuilder), typeof(OwnedNavigationViewBuilder)); + MirrorTypes.Add(typeof(ViewBuilder<>), typeof(OwnedNavigationViewBuilder<,>)); + MirrorTypes.Add(typeof(SplitViewBuilder), typeof(OwnedNavigationSplitViewBuilder)); + MirrorTypes.Add(typeof(SplitViewBuilder<>), typeof(OwnedNavigationSplitViewBuilder<,>)); + MirrorTypes.Add(typeof(TableValuedFunctionBuilder), typeof(OwnedNavigationTableValuedFunctionBuilder)); + MirrorTypes.Add(typeof(TableValuedFunctionBuilder<>), typeof(OwnedNavigationTableValuedFunctionBuilder<,>)); base.Initialize(); } diff --git a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs index f337f3f893a..1f9521f13db 100644 --- a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs +++ b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs @@ -55,70 +55,68 @@ public void Generic_fluent_api_methods_should_return_generic_types() var nonGenericMethods = new List<(Type Type, MethodInfo Method)>(); foreach (var type in GetAllTypes(Fixture.FluentApiTypes)) { - if (!type.IsVisible - || !type.IsGenericType - || type.BaseType == typeof(object) - || type.BaseType.IsGenericType) + if (!type.IsVisible) { continue; } - - foreach (var method in type.GetMethods(PublicInstance)) + + if (type.IsGenericType + && type.BaseType != typeof(object) + && !type.BaseType.IsGenericType) { - if (method.ReturnType == type.BaseType - && !Fixture.UnmatchedMetadataMethods.Contains(method)) + foreach (var method in type.BaseType.GetMethods(PublicInstance)) { - var hidingMethod = type.GetMethod( - method.Name, - method.GetGenericArguments().Length, - PublicInstance | BindingFlags.DeclaredOnly, - null, - method.GetParameters().Select(p => p.ParameterType).ToArray(), - null); - if (hidingMethod == null || hidingMethod == method || hidingMethod.ReturnType != type) + if (method.ReturnType == type.BaseType + && !Fixture.UnmatchedMetadataMethods.Contains(method)) { - nonGenericMethods.Add((type, method)); + var parameters = method.GetParameters().Select(p => GetEquivalentGenericType(p.ParameterType, type.GetGenericArguments())).ToArray(); + var hidingMethod = type.GetMethod( + method.Name, + method.GetGenericArguments().Length, + PublicInstance | BindingFlags.DeclaredOnly, + null, + parameters, + null); + if (hidingMethod == null || hidingMethod.ReturnType != type) + { + nonGenericMethods.Add((type.BaseType, method)); + } } } } - } - - foreach (var type in GetAllTypes(Fixture.FluentApiTypes)) - { - if (!type.IsVisible) + + if (!type.IsGenericType + && type.BaseType == typeof(object)) { - continue; - } - - // Look for extension methods - foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)) - { - if (method.ReturnType != (method.GetParameters().FirstOrDefault()?.ParameterType) - || !Fixture.GenericFluentApiTypes.TryGetValue(method.ReturnType, out var genericType) - || Fixture.UnmatchedMetadataMethods.Contains(method)) + // Look for extension methods + foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)) { - continue; - } - - var methodFound = false; - foreach (var hidingMethod in type.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)) - { - if (method.Name != hidingMethod.Name - || hidingMethod.GetGenericArguments().Length != genericType.GetGenericArguments().Length - || hidingMethod.ReturnType.GetGenericTypeDefinition() != genericType - || !hidingMethod.GetParameters().Skip(1).Select(p => p.ParameterType).SequenceEqual( - method.GetParameters().Skip(1).Select(p => p.ParameterType))) + if (method.ReturnType != (method.GetParameters().FirstOrDefault()?.ParameterType) + || !Fixture.GenericFluentApiTypes.TryGetValue(method.ReturnType, out var genericType) + || Fixture.UnmatchedMetadataMethods.Contains(method)) { continue; } - methodFound = true; - break; - } + var methodFound = false; + foreach (var hidingMethod in type.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly)) + { + if (method.Name != hidingMethod.Name + || hidingMethod.GetGenericArguments().Length != genericType.GetGenericArguments().Length + || hidingMethod.ReturnType.GetGenericTypeDefinition() != genericType + || !hidingMethod.GetParameters().Skip(1).Select(p => p.ParameterType) + .SequenceEqual(method.GetParameters().Skip(1).Select(p => GetEquivalentGenericType(p.ParameterType, hidingMethod.GetGenericArguments())))) + { + continue; + } + methodFound = true; + break; + } - if (!methodFound) - { - nonGenericMethods.Add((type, method)); + if (!methodFound) + { + nonGenericMethods.Add((type, method)); + } } } } @@ -128,8 +126,76 @@ public void Generic_fluent_api_methods_should_return_generic_types() "\r\n-- Non-generic fluent returns that aren't hidden --\r\n" + string.Join( Environment.NewLine, nonGenericMethods.Select( - m => - $"{m.Method.ReturnType.ShortDisplayName()} {m.Type.Name}.{m.Method.Name}({Format(m.Method.GetParameters())})"))); + m => $"{m.Method.ReturnType.ShortDisplayName()} {m.Type.Name}.{m.Method.Name}({Format(m.Method.GetParameters())})"))); + } + + protected Type GetEquivalentGenericType(Type parameterType, Type[] genericArguments) + { + if (parameterType.IsGenericType + && parameterType.GetGenericTypeDefinition() == typeof(Action<>) + && Fixture.GenericFluentApiTypes.TryGetValue(parameterType.GetGenericArguments()[0], out var genericBuilder) + && genericBuilder.GetGenericArguments().Length == genericArguments.Length) + { + return typeof(Action<>).MakeGenericType(genericBuilder.MakeGenericType(genericArguments)); + } + + return parameterType; + } + + + [ConditionalFact] + public void Builders_have_matching_methods() + { + foreach (var tuple in Fixture.MirrorTypes) + { + var unmatchedMethods = new List<(Type Type, MethodInfo Method)>(); + var wrongReturnMethods = new List<(Type Type, MethodInfo Method)>(); + + foreach (var method in tuple.Key.GetMethods(PublicInstance | BindingFlags.DeclaredOnly)) + { + if (!Fixture.UnmatchedMetadataMethods.Contains(method)) + { + MethodInfo hidingMethod = null; + foreach (var targetMethod in tuple.Value.GetMethods(PublicInstance | BindingFlags.DeclaredOnly)) + { + if (targetMethod.Name == method.Name + && targetMethod.GetGenericArguments().Length == method.GetGenericArguments().Length + && method.GetParameters().Select(p => p.ParameterType) + .SequenceEqual(targetMethod.GetParameters().Select(p => p.ParameterType), + new ParameterTypeEqualityComparer(method, targetMethod, this))) + { + Check.DebugAssert(hidingMethod == null, + "There should only be one method with the expected signature. Found: " + Environment.NewLine + + Format(hidingMethod ?? targetMethod, tuple.Value) + Environment.NewLine + + Format(targetMethod, tuple.Value)); + + hidingMethod = targetMethod; + continue; + } + } + + if (hidingMethod == null) + { + unmatchedMethods.Add((tuple.Key, method)); + } + else if (method.ReturnType == tuple.Key + && hidingMethod.ReturnType != tuple.Value) + { + wrongReturnMethods.Add((tuple.Value, method)); + } + } + } + + Assert.False( + unmatchedMethods.Count > 0, + $"\r\n-- Missing equivalent methods on {tuple.Value.DisplayName()} --\r\n" + + string.Join(Environment.NewLine, unmatchedMethods.Select(m => Format(m.Method, m.Type)))); + + Assert.False( + wrongReturnMethods.Count > 0, + $"\r\n-- Expected these methods to return {tuple.Value.DisplayName()} --\r\n" + + string.Join(Environment.NewLine, unmatchedMethods.Select(m => Format(m.Method, m.Type)))); + } } [ConditionalFact] @@ -884,9 +950,75 @@ protected virtual IEnumerable GetAllTypes(IEnumerable types) } } - private static string Format(ParameterInfo[] parameters) + protected static string Format(ParameterInfo[] parameters) => string.Join(", ", parameters.Select(p => p.ParameterType.Name)); + protected static string Format(MethodInfo method, Type type) + => $"{method.ReturnType.ShortDisplayName()} {type.Name}.{method.Name}({Format(method.GetParameters())})"; + + protected class ParameterTypeEqualityComparer : IEqualityComparer + { + private readonly MethodInfo _sourceMethod; + private readonly MethodInfo _targetMethod; + private readonly ApiConsistencyTestBase _tests; + + public ParameterTypeEqualityComparer( + MethodInfo sourceMethod, + MethodInfo targetMethod, + ApiConsistencyTestBase tests) + { + _sourceMethod = sourceMethod; + _targetMethod = targetMethod; + _tests = tests; + } + + public bool Equals(Type sourceParameterType, Type targetParameterType) + { + if (sourceParameterType == targetParameterType) + { + return true; + } + + var sourceType = _sourceMethod.DeclaringType; + var targetType = _targetMethod.DeclaringType; + if (_targetMethod.DeclaringType.IsGenericType + && sourceParameterType == _tests.GetEquivalentGenericType( + sourceParameterType, _targetMethod.DeclaringType.GetGenericArguments())) + { + return true; + } + + if (sourceType.IsGenericType + && targetType.IsGenericType + && sourceParameterType.IsGenericType + && sourceParameterType.GetGenericTypeDefinition() == typeof(Expression<>) + && targetParameterType.IsGenericType + && targetParameterType.GetGenericTypeDefinition() == typeof(Expression<>)) + { + var sourceExpressionType = sourceParameterType.GetGenericArguments()[0]; + var targetExpressionType = targetParameterType.GetGenericArguments()[0]; + if (sourceExpressionType.IsGenericType + && sourceExpressionType.GetGenericTypeDefinition() == typeof(Func<,>) + && targetExpressionType.IsGenericType + && targetExpressionType.GetGenericTypeDefinition() == typeof(Func<,>)) + { + var sourceFuncParameterType = sourceExpressionType.GetGenericArguments()[0]; + var targetFuncParameterType = targetExpressionType.GetGenericArguments()[0]; + if (sourceFuncParameterType == sourceType.GetGenericArguments()[^1] + && targetFuncParameterType == targetType.GetGenericArguments()[^1]) + { + return true; + } + } + } + + return false; + } + + public int GetHashCode(Type obj) + => obj.GetHashCode(); + } + public abstract class ApiConsistencyFixtureBase { protected ApiConsistencyFixtureBase() @@ -916,6 +1048,8 @@ protected ApiConsistencyFixtureBase() { typeof(DbContextOptionsBuilder), typeof(DbContextOptionsBuilder<>) } }; + public virtual Dictionary MirrorTypes { get; } = new(); + public virtual HashSet NonVirtualMethods { get; } = new(); public virtual HashSet NotAnnotatedMethods { get; } = new(); public virtual HashSet AsyncMethodExceptions { get; } = new(); diff --git a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs index 690a9ed4267..752dda35c20 100644 --- a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs +++ b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs @@ -33,21 +33,13 @@ protected virtual void UseTransaction(DatabaseFacade facade, IDbContextTransacti { } + protected abstract TestHelpers TestHelpers { get; } + public virtual ModelBuilder CreateModelBuilder() - { - var context = CreateContext(); - return new ModelBuilder( - context.GetService().CreateConventionSet(), - context.GetService()); - } + => TestHelpers.CreateConventionBuilder(CreateContext().GetInfrastructure()); protected virtual IModel Validate(ModelBuilder modelBuilder) - { - var context = CreateContext(); - var modelRuntimeInitializer = context.GetService(); - var logger = context.GetService>(); - return modelRuntimeInitializer.Initialize((IModel)modelBuilder.Model, designTime: true, logger); - } + => ((TestHelpers.TestModelBuilder)modelBuilder).FinalizeModel(designTime: true); protected class Person { @@ -868,7 +860,7 @@ public class GeneratedEntity } [ConditionalFact] - public virtual ModelBuilder DatabaseGeneratedOption_Identity_does_not_throw_on_noninteger_properties() + public virtual IModel DatabaseGeneratedOption_Identity_does_not_throw_on_noninteger_properties() { var modelBuilder = CreateModelBuilder(); @@ -894,9 +886,7 @@ public virtual ModelBuilder DatabaseGeneratedOption_Identity_does_not_throw_on_n Assert.Equal(ValueGenerated.OnAdd, guidProperty.ValueGenerated); Assert.True(guidProperty.RequiresValueGenerator()); - Validate(modelBuilder); - - return modelBuilder; + return Validate(modelBuilder); } public class GeneratedEntityNonInteger @@ -2846,7 +2836,8 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build .Log(CoreEventId.ConflictingKeylessAndKeyAttributesWarning)); protected override bool ShouldLogCategory(string logCategory) - => logCategory == DbLoggerCategory.Model.Name; + => logCategory == DbLoggerCategory.Model.Name + || logCategory == DbLoggerCategory.Model.Validation.Name; protected override void Seed(PoolableDbContext context) { diff --git a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs index 9daa1ebd398..535b8402c78 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs @@ -200,7 +200,14 @@ public TestModelBuilder CreateConventionBuilder( customServices.AddScoped(_ => validationLogger); - var contextServices = CreateContextServices(customServices); + return CreateConventionBuilder(CreateContextServices(customServices), configure, validationLogger); + } + + public TestModelBuilder CreateConventionBuilder( + IServiceProvider contextServices, + Action configure = null, + IDiagnosticsLogger validationLogger = null) + { var modelCreationDependencies = contextServices.GetRequiredService(); var modelConfigurationBuilder = new TestModelConfigurationBuilder( @@ -211,7 +218,7 @@ public TestModelBuilder CreateConventionBuilder( return modelConfigurationBuilder.CreateModelBuilder( modelCreationDependencies.ModelDependencies, modelCreationDependencies.ModelRuntimeInitializer, - validationLogger); + validationLogger ?? contextServices.GetRequiredService>()); } public virtual LoggingDefinitions LoggingDefinitions { get; } = new TestLoggingDefinitions(); diff --git a/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs index f8cab2a2ace..7c511785218 100644 --- a/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/DataAnnotationSqlServerTest.cs @@ -20,8 +20,11 @@ public DataAnnotationSqlServerTest(DataAnnotationSqlServerFixture fixture, ITest protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); + protected override TestHelpers TestHelpers + => SqlServerTestHelpers.Instance; + [ConditionalFact] - public virtual ModelBuilder Default_for_key_string_column_throws() + public virtual void Default_for_key_string_column_throws() { var modelBuilder = CreateModelBuilder(); @@ -35,8 +38,6 @@ public virtual ModelBuilder Default_for_key_string_column_throws() .GenerateMessage(nameof(Login1.UserName), nameof(Login1)), "RelationalEventId.ModelValidationKeyDefaultValueWarning"), Assert.Throws(() => Validate(modelBuilder)).Message); - - return modelBuilder; } public override IModel Non_public_annotations_are_enabled() @@ -44,7 +45,7 @@ public override IModel Non_public_annotations_are_enabled() var model = base.Non_public_annotations_are_enabled(); var property = GetProperty(model, "PersonFirstName"); - Assert.Equal("dsdsd", property.GetColumnBaseName()); + Assert.Equal("dsdsd", property.GetColumnName()); Assert.Equal("nvarchar(128)", property.GetColumnType()); return model; @@ -55,7 +56,7 @@ public override IModel Field_annotations_are_enabled() var model = base.Field_annotations_are_enabled(); var property = GetProperty(model, "_personFirstName"); - Assert.Equal("dsdsd", property.GetColumnBaseName()); + Assert.Equal("dsdsd", property.GetColumnName()); Assert.Equal("nvarchar(128)", property.GetColumnType()); return model; @@ -66,7 +67,7 @@ public override IModel Key_and_column_work_together() var model = base.Key_and_column_work_together(); var relational = GetProperty(model, "PersonFirstName"); - Assert.Equal("dsdsd", relational.GetColumnBaseName()); + Assert.Equal("dsdsd", relational.GetColumnName()); Assert.Equal("nvarchar(128)", relational.GetColumnType()); return model; @@ -123,16 +124,18 @@ public virtual void ColumnAttribute_configures_the_property_correctly() var modelBuilder = CreateModelBuilder(); modelBuilder.Entity().HasKey(o => o.UniqueNo); + var model = modelBuilder.FinalizeModel(); + Assert.Equal( "Unique_No", - modelBuilder.Model.FindEntityType(typeof(One)).FindProperty(nameof(One.UniqueNo)).GetColumnBaseName()); + model.FindEntityType(typeof(One)).FindProperty(nameof(One.UniqueNo)).GetColumnName()); } - public override ModelBuilder DatabaseGeneratedOption_Identity_does_not_throw_on_noninteger_properties() + public override IModel DatabaseGeneratedOption_Identity_does_not_throw_on_noninteger_properties() { - var modelBuilder = base.DatabaseGeneratedOption_Identity_does_not_throw_on_noninteger_properties(); + var model = base.DatabaseGeneratedOption_Identity_does_not_throw_on_noninteger_properties(); - var entity = modelBuilder.Model.FindEntityType(typeof(GeneratedEntityNonInteger)); + var entity = model.FindEntityType(typeof(GeneratedEntityNonInteger)); var stringProperty = entity.FindProperty(nameof(GeneratedEntityNonInteger.String)); Assert.Equal(SqlServerValueGenerationStrategy.None, stringProperty.GetValueGenerationStrategy()); @@ -143,7 +146,7 @@ public override ModelBuilder DatabaseGeneratedOption_Identity_does_not_throw_on_ var guidProperty = entity.FindProperty(nameof(GeneratedEntityNonInteger.Guid)); Assert.Equal(SqlServerValueGenerationStrategy.None, guidProperty.GetValueGenerationStrategy()); - return modelBuilder; + return model; } public override void ConcurrencyCheckAttribute_throws_if_value_in_database_changed() diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/IncompleteMappingInheritanceQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/IncompleteMappingInheritanceQuerySqlServerTest.cs index adc75cf5ab3..06e2b343693 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/IncompleteMappingInheritanceQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/IncompleteMappingInheritanceQuerySqlServerTest.cs @@ -32,15 +32,15 @@ public virtual void Common_property_shares_column() var cokeType = context.Model.FindEntityType(typeof(Coke)); var teaType = context.Model.FindEntityType(typeof(Tea)); - Assert.Equal("SugarGrams", cokeType.FindProperty("SugarGrams").GetColumnBaseName()); - Assert.Equal("CaffeineGrams", cokeType.FindProperty("CaffeineGrams").GetColumnBaseName()); - Assert.Equal("CokeCO2", cokeType.FindProperty("Carbonation").GetColumnBaseName()); + Assert.Equal("SugarGrams", cokeType.FindProperty("SugarGrams").GetColumnName()); + Assert.Equal("CaffeineGrams", cokeType.FindProperty("CaffeineGrams").GetColumnName()); + Assert.Equal("CokeCO2", cokeType.FindProperty("Carbonation").GetColumnName()); - Assert.Equal("SugarGrams", liltType.FindProperty("SugarGrams").GetColumnBaseName()); - Assert.Equal("LiltCO2", liltType.FindProperty("Carbonation").GetColumnBaseName()); + Assert.Equal("SugarGrams", liltType.FindProperty("SugarGrams").GetColumnName()); + Assert.Equal("LiltCO2", liltType.FindProperty("Carbonation").GetColumnName()); - Assert.Equal("CaffeineGrams", teaType.FindProperty("CaffeineGrams").GetColumnBaseName()); - Assert.Equal("HasMilk", teaType.FindProperty("HasMilk").GetColumnBaseName()); + Assert.Equal("CaffeineGrams", teaType.FindProperty("CaffeineGrams").GetColumnName()); + Assert.Equal("HasMilk", teaType.FindProperty("HasMilk").GetColumnName()); } public override async Task Can_query_when_shared_column(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/InheritanceQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/InheritanceQuerySqlServerTest.cs index 7f2dabb7c9b..cd3db9c45d9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/InheritanceQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/InheritanceQuerySqlServerTest.cs @@ -29,15 +29,15 @@ public virtual void Common_property_shares_column() var cokeType = context.Model.FindEntityType(typeof(Coke)); var teaType = context.Model.FindEntityType(typeof(Tea)); - Assert.Equal("SugarGrams", cokeType.FindProperty("SugarGrams").GetColumnBaseName()); - Assert.Equal("CaffeineGrams", cokeType.FindProperty("CaffeineGrams").GetColumnBaseName()); - Assert.Equal("CokeCO2", cokeType.FindProperty("Carbonation").GetColumnBaseName()); + Assert.Equal("SugarGrams", cokeType.FindProperty("SugarGrams").GetColumnName()); + Assert.Equal("CaffeineGrams", cokeType.FindProperty("CaffeineGrams").GetColumnName()); + Assert.Equal("CokeCO2", cokeType.FindProperty("Carbonation").GetColumnName()); - Assert.Equal("SugarGrams", liltType.FindProperty("SugarGrams").GetColumnBaseName()); - Assert.Equal("LiltCO2", liltType.FindProperty("Carbonation").GetColumnBaseName()); + Assert.Equal("SugarGrams", liltType.FindProperty("SugarGrams").GetColumnName()); + Assert.Equal("LiltCO2", liltType.FindProperty("Carbonation").GetColumnName()); - Assert.Equal("CaffeineGrams", teaType.FindProperty("CaffeineGrams").GetColumnBaseName()); - Assert.Equal("HasMilk", teaType.FindProperty("HasMilk").GetColumnBaseName()); + Assert.Equal("CaffeineGrams", teaType.FindProperty("CaffeineGrams").GetColumnName()); + Assert.Equal("HasMilk", teaType.FindProperty("HasMilk").GetColumnName()); } public override async Task Can_query_when_shared_column(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs b/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs index a176589a48d..f320b234e00 100644 --- a/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/SqlServerApiConsistencyTest.cs @@ -33,9 +33,9 @@ public class SqlServerApiConsistencyFixture : ApiConsistencyFixtureBase typeof(SqlServerServiceCollectionExtensions), typeof(SqlServerDbFunctionsExtensions), typeof(OwnedNavigationTemporalPeriodPropertyBuilder), - typeof(TemporalPeriodPropertyBuilder), typeof(OwnedNavigationTemporalTableBuilder), - typeof(OwnedNavigationTemporalTableBuilder<>), + typeof(OwnedNavigationTemporalTableBuilder<,>), + typeof(TemporalPeriodPropertyBuilder), typeof(TemporalTableBuilder), typeof(TemporalTableBuilder<>) }; @@ -90,5 +90,17 @@ public override null ) }; + + protected override void Initialize() + { + GenericFluentApiTypes.Add(typeof(TemporalTableBuilder), typeof(TemporalTableBuilder<>)); + GenericFluentApiTypes.Add(typeof(OwnedNavigationTemporalTableBuilder), typeof(OwnedNavigationTemporalTableBuilder<,>)); + + MirrorTypes.Add(typeof(TemporalTableBuilder), typeof(OwnedNavigationTemporalTableBuilder)); + MirrorTypes.Add(typeof(TemporalTableBuilder<>), typeof(OwnedNavigationTemporalTableBuilder<,>)); + MirrorTypes.Add(typeof(TemporalPeriodPropertyBuilder), typeof(OwnedNavigationTemporalPeriodPropertyBuilder)); + + base.Initialize(); + } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/UpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/UpdatesSqlServerTest.cs index 5e33fb7b297..92291a1d3ae 100644 --- a/test/EFCore.SqlServer.FunctionalTests/UpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/UpdatesSqlServerTest.cs @@ -173,10 +173,10 @@ public override void Identifiers_are_generated_correctly() entityType2.GetKeys().Single().GetName()); Assert.Equal( "ExtraPropertyWithAnExtremelyLongAndOverlyConvolutedNameThatIsUsedToVerifyThatTheStoreIdentifierGenerationLengthLimitIsWorkingCo~", - entityType2.GetProperties().ElementAt(1).GetColumnBaseName()); + entityType2.GetProperties().ElementAt(1).GetColumnName()); Assert.Equal( "ExtraPropertyWithAnExtremelyLongAndOverlyConvolutedNameThatIsUsedToVerifyThatTheStoreIdentifierGenerationLengthLimitIsWorkingC~1", - entityType2.GetProperties().ElementAt(2).GetColumnBaseName()); + entityType2.GetProperties().ElementAt(2).GetColumnName()); Assert.Equal( "IX_LoginEntityTypeWithAnExtremelyLongAndOverlyConvolutedNameThatIsUsedToVerifyThatTheStoreIdentifierGenerationLengthLimitIsWor~1", entityType2.GetIndexes().Single().GetDatabaseName()); diff --git a/test/EFCore.SqlServer.Tests/Metadata/SqlServerMetadataExtensionsTest.cs b/test/EFCore.SqlServer.Tests/Metadata/SqlServerMetadataExtensionsTest.cs index 51823ccbf87..f2873886964 100644 --- a/test/EFCore.SqlServer.Tests/Metadata/SqlServerMetadataExtensionsTest.cs +++ b/test/EFCore.SqlServer.Tests/Metadata/SqlServerMetadataExtensionsTest.cs @@ -99,23 +99,23 @@ public void Can_get_and_set_column_name() .Property(e => e.Name) .Metadata; - Assert.Equal("Name", property.GetColumnBaseName()); + Assert.Equal("Name", property.GetColumnName()); Assert.Null(((IConventionProperty)property).GetColumnNameConfigurationSource()); ((IConventionProperty)property).SetColumnName("Eman", fromDataAnnotation: true); - Assert.Equal("Eman", property.GetColumnBaseName()); + Assert.Equal("Eman", property.GetColumnName()); Assert.Equal(ConfigurationSource.DataAnnotation, ((IConventionProperty)property).GetColumnNameConfigurationSource()); property.SetColumnName("MyNameIs"); Assert.Equal("Name", property.Name); - Assert.Equal("MyNameIs", property.GetColumnBaseName()); + Assert.Equal("MyNameIs", property.GetColumnName()); Assert.Equal(ConfigurationSource.Explicit, ((IConventionProperty)property).GetColumnNameConfigurationSource()); property.SetColumnName(null); - Assert.Equal("Name", property.GetColumnBaseName()); + Assert.Equal("Name", property.GetColumnName()); Assert.Null(((IConventionProperty)property).GetColumnNameConfigurationSource()); } diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs index 23d831e3b0a..62e650279c5 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderGenericTest.cs @@ -1,1496 +1,66 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#nullable enable + // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ModelBuilding; -public class SqlServerModelBuilderGenericTest : ModelBuilderGenericTest +public class SqlServerModelBuilderGenericTest : SqlServerModelBuilderTestBase { - public class SqlServerGenericNonRelationship : GenericNonRelationship - { - [ConditionalFact] - public virtual void Index_has_a_filter_if_nonclustered_unique_with_nullable_properties() - { - var modelBuilder = CreateModelBuilder(); - var entityTypeBuilder = modelBuilder - .Entity(); - var indexBuilder = entityTypeBuilder - .HasIndex(ix => ix.Name) - .IsUnique(); - - var entityType = modelBuilder.Model.FindEntityType(typeof(Customer)); - var index = entityType.GetIndexes().Single(); - Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); - - indexBuilder.IsUnique(false); - - Assert.Null(index.GetFilter()); - - indexBuilder.IsUnique(); - - Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); - - indexBuilder.IsClustered(); - - Assert.Null(index.GetFilter()); - - indexBuilder.IsClustered(false); - - Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); - - entityTypeBuilder.Property(e => e.Name).IsRequired(); - - Assert.Null(index.GetFilter()); - - entityTypeBuilder.Property(e => e.Name).IsRequired(false); - - Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); - - entityTypeBuilder.Property(e => e.Name).HasColumnName("RelationalName"); - - Assert.Equal("[RelationalName] IS NOT NULL", index.GetFilter()); - - entityTypeBuilder.Property(e => e.Name).HasColumnName("SqlServerName"); - - Assert.Equal("[SqlServerName] IS NOT NULL", index.GetFilter()); - - entityTypeBuilder.Property(e => e.Name).HasColumnName(null); - - Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); - - indexBuilder.HasFilter("Foo"); - - Assert.Equal("Foo", index.GetFilter()); - - indexBuilder.HasFilter(null); - - Assert.Null(index.GetFilter()); - } - - [ConditionalFact] - public virtual void Can_set_store_type_for_property_type() - { - var modelBuilder = CreateModelBuilder( - c => - { - c.Properties().HaveColumnType("smallint"); - c.Properties().HaveColumnType("nchar(max)"); - c.Properties(typeof(Nullable<>)).HavePrecision(2); - }); - - modelBuilder.Entity( - b => - { - b.Property("Charm"); - b.Property("Strange"); - b.Property("Top"); - b.Property("Bottom"); - }); - - var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); - - Assert.Equal("smallint", entityType.FindProperty(Customer.IdProperty.Name).GetColumnType()); - Assert.Equal("smallint", entityType.FindProperty("Up").GetColumnType()); - Assert.Equal("nchar(max)", entityType.FindProperty("Down").GetColumnType()); - var charm = entityType.FindProperty("Charm"); - Assert.Equal("smallint", charm.GetColumnType()); - Assert.Null(charm.GetPrecision()); - Assert.Equal("nchar(max)", entityType.FindProperty("Strange").GetColumnType()); - var top = entityType.FindProperty("Top"); - Assert.Equal("smallint", top.GetColumnType()); - Assert.Equal(2, top.GetPrecision()); - Assert.Equal("nchar(max)", entityType.FindProperty("Bottom").GetColumnType()); - } - - [ConditionalFact] - public virtual void Can_set_fixed_length_for_property_type() - { - var modelBuilder = CreateModelBuilder( - c => - { - c.Properties().AreFixedLength(false); - c.Properties().AreFixedLength(); - }); - - modelBuilder.Entity( - b => - { - b.Property("Charm"); - b.Property("Strange"); - b.Property("Top"); - b.Property("Bottom"); - }); - - var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); - - Assert.False(entityType.FindProperty(Customer.IdProperty.Name).IsFixedLength()); - Assert.False(entityType.FindProperty("Up").IsFixedLength()); - Assert.True(entityType.FindProperty("Down").IsFixedLength()); - Assert.False(entityType.FindProperty("Charm").IsFixedLength()); - Assert.True(entityType.FindProperty("Strange").IsFixedLength()); - Assert.False(entityType.FindProperty("Top").IsFixedLength()); - Assert.True(entityType.FindProperty("Bottom").IsFixedLength()); - } - - [ConditionalFact] - public virtual void Can_set_collation_for_property_type() - { - var modelBuilder = CreateModelBuilder( - c => - { - c.Properties().UseCollation("Latin1_General_CS_AS_KS_WS"); - c.Properties().UseCollation("Latin1_General_BIN"); - }); - - modelBuilder.Entity( - b => - { - b.Property("Charm"); - b.Property("Strange"); - b.Property("Top"); - b.Property("Bottom"); - }); - - var model = modelBuilder.FinalizeModel(); - var entityType = model.FindEntityType(typeof(Quarks)); - - Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty(Customer.IdProperty.Name).GetCollation()); - Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Up").GetCollation()); - Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Down").GetCollation()); - Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Charm").GetCollation()); - Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Strange").GetCollation()); - Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Top").GetCollation()); - Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Bottom").GetCollation()); - } - - protected override TestModelBuilder CreateModelBuilder(Action configure = null) - => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); - } - - public class SqlServerGenericInheritance : GenericInheritance - { - [ConditionalFact] // #7240 - public void Can_use_shadow_FK_that_collides_with_convention_shadow_FK_on_other_derived_type() - { - var modelBuilder = CreateModelBuilder(); - modelBuilder.Entity(); - modelBuilder.Entity() - .HasOne(p => p.A) - .WithOne() - .HasForeignKey("ParentId"); - - var model = modelBuilder.FinalizeModel(); - - var property1 = model.FindEntityType(typeof(DisjointChildSubclass1)).FindProperty("ParentId"); - Assert.True(property1.IsForeignKey()); - Assert.Equal("ParentId", property1.GetColumnBaseName()); - var property2 = model.FindEntityType(typeof(DisjointChildSubclass2)).FindProperty("ParentId"); - Assert.True(property2.IsForeignKey()); - Assert.Equal("DisjointChildSubclass2_ParentId", property2.GetColumnBaseName()); - } - - [ConditionalFact] - public void Inherited_clr_properties_are_mapped_to_the_same_column() - { - var modelBuilder = CreateModelBuilder(); - modelBuilder.Entity(); - modelBuilder.Ignore(); - modelBuilder.Entity(); - modelBuilder.Entity(); - - var model = modelBuilder.FinalizeModel(); - - var property1 = model.FindEntityType(typeof(DisjointChildSubclass1)).FindProperty(nameof(Child.Name)); - Assert.Equal(nameof(Child.Name), property1.GetColumnBaseName()); - var property2 = model.FindEntityType(typeof(DisjointChildSubclass2)).FindProperty(nameof(Child.Name)); - Assert.Equal(nameof(Child.Name), property2.GetColumnBaseName()); - } - - [ConditionalFact] //Issue#10659 - public void Index_convention_run_for_fk_when_derived_type_discovered_before_base_type() - { - var modelBuilder = CreateModelBuilder(); - modelBuilder.Ignore(); - modelBuilder.Entity(); - modelBuilder.Entity(); - - var index = modelBuilder.Model.FindEntityType(typeof(CustomerDetails)).GetIndexes().Single(); - - Assert.Equal("[CustomerId] IS NOT NULL", index.GetFilter()); - } - - [ConditionalFact] - public void Index_convention_sets_filter_for_unique_index_when_base_type_changed() - { - var modelBuilder = CreateModelBuilder(); - modelBuilder.Ignore(); - modelBuilder.Entity() - .HasIndex(e => e.CustomerId) - .IsUnique(); - - modelBuilder.Entity(); - - var index = modelBuilder.Model.FindEntityType(typeof(CustomerDetails)).GetIndexes().Single(); - - Assert.Equal("[CustomerId] IS NOT NULL", index.GetFilter()); - - modelBuilder.Ignore(); - - Assert.Null(index.GetFilter()); - } - - [ConditionalFact] - public virtual void Can_override_TPC_with_TPH() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity

(); - modelBuilder.Entity(); - modelBuilder.Entity(); - modelBuilder.Entity() - .UseTpcMappingStrategy() - .UseTphMappingStrategy(); - - var model = modelBuilder.FinalizeModel(); - - Assert.Equal("Discriminator", model.FindEntityType(typeof(PBase)).GetDiscriminatorPropertyName()); - Assert.Equal(nameof(PBase), model.FindEntityType(typeof(PBase)).GetDiscriminatorValue()); - Assert.Equal(nameof(P), model.FindEntityType(typeof(P)).GetDiscriminatorValue()); - Assert.Equal(nameof(Q), model.FindEntityType(typeof(Q)).GetDiscriminatorValue()); - } - - [ConditionalFact] - public virtual void TPT_identifying_FK_is_created_only_on_declaring_table() - { - var modelBuilder = CreateModelBuilder(); - modelBuilder.Entity() - .Ignore(b => b.Bun) - .Ignore(b => b.Pickles); - modelBuilder.Entity( - b => - { - b.ToTable("Ingredients"); - b.Ignore(i => i.BigMak); - }); - modelBuilder.Entity( - b => - { - b.ToTable("Buns"); - b.HasOne(i => i.BigMak).WithOne().HasForeignKey(i => i.Id); - }); - modelBuilder.Entity( - b => - { - b.ToTable("SesameBuns"); - }); - - var model = modelBuilder.FinalizeModel(); - - var principalType = model.FindEntityType(typeof(BigMak)); - Assert.Empty(principalType.GetForeignKeys()); - Assert.Empty(principalType.GetIndexes()); - Assert.Null(principalType.FindDiscriminatorProperty()); - - var ingredientType = model.FindEntityType(typeof(Ingredient)); - - var bunType = model.FindEntityType(typeof(Bun)); - Assert.Empty(bunType.GetIndexes()); - Assert.Null(bunType.FindDiscriminatorProperty()); - var bunFk = bunType.GetDeclaredForeignKeys().Single(fk => !fk.IsBaseLinking()); - Assert.Equal("FK_Buns_BigMak_Id", bunFk.GetConstraintName()); - Assert.Equal( - "FK_Buns_BigMak_Id", bunFk.GetConstraintName( - StoreObjectIdentifier.Create(bunType, StoreObjectType.Table).Value, - StoreObjectIdentifier.Create(principalType, StoreObjectType.Table).Value)); - Assert.Single(bunFk.GetMappedConstraints()); - - var bunLinkingFk = bunType.GetDeclaredForeignKeys().Single(fk => fk.IsBaseLinking()); - Assert.Equal("FK_Buns_Ingredients_Id", bunLinkingFk.GetConstraintName()); - Assert.Equal( - "FK_Buns_Ingredients_Id", bunLinkingFk.GetConstraintName( - StoreObjectIdentifier.Create(bunType, StoreObjectType.Table).Value, - StoreObjectIdentifier.Create(ingredientType, StoreObjectType.Table).Value)); - Assert.Single(bunLinkingFk.GetMappedConstraints()); - - var sesameBunType = model.FindEntityType(typeof(SesameBun)); - Assert.Empty(sesameBunType.GetIndexes()); - var sesameBunFk = sesameBunType.GetDeclaredForeignKeys().Single(); - Assert.True(sesameBunFk.IsBaseLinking()); - Assert.Equal("FK_SesameBuns_Buns_Id", sesameBunFk.GetConstraintName()); - Assert.Equal( - "FK_SesameBuns_Buns_Id", sesameBunFk.GetConstraintName( - StoreObjectIdentifier.Create(sesameBunType, StoreObjectType.Table).Value, - StoreObjectIdentifier.Create(bunType, StoreObjectType.Table).Value)); - Assert.Single(sesameBunFk.GetMappedConstraints()); - } - - [ConditionalFact] - public virtual void TPC_identifying_FKs_are_created_on_all_tables() - { - var modelBuilder = CreateModelBuilder(); - modelBuilder.Entity() - .Ignore(b => b.Bun) - .Ignore(b => b.Pickles); - modelBuilder.Entity( - b => - { - b.ToTable("Ingredients"); - b.Ignore(i => i.BigMak); - b.UseTpcMappingStrategy(); - }); - modelBuilder.Entity( - b => - { - b.ToTable("Buns"); - b.HasOne(i => i.BigMak).WithOne().HasForeignKey(i => i.Id); - b.UseTpcMappingStrategy(); - }); - modelBuilder.Entity( - b => - { - b.ToTable("SesameBuns"); - }); - - var model = modelBuilder.FinalizeModel(); - - var principalType = model.FindEntityType(typeof(BigMak)); - Assert.Empty(principalType.GetForeignKeys()); - Assert.Empty(principalType.GetIndexes()); - Assert.Null(principalType.FindDiscriminatorProperty()); - - var ingredientType = model.FindEntityType(typeof(Ingredient)); - - var bunType = model.FindEntityType(typeof(Bun)); - Assert.Empty(bunType.GetIndexes()); - Assert.Null(bunType.FindDiscriminatorProperty()); - var bunFk = bunType.GetDeclaredForeignKeys().Single(); - Assert.Equal("FK_Buns_BigMak_Id", bunFk.GetConstraintName()); - Assert.Equal( - "FK_Buns_BigMak_Id", bunFk.GetConstraintName( - StoreObjectIdentifier.Create(bunType, StoreObjectType.Table).Value, - StoreObjectIdentifier.Create(principalType, StoreObjectType.Table).Value)); - Assert.Equal(2, bunFk.GetMappedConstraints().Count()); - - Assert.Empty(bunType.GetDeclaredForeignKeys().Where(fk => fk.IsBaseLinking())); - - var sesameBunType = model.FindEntityType(typeof(SesameBun)); - Assert.Empty(sesameBunType.GetIndexes()); - Assert.Empty(sesameBunType.GetDeclaredForeignKeys()); - Assert.Equal( - "FK_SesameBuns_BigMak_Id", bunFk.GetConstraintName( - StoreObjectIdentifier.Create(sesameBunType, StoreObjectType.Table).Value, - StoreObjectIdentifier.Create(principalType, StoreObjectType.Table).Value)); - } - - [ConditionalFact] - public virtual void TPT_index_can_use_inherited_properties() - { - var modelBuilder = CreateModelBuilder(); - modelBuilder.Entity() - .Ignore(b => b.Bun) - .Ignore(b => b.Pickles); - modelBuilder.Entity( - b => - { - b.ToTable("Ingredients"); - b.Property("NullableProp"); - b.Ignore(i => i.BigMak); - }); - modelBuilder.Entity( - b => - { - b.ToTable("Buns"); - b.HasIndex(bun => bun.BurgerId); - b.HasIndex("NullableProp"); - b.HasOne(i => i.BigMak).WithOne().HasForeignKey(i => i.Id); - }); - - var model = modelBuilder.FinalizeModel(); - - var bunType = model.FindEntityType(typeof(Bun)); - Assert.All(bunType.GetIndexes(), i => Assert.Null(i.GetFilter())); - } - - [ConditionalFact] - public void Can_add_check_constraints() - { - var modelBuilder = CreateModelBuilder(); - modelBuilder.Entity() - .HasBaseType(null) - .HasCheckConstraint("CK_ChildBase_LargeId", "Id > 1000", c => c.HasName("CK_LargeId")); - modelBuilder.Entity() - .HasCheckConstraint("PositiveId", "Id > 0") - .HasCheckConstraint("CK_ChildBase_LargeId", "Id > 1000"); - modelBuilder.Entity() - .HasBaseType(); - modelBuilder.Entity(); - - var model = modelBuilder.FinalizeModel(); - - var @base = model.FindEntityType(typeof(ChildBase)); - Assert.Equal(2, @base.GetCheckConstraints().Count()); - - var firstCheckConstraint = @base.FindCheckConstraint("PositiveId"); - Assert.Equal("PositiveId", firstCheckConstraint.ModelName); - Assert.Equal("Id > 0", firstCheckConstraint.Sql); - Assert.Equal("PositiveId", firstCheckConstraint.Name); - - var secondCheckConstraint = @base.FindCheckConstraint("CK_ChildBase_LargeId"); - Assert.Equal("CK_ChildBase_LargeId", secondCheckConstraint.ModelName); - Assert.Equal("Id > 1000", secondCheckConstraint.Sql); - Assert.Equal("CK_LargeId", secondCheckConstraint.Name); - - var child = model.FindEntityType(typeof(Child)); - Assert.Equal(@base.GetCheckConstraints(), child.GetCheckConstraints()); - Assert.Empty(child.GetDeclaredCheckConstraints()); - } - - [ConditionalFact] - public void Adding_conflicting_check_constraint_to_derived_type_throws() - { - var modelBuilder = CreateModelBuilder(); - modelBuilder.Entity() - .HasCheckConstraint("LargeId", "Id > 100", c => c.HasName("CK_LargeId")); - - Assert.Equal( - RelationalStrings.DuplicateCheckConstraint("LargeId", nameof(Child), nameof(ChildBase)), - Assert.Throws( - () => modelBuilder.Entity().HasCheckConstraint("LargeId", "Id > 1000")).Message); - } - - [ConditionalFact] - public void Adding_conflicting_check_constraint_to_derived_type_before_base_throws() - { - var modelBuilder = CreateModelBuilder(); - modelBuilder.Entity() - .HasBaseType(null) - .HasCheckConstraint("LargeId", "Id > 1000"); - modelBuilder.Entity() - .HasCheckConstraint("LargeId", "Id > 100", c => c.HasName("CK_LargeId")); - - Assert.Equal( - RelationalStrings.DuplicateCheckConstraint("LargeId", nameof(Child), nameof(ChildBase)), - Assert.Throws( - () => modelBuilder.Entity().HasBaseType()).Message); - } - - public class Parent - { - public int Id { get; set; } - public DisjointChildSubclass1 A { get; set; } - public IList B { get; set; } - } - - public abstract class ChildBase - { - public int Id { get; set; } - } - - public abstract class Child : ChildBase - { - public string Name { get; set; } - } - - public class DisjointChildSubclass1 : Child - { - } - - public class DisjointChildSubclass2 : Child - { - } - - protected override TestModelBuilder CreateModelBuilder(Action configure = null) - => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); - } - - public class SqlServerGenericOneToMany : GenericOneToMany + public class SqlServerGenericNonRelationship : SqlServerNonRelationship { - protected override TestModelBuilder CreateModelBuilder(Action configure = null) - => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderGenericTest.GenericTestModelBuilder(testHelpers, configure); } - public class SqlServerGenericManyToOne : GenericManyToOne + public class SqlServerGenericInheritance : SqlServerInheritance { - protected override TestModelBuilder CreateModelBuilder(Action configure = null) - => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderGenericTest.GenericTestModelBuilder(testHelpers, configure); } - public class SqlServerGenericOneToOne : GenericOneToOne + public class SqlServerGenericOneToMany : SqlServerOneToMany { - protected override TestModelBuilder CreateModelBuilder(Action configure = null) - => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); - } - - public class SqlServerGenericManyToMany : GenericManyToMany - { - [ConditionalFact] - public virtual void Join_entity_type_uses_same_schema() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity().ToTable("Category", "mySchema").Ignore(c => c.ProductCategories); - modelBuilder.Entity().ToTable("Product", "mySchema"); - modelBuilder.Entity(); - - var model = modelBuilder.FinalizeModel(); - - var productType = model.FindEntityType(typeof(Product)); - var categoryType = model.FindEntityType(typeof(Category)); - - var categoriesNavigation = productType.GetSkipNavigations().Single(); - var productsNavigation = categoryType.GetSkipNavigations().Single(); - - var categoriesFk = categoriesNavigation.ForeignKey; - var productsFk = productsNavigation.ForeignKey; - var productCategoryType = categoriesFk.DeclaringEntityType; - - Assert.Equal(typeof(Dictionary), productCategoryType.ClrType); - Assert.Equal("mySchema", productCategoryType.GetSchema()); - Assert.Same(categoriesFk, productCategoryType.GetForeignKeys().Last()); - Assert.Same(productsFk, productCategoryType.GetForeignKeys().First()); - Assert.Equal(2, productCategoryType.GetForeignKeys().Count()); - } - - [ConditionalFact] - public virtual void Join_entity_type_uses_default_schema_if_related_are_different() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity().ToTable("Category").Ignore(c => c.ProductCategories); - modelBuilder.Entity().ToTable("Product", "dbo"); - modelBuilder.Entity(); - - var model = modelBuilder.FinalizeModel(); - - var productType = model.FindEntityType(typeof(Product)); - var categoryType = model.FindEntityType(typeof(Category)); - - var categoriesNavigation = productType.GetSkipNavigations().Single(); - var productsNavigation = categoryType.GetSkipNavigations().Single(); - - var categoriesFk = categoriesNavigation.ForeignKey; - var productsFk = productsNavigation.ForeignKey; - var productCategoryType = categoriesFk.DeclaringEntityType; - - Assert.Equal(typeof(Dictionary), productCategoryType.ClrType); - Assert.Null(productCategoryType.GetSchema()); - Assert.Same(categoriesFk, productCategoryType.GetForeignKeys().Last()); - Assert.Same(productsFk, productCategoryType.GetForeignKeys().First()); - Assert.Equal(2, productCategoryType.GetForeignKeys().Count()); - } - - protected override TestModelBuilder CreateModelBuilder(Action configure = null) - => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); - } - - public class SqlServerGenericOwnedTypes : GenericOwnedTypes - { - [ConditionalFact] - public virtual void Owned_types_use_table_splitting_by_default() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity().OwnsOne( - b => b.AlternateLabel, - b => - { - b.Ignore(l => l.Book); - b.OwnsOne( - l => l.AnotherBookLabel, - ab => - { - ab.Property(l => l.BookId).HasColumnName("BookId2"); - ab.Ignore(l => l.Book); - ab.OwnsOne( - s => s.SpecialBookLabel, - s => - { - s.Property(l => l.BookId).HasColumnName("BookId2"); - s.Ignore(l => l.Book); - s.Ignore(l => l.BookLabel); - }); - }); - }); - - modelBuilder.Entity().OwnsOne(b => b.Label) - .Ignore(l => l.Book) - .OwnsOne(l => l.SpecialBookLabel) - .Ignore(l => l.Book) - .OwnsOne(a => a.AnotherBookLabel) - .Ignore(l => l.Book); - - modelBuilder.Entity().OwnsOne(b => b.Label) - .OwnsOne(l => l.AnotherBookLabel) - .Ignore(l => l.Book) - .OwnsOne(a => a.SpecialBookLabel) - .Ignore(l => l.Book) - .Ignore(l => l.BookLabel); - - modelBuilder.Entity().OwnsOne( - b => b.AlternateLabel, - b => - { - b.Ignore(l => l.Book); - b.OwnsOne( - l => l.SpecialBookLabel, - ab => - { - ab.Property(l => l.BookId).HasColumnName("BookId2"); - ab.Ignore(l => l.Book); - ab.OwnsOne( - s => s.AnotherBookLabel, - s => - { - s.Property(l => l.BookId).HasColumnName("BookId2"); - s.Ignore(l => l.Book); - }); - }); - }); - - var model = (IModel)modelBuilder.Model; - var book = model.FindEntityType(typeof(Book)); - var bookOwnership1 = book.FindNavigation(nameof(Book.Label)).ForeignKey; - var bookOwnership2 = book.FindNavigation(nameof(Book.AlternateLabel)).ForeignKey; - var bookLabel1Ownership1 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel)).ForeignKey; - var bookLabel1Ownership2 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel)).ForeignKey; - var bookLabel2Ownership1 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel)).ForeignKey; - var bookLabel2Ownership2 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel)).ForeignKey; - - Assert.Equal(book.GetTableName(), bookOwnership1.DeclaringEntityType.GetTableName()); - Assert.Equal(book.GetTableName(), bookOwnership2.DeclaringEntityType.GetTableName()); - Assert.Equal(book.GetTableName(), bookLabel1Ownership1.DeclaringEntityType.GetTableName()); - Assert.Equal(book.GetTableName(), bookLabel1Ownership2.DeclaringEntityType.GetTableName()); - Assert.Equal(book.GetTableName(), bookLabel2Ownership1.DeclaringEntityType.GetTableName()); - Assert.Equal(book.GetTableName(), bookLabel2Ownership2.DeclaringEntityType.GetTableName()); - - Assert.NotSame(bookOwnership1.DeclaringEntityType, bookOwnership2.DeclaringEntityType); - Assert.Single(bookOwnership1.DeclaringEntityType.GetForeignKeys()); - Assert.Single(bookOwnership1.DeclaringEntityType.GetForeignKeys()); - - Assert.NotSame(bookLabel1Ownership1.DeclaringEntityType, bookLabel2Ownership1.DeclaringEntityType); - Assert.NotSame(bookLabel1Ownership2.DeclaringEntityType, bookLabel2Ownership2.DeclaringEntityType); - Assert.Single(bookLabel1Ownership1.DeclaringEntityType.GetForeignKeys()); - Assert.Single(bookLabel1Ownership2.DeclaringEntityType.GetForeignKeys()); - Assert.Single(bookLabel2Ownership1.DeclaringEntityType.GetForeignKeys()); - Assert.Single(bookLabel2Ownership2.DeclaringEntityType.GetForeignKeys()); - - Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(BookLabel))); - Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(AnotherBookLabel))); - Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(SpecialBookLabel))); - - Assert.Null( - bookOwnership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id)) - .GetColumnName(StoreObjectIdentifier.Table("Label"))); - Assert.Null( - bookLabel2Ownership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id)) - .GetColumnName(StoreObjectIdentifier.Table("AlternateLabel"))); - - modelBuilder.Entity().OwnsOne(b => b.Label).ToTable("Label"); - modelBuilder.Entity().OwnsOne(b => b.AlternateLabel).ToTable("AlternateLabel"); - - model = modelBuilder.FinalizeModel(); - - Assert.Equal( - nameof(BookLabel.Id), - bookOwnership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id)) - .GetColumnName(StoreObjectIdentifier.Table("Label"))); - Assert.Equal( - nameof(BookLabel.AnotherBookLabel) + "_" + nameof(BookLabel.Id), - bookLabel2Ownership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id)) - .GetColumnName(StoreObjectIdentifier.Table("AlternateLabel"))); - - var alternateTable = model.GetRelationalModel().FindTable("AlternateLabel", null); - var bookId = alternateTable.FindColumn("BookId2"); - - Assert.Equal(4, bookId.PropertyMappings.Count()); - Assert.All(bookId.PropertyMappings, m => Assert.Equal(ValueGenerated.OnUpdateSometimes, m.Property.ValueGenerated)); - } - - [ConditionalFact] - public virtual void Owned_types_can_be_mapped_to_different_tables() - { - var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; - - modelBuilder.Entity( - bb => - { - bb.ToTable("BT", "BS", t => - { - t.ExcludeFromMigrations(); - - Assert.Equal("BT", t.Name); - Assert.Equal("BS", t.Schema); - }); - bb.OwnsOne( - b => b.AlternateLabel, tb => - { - tb.Ignore(l => l.Book); - tb.WithOwner() - .HasConstraintName("AlternateLabelFK"); - tb.ToTable("TT", "TS"); - tb.IsMemoryOptimized(); - tb.OwnsOne( - l => l.AnotherBookLabel, ab => - { - ab.Ignore(l => l.Book); - ab.ToTable("AT1", "AS1", t => - { - t.ExcludeFromMigrations(false); - - Assert.Equal("AT1", t.Name); - Assert.Equal("AS1", t.Schema); - }); - ab.OwnsOne(s => s.SpecialBookLabel) - .ToTable("ST11", "SS11") - .Ignore(l => l.Book) - .Ignore(l => l.BookLabel); - - ab.OwnedEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel)) - .AddAnnotation("Foo", "Bar"); - }); - tb.OwnsOne( - l => l.SpecialBookLabel, sb => - { - sb.Ignore(l => l.Book); - sb.ToTable("ST2", "SS2"); - sb.OwnsOne(s => s.AnotherBookLabel) - .ToTable("AT21", "AS21") - .Ignore(l => l.Book); - }); - }); - bb.OwnsOne( - b => b.Label, lb => - { - lb.Ignore(l => l.Book); - lb.ToTable("LT", "LS"); - lb.OwnsOne( - l => l.SpecialBookLabel, sb => - { - sb.Ignore(l => l.Book); - sb.ToTable("ST1", "SS1"); - sb.OwnsOne(a => a.AnotherBookLabel) - .ToTable("AT11", "AS11") - .Ignore(l => l.Book); - }); - lb.OwnsOne( - l => l.AnotherBookLabel, ab => - { - ab.Ignore(l => l.Book); - ab.ToTable("AT2", "AS2"); - ab.OwnsOne(a => a.SpecialBookLabel) - .ToTable("ST21", "SS21") - .Ignore(l => l.BookLabel) - .Ignore(l => l.Book); - }); - }); - }); - - modelBuilder.FinalizeModel(); - - var book = model.FindEntityType(typeof(Book)); - var bookOwnership1 = book.FindNavigation(nameof(Book.Label)).ForeignKey; - var bookOwnership2 = book.FindNavigation(nameof(Book.AlternateLabel)).ForeignKey; - var bookLabel1Ownership1 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel)).ForeignKey; - var bookLabel1Ownership2 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel)).ForeignKey; - var bookLabel2Ownership1 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel)).ForeignKey; - var bookLabel2Ownership2 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel)).ForeignKey; - var bookLabel1Ownership11 = bookLabel1Ownership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel)) - .ForeignKey; - var bookLabel1Ownership21 = bookLabel1Ownership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel)) - .ForeignKey; - var bookLabel2Ownership11 = bookLabel2Ownership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel)) - .ForeignKey; - var bookLabel2Ownership21 = bookLabel2Ownership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel)) - .ForeignKey; - - Assert.Equal("AlternateLabelFK", bookOwnership2.GetConstraintName()); - - Assert.Equal("BS", book.GetSchema()); - Assert.Equal("BT", book.GetTableName()); - Assert.True(book.IsTableExcludedFromMigrations()); - Assert.Equal("LS", bookOwnership1.DeclaringEntityType.GetSchema()); - Assert.Equal("LT", bookOwnership1.DeclaringEntityType.GetTableName()); - Assert.False(bookOwnership1.DeclaringEntityType.IsMemoryOptimized()); - Assert.True(bookOwnership1.DeclaringEntityType.IsTableExcludedFromMigrations()); - Assert.Equal("TS", bookOwnership2.DeclaringEntityType.GetSchema()); - Assert.Equal("TT", bookOwnership2.DeclaringEntityType.GetTableName()); - Assert.True(bookOwnership2.DeclaringEntityType.IsMemoryOptimized()); - Assert.True(bookOwnership2.DeclaringEntityType.IsTableExcludedFromMigrations()); - Assert.Equal("AS2", bookLabel1Ownership1.DeclaringEntityType.GetSchema()); - Assert.Equal("AT2", bookLabel1Ownership1.DeclaringEntityType.GetTableName()); - Assert.Equal("SS1", bookLabel1Ownership2.DeclaringEntityType.GetSchema()); - Assert.Equal("ST1", bookLabel1Ownership2.DeclaringEntityType.GetTableName()); - Assert.Equal("AS1", bookLabel2Ownership1.DeclaringEntityType.GetSchema()); - Assert.Equal("AT1", bookLabel2Ownership1.DeclaringEntityType.GetTableName()); - Assert.False(bookLabel2Ownership1.DeclaringEntityType.IsTableExcludedFromMigrations()); - Assert.Equal("SS2", bookLabel2Ownership2.DeclaringEntityType.GetSchema()); - Assert.Equal("ST2", bookLabel2Ownership2.DeclaringEntityType.GetTableName()); - Assert.Equal("SS21", bookLabel1Ownership11.DeclaringEntityType.GetSchema()); - Assert.Equal("ST21", bookLabel1Ownership11.DeclaringEntityType.GetTableName()); - Assert.Equal("AS11", bookLabel1Ownership21.DeclaringEntityType.GetSchema()); - Assert.Equal("AT11", bookLabel1Ownership21.DeclaringEntityType.GetTableName()); - Assert.Equal("SS11", bookLabel2Ownership11.DeclaringEntityType.GetSchema()); - Assert.Equal("ST11", bookLabel2Ownership11.DeclaringEntityType.GetTableName()); - Assert.Equal("AS21", bookLabel2Ownership21.DeclaringEntityType.GetSchema()); - Assert.Equal("AT21", bookLabel2Ownership21.DeclaringEntityType.GetTableName()); - - Assert.Equal("Bar", bookLabel2Ownership11.PrincipalToDependent["Foo"]); - - Assert.NotSame(bookOwnership1.DeclaringEntityType, bookOwnership2.DeclaringEntityType); - Assert.Single(bookOwnership1.DeclaringEntityType.GetForeignKeys()); - Assert.Single(bookOwnership2.DeclaringEntityType.GetForeignKeys()); - - Assert.NotSame(bookLabel1Ownership1.DeclaringEntityType, bookLabel2Ownership1.DeclaringEntityType); - Assert.NotSame(bookLabel1Ownership2.DeclaringEntityType, bookLabel2Ownership2.DeclaringEntityType); - Assert.Single(bookLabel1Ownership1.DeclaringEntityType.GetForeignKeys()); - Assert.Single(bookLabel1Ownership2.DeclaringEntityType.GetForeignKeys()); - Assert.Single(bookLabel2Ownership1.DeclaringEntityType.GetForeignKeys()); - Assert.Single(bookLabel2Ownership2.DeclaringEntityType.GetForeignKeys()); - - Assert.NotSame(bookLabel1Ownership11.DeclaringEntityType, bookLabel2Ownership11.DeclaringEntityType); - Assert.NotSame(bookLabel1Ownership21.DeclaringEntityType, bookLabel2Ownership21.DeclaringEntityType); - Assert.Single(bookLabel1Ownership11.DeclaringEntityType.GetForeignKeys()); - Assert.Single(bookLabel1Ownership21.DeclaringEntityType.GetForeignKeys()); - Assert.Single(bookLabel2Ownership11.DeclaringEntityType.GetForeignKeys()); - Assert.Single(bookLabel2Ownership21.DeclaringEntityType.GetForeignKeys()); - - Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(BookLabel))); - Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(AnotherBookLabel))); - Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(SpecialBookLabel))); - - Assert.Equal(ValueGenerated.Never, bookOwnership1.DeclaringEntityType.FindPrimaryKey().Properties.Single().ValueGenerated); - Assert.Equal(ValueGenerated.Never, bookOwnership2.DeclaringEntityType.FindPrimaryKey().Properties.Single().ValueGenerated); - - Assert.Equal( - ValueGenerated.Never, bookLabel1Ownership1.DeclaringEntityType.FindPrimaryKey().Properties.Single().ValueGenerated); - Assert.Equal( - ValueGenerated.Never, bookLabel1Ownership2.DeclaringEntityType.FindPrimaryKey().Properties.Single().ValueGenerated); - Assert.Equal( - ValueGenerated.Never, bookLabel2Ownership1.DeclaringEntityType.FindPrimaryKey().Properties.Single().ValueGenerated); - Assert.Equal( - ValueGenerated.Never, bookLabel2Ownership2.DeclaringEntityType.FindPrimaryKey().Properties.Single().ValueGenerated); - - Assert.Equal( - ValueGenerated.Never, bookLabel1Ownership11.DeclaringEntityType.FindPrimaryKey().Properties.Single().ValueGenerated); - Assert.Equal( - ValueGenerated.Never, bookLabel1Ownership21.DeclaringEntityType.FindPrimaryKey().Properties.Single().ValueGenerated); - Assert.Equal( - ValueGenerated.Never, bookLabel2Ownership11.DeclaringEntityType.FindPrimaryKey().Properties.Single().ValueGenerated); - Assert.Equal( - ValueGenerated.Never, bookLabel2Ownership21.DeclaringEntityType.FindPrimaryKey().Properties.Single().ValueGenerated); - } - - [ConditionalFact] - public virtual void Owned_type_collections_can_be_mapped_to_different_tables() - { - var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; - - modelBuilder.Entity().OwnsMany( - c => c.Orders, - r => - { - r.HasKey(o => o.OrderId); - r.IsMemoryOptimized(); - r.Ignore(o => o.OrderCombination); - r.Ignore(o => o.Details); - }); - - var ownership = model.FindEntityType(typeof(Customer)).FindNavigation(nameof(Customer.Orders)).ForeignKey; - var owned = ownership.DeclaringEntityType; - Assert.True(ownership.IsOwnership); - Assert.Equal(nameof(Order.Customer), ownership.DependentToPrincipal.Name); - Assert.Equal("FK_Order_Customer_CustomerId", ownership.GetConstraintName()); - - Assert.Single(owned.GetForeignKeys()); - Assert.Single(owned.GetIndexes()); - Assert.Equal( - new[] { nameof(Order.OrderId), nameof(Order.AnotherCustomerId), nameof(Order.CustomerId) }, - owned.GetProperties().Select(p => p.GetColumnBaseName())); - Assert.Equal(nameof(Order), owned.GetTableName()); - Assert.Null(owned.GetSchema()); - Assert.True(owned.IsMemoryOptimized()); - - modelBuilder.Entity().OwnsMany( - c => c.Orders, - r => - { - r.WithOwner(o => o.Customer).HasConstraintName("Owned"); - r.ToTable("bar", "foo"); - }); - - Assert.Equal("bar", owned.GetTableName()); - Assert.Equal("foo", owned.GetSchema()); - Assert.Equal("Owned", ownership.GetConstraintName()); - - modelBuilder.Entity().OwnsMany( - c => c.Orders, - r => r.ToTable("blah")); - - modelBuilder.FinalizeModel(); - - Assert.Equal("blah", owned.GetTableName()); - Assert.Null(owned.GetSchema()); - } - - [ConditionalFact] - public virtual void Owned_type_collections_can_be_mapped_to_a_view() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity().OwnsMany( - c => c.Orders, - r => - { - r.HasKey(o => o.OrderId); - r.Ignore(o => o.OrderCombination); - r.Ignore(o => o.Details); - r.ToView("bar", "foo"); - }); - - var model = modelBuilder.FinalizeModel(); - - var owner = model.FindEntityType(typeof(Customer)); - var ownership = owner.FindNavigation(nameof(Customer.Orders)).ForeignKey; - var owned = ownership.DeclaringEntityType; - Assert.True(ownership.IsOwnership); - Assert.Equal(nameof(Order.Customer), ownership.DependentToPrincipal.Name); - Assert.Empty(ownership.GetMappedConstraints()); - - Assert.Equal(nameof(Customer), owner.GetTableName()); - Assert.Null(owner.GetSchema()); - - Assert.Null(owned.GetForeignKeys().Single().GetConstraintName()); - Assert.Single(owned.GetIndexes()); - Assert.Null(owned.FindPrimaryKey().GetName()); - Assert.Equal( - new[] { nameof(Order.OrderId), nameof(Order.AnotherCustomerId), nameof(Order.CustomerId) }, - owned.GetProperties().Select(p => p.GetColumnBaseName())); - Assert.Null(owned.GetTableName()); - Assert.Null(owned.GetSchema()); - Assert.Equal("bar", owned.GetViewName()); - Assert.Equal("foo", owned.GetViewSchema()); - } - - [ConditionalFact] - public virtual void Owner_can_be_mapped_to_a_view() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Entity().OwnsMany( - c => c.Orders, - r => - { - r.HasKey(o => o.OrderId); - r.Ignore(o => o.OrderCombination); - r.Ignore(o => o.Details); - }) - .ToView("bar", "foo"); - - var model = modelBuilder.FinalizeModel(); - - var owner = model.FindEntityType(typeof(Customer)); - var ownership = owner.FindNavigation(nameof(Customer.Orders)).ForeignKey; - var owned = ownership.DeclaringEntityType; - Assert.True(ownership.IsOwnership); - Assert.Equal(nameof(Order.Customer), ownership.DependentToPrincipal.Name); - Assert.Empty(ownership.GetMappedConstraints()); - - Assert.Null(owner.GetTableName()); - Assert.Null(owner.GetSchema()); - Assert.Equal("bar", owner.GetViewName()); - Assert.Equal("foo", owner.GetViewSchema()); - - Assert.Null(owned.GetForeignKeys().Single().GetConstraintName()); - Assert.Equal("IX_Order_CustomerId", owned.GetIndexes().Single().GetDatabaseName()); - Assert.Equal("PK_Order", owned.FindPrimaryKey().GetName()); - Assert.Equal( - new[] { nameof(Order.OrderId), nameof(Order.AnotherCustomerId), nameof(Order.CustomerId) }, - owned.GetProperties().Select(p => p.GetColumnBaseName())); - Assert.Equal(nameof(Order), owned.GetTableName()); - Assert.Null(owned.GetSchema()); - } - - public override void Can_configure_owned_type() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Ignore(); - modelBuilder.Ignore(); - - var ownedBuilder = modelBuilder.Entity().OwnsOne(c => c.Details) - .ToTable("OtherCustomerDetails") - .HasCheckConstraint("CK_CustomerDetails_T", "AlternateKey <> 0", c => c.HasName("CK_Guid")); - ownedBuilder.Property(d => d.CustomerId); - ownedBuilder.HasIndex(d => d.CustomerId); - ownedBuilder.WithOwner(d => (OtherCustomer)d.Customer) - .HasPrincipalKey(c => c.AlternateKey); - - modelBuilder.Entity().OwnsOne( - c => c.Details, b => - { - b.ToTable("SpecialCustomerDetails"); - b.HasCheckConstraint("CK_CustomerDetails_T", "AlternateKey <> 0", c => c.HasName("CK_Guid")); - b.Property(d => d.CustomerId); - b.HasIndex(d => d.CustomerId); - b.WithOwner(d => (SpecialCustomer)d.Customer) - .HasPrincipalKey(c => c.AlternateKey); - }); - - var model = modelBuilder.FinalizeModel(); - - var owner1 = model.FindEntityType(typeof(OtherCustomer)); - Assert.Equal(typeof(OtherCustomer).FullName, owner1.Name); - AssertOwnership(owner1); - - var owner2 = model.FindEntityType(typeof(SpecialCustomer)); - Assert.Equal(typeof(SpecialCustomer).FullName, owner2.Name); - AssertOwnership(owner2); - - Assert.Null(model.FindEntityType(typeof(CustomerDetails))); - Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(CustomerDetails))); - - static void AssertOwnership(IEntityType owner) - { - var ownership1 = owner.FindNavigation(nameof(Customer.Details)).ForeignKey; - Assert.True(ownership1.IsOwnership); - Assert.Equal(nameof(Customer.Details), ownership1.PrincipalToDependent.Name); - Assert.Equal("CustomerAlternateKey", ownership1.Properties.Single().Name); - Assert.Equal(nameof(Customer.AlternateKey), ownership1.PrincipalKey.Properties.Single().Name); - var owned = ownership1.DeclaringEntityType; - Assert.Equal(owner.ShortName() + "Details", owned.GetTableName()); - var checkConstraint = owned.GetCheckConstraints().Single(); - Assert.Same(owned, checkConstraint.EntityType); - Assert.Equal("CK_CustomerDetails_T", checkConstraint.ModelName); - Assert.Equal("AlternateKey <> 0", checkConstraint.Sql); - Assert.Equal("CK_Guid", checkConstraint.Name); - Assert.Single(owned.GetForeignKeys()); - var index = owned.GetIndexes().Single(); - Assert.Same(owned, index.DeclaringEntityType); - Assert.Equal(nameof(CustomerDetails.CustomerId), index.Properties.Single().Name); - Assert.Equal( - new[] { "CustomerAlternateKey", nameof(CustomerDetails.CustomerId), nameof(CustomerDetails.Id) }, - owned.GetProperties().Select(p => p.Name)); - } - } - - public override void Can_configure_owned_type_key() - { - var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; - - modelBuilder.Entity().OwnsOne(c => c.Details) - .ToTable("Details") - .HasKey(c => c.Id); - - modelBuilder.FinalizeModel(); - - var owner = model.FindEntityType(typeof(Customer)); - var owned = owner.FindNavigation(nameof(Customer.Details)).ForeignKey.DeclaringEntityType; - Assert.Equal( - new[] { nameof(CustomerDetails.Id), nameof(CustomerDetails.CustomerId) }, - owned.GetProperties().Select(p => p.Name).ToArray()); - Assert.Equal(nameof(CustomerDetails.Id), owned.FindPrimaryKey().Properties.Single().Name); - } - - [ConditionalFact] - public virtual void Temporal_table_default_settings() - { - var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; - - modelBuilder.Entity().ToTable(tb => - { - tb.IsTemporal(); - Assert.Null(tb.Name); - Assert.Null(tb.Schema); - }); - modelBuilder.FinalizeModel(); - - var entity = model.FindEntityType(typeof(Customer)); - Assert.True(entity.IsTemporal()); - Assert.Equal("CustomerHistory", entity.GetHistoryTableName()); - Assert.Null(entity.GetHistoryTableSchema()); - - var periodStart = entity.GetProperty(entity.GetPeriodStartPropertyName()); - var periodEnd = entity.GetProperty(entity.GetPeriodEndPropertyName()); - - Assert.Equal("PeriodStart", periodStart.Name); - Assert.True(periodStart.IsShadowProperty()); - Assert.Equal(typeof(DateTime), periodStart.ClrType); - Assert.Equal(ValueGenerated.OnAddOrUpdate, periodStart.ValueGenerated); - - Assert.Equal("PeriodEnd", periodEnd.Name); - Assert.True(periodEnd.IsShadowProperty()); - Assert.Equal(typeof(DateTime), periodEnd.ClrType); - Assert.Equal(ValueGenerated.OnAddOrUpdate, periodEnd.ValueGenerated); - } - - [ConditionalFact] - public virtual void Temporal_table_with_history_table_configuration() - { - var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; - - modelBuilder.Entity().ToTable( - tb => tb.IsTemporal( - ttb => - { - ttb.UseHistoryTable("HistoryTable", "historySchema"); - ttb.HasPeriodStart("MyPeriodStart").HasColumnName("PeriodStartColumn"); - ttb.HasPeriodEnd("MyPeriodEnd").HasColumnName("PeriodEndColumn"); - })); - - modelBuilder.FinalizeModel(); - - var entity = model.FindEntityType(typeof(Customer)); - Assert.True(entity.IsTemporal()); - Assert.Equal(5, entity.GetProperties().Count()); - - Assert.Equal("HistoryTable", entity.GetHistoryTableName()); - Assert.Equal("historySchema", entity.GetHistoryTableSchema()); - - var periodStart = entity.GetProperty(entity.GetPeriodStartPropertyName()); - var periodEnd = entity.GetProperty(entity.GetPeriodEndPropertyName()); - - Assert.Equal("MyPeriodStart", periodStart.Name); - Assert.Equal("PeriodStartColumn", periodStart[RelationalAnnotationNames.ColumnName]); - Assert.True(periodStart.IsShadowProperty()); - Assert.Equal(typeof(DateTime), periodStart.ClrType); - Assert.Equal(ValueGenerated.OnAddOrUpdate, periodStart.ValueGenerated); - - Assert.Equal("MyPeriodEnd", periodEnd.Name); - Assert.Equal("PeriodEndColumn", periodEnd[RelationalAnnotationNames.ColumnName]); - Assert.True(periodEnd.IsShadowProperty()); - Assert.Equal(typeof(DateTime), periodEnd.ClrType); - Assert.Equal(ValueGenerated.OnAddOrUpdate, periodEnd.ValueGenerated); - } - - [ConditionalFact] - public virtual void Temporal_table_with_changed_configuration() - { - var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; - - modelBuilder.Entity().ToTable( - tb => tb.IsTemporal( - ttb => - { - ttb.UseHistoryTable("HistoryTable", "historySchema"); - ttb.HasPeriodStart("MyPeriodStart"); - ttb.HasPeriodEnd("MyPeriodEnd"); - })); - - modelBuilder.Entity().ToTable( - tb => tb.IsTemporal( - ttb => - { - ttb.UseHistoryTable("ChangedHistoryTable", "changedHistorySchema"); - ttb.HasPeriodStart("ChangedMyPeriodStart"); - ttb.HasPeriodEnd("ChangedMyPeriodEnd"); - })); - - modelBuilder.FinalizeModel(); - - var entity = model.FindEntityType(typeof(Customer)); - Assert.True(entity.IsTemporal()); - Assert.Equal(5, entity.GetProperties().Count()); - - Assert.Equal("ChangedHistoryTable", entity.GetHistoryTableName()); - Assert.Equal("changedHistorySchema", entity.GetHistoryTableSchema()); - - var periodStart = entity.GetProperty(entity.GetPeriodStartPropertyName()); - var periodEnd = entity.GetProperty(entity.GetPeriodEndPropertyName()); - - Assert.Equal("ChangedMyPeriodStart", periodStart.Name); - Assert.Equal("ChangedMyPeriodStart", periodStart[RelationalAnnotationNames.ColumnName]); - Assert.True(periodStart.IsShadowProperty()); - Assert.Equal(typeof(DateTime), periodStart.ClrType); - Assert.Equal(ValueGenerated.OnAddOrUpdate, periodStart.ValueGenerated); - - Assert.Equal("ChangedMyPeriodEnd", periodEnd.Name); - Assert.Equal("ChangedMyPeriodEnd", periodEnd[RelationalAnnotationNames.ColumnName]); - Assert.True(periodEnd.IsShadowProperty()); - Assert.Equal(typeof(DateTime), periodEnd.ClrType); - Assert.Equal(ValueGenerated.OnAddOrUpdate, periodEnd.ValueGenerated); - } - - [ConditionalFact] - public virtual void Temporal_table_with_period_column_names_changed_configuration() - { - var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; - - modelBuilder.Entity().ToTable( - tb => tb.IsTemporal( - ttb => - { - ttb.UseHistoryTable("HistoryTable", "historySchema"); - ttb.HasPeriodStart("MyPeriodStart").HasColumnName("PeriodStartColumn"); - ttb.HasPeriodEnd("MyPeriodEnd").HasColumnName("PeriodEndColumn"); - })); - - modelBuilder.Entity().ToTable( - tb => tb.IsTemporal( - ttb => - { - ttb.UseHistoryTable("ChangedHistoryTable", "changedHistorySchema"); - ttb.HasPeriodStart("MyPeriodStart").HasColumnName("ChangedPeriodStartColumn"); - ttb.HasPeriodEnd("MyPeriodEnd").HasColumnName("ChangedPeriodEndColumn"); - })); - - modelBuilder.FinalizeModel(); - - var entity = model.FindEntityType(typeof(Customer)); - Assert.True(entity.IsTemporal()); - Assert.Equal(5, entity.GetProperties().Count()); - - Assert.Equal("ChangedHistoryTable", entity.GetHistoryTableName()); - Assert.Equal("changedHistorySchema", entity.GetHistoryTableSchema()); - - var periodStart = entity.GetProperty(entity.GetPeriodStartPropertyName()); - var periodEnd = entity.GetProperty(entity.GetPeriodEndPropertyName()); - - Assert.Equal("MyPeriodStart", periodStart.Name); - Assert.Equal("ChangedPeriodStartColumn", periodStart[RelationalAnnotationNames.ColumnName]); - Assert.True(periodStart.IsShadowProperty()); - Assert.Equal(typeof(DateTime), periodStart.ClrType); - Assert.Equal(ValueGenerated.OnAddOrUpdate, periodStart.ValueGenerated); - - Assert.Equal("MyPeriodEnd", periodEnd.Name); - Assert.Equal("ChangedPeriodEndColumn", periodEnd[RelationalAnnotationNames.ColumnName]); - Assert.True(periodEnd.IsShadowProperty()); - Assert.Equal(typeof(DateTime), periodEnd.ClrType); - Assert.Equal(ValueGenerated.OnAddOrUpdate, periodEnd.ValueGenerated); - } - - [ConditionalFact] - public virtual void Temporal_table_with_explicit_properties_mapped_to_the_period_columns() - { - var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; - - modelBuilder.Entity().ToTable( - tb => tb.IsTemporal( - ttb => - { - ttb.UseHistoryTable("HistoryTable", schema: null); - ttb.HasPeriodStart("Start").HasColumnName("PeriodStartColumn"); - ttb.HasPeriodEnd("End").HasColumnName("PeriodEndColumn"); - })); - - modelBuilder.Entity() - .Property("MappedStart") - .HasColumnName("PeriodStartColumn") - .ValueGeneratedOnAddOrUpdate(); - - modelBuilder.Entity() - .Property("MappedEnd") - .HasColumnName("PeriodEndColumn") - .ValueGeneratedOnAddOrUpdate(); - - modelBuilder.FinalizeModel(); - - var entity = model.FindEntityType(typeof(Customer)); - Assert.True(entity.IsTemporal()); - Assert.Equal(7, entity.GetProperties().Count()); - - Assert.Equal("HistoryTable", entity.GetHistoryTableName()); - - var periodStart = entity.GetProperty(entity.GetPeriodStartPropertyName()); - var periodEnd = entity.GetProperty(entity.GetPeriodEndPropertyName()); - - Assert.Equal("Start", periodStart.Name); - Assert.Equal("PeriodStartColumn", periodStart[RelationalAnnotationNames.ColumnName]); - Assert.True(periodStart.IsShadowProperty()); - Assert.Equal(typeof(DateTime), periodStart.ClrType); - Assert.Equal(ValueGenerated.OnAddOrUpdate, periodStart.ValueGenerated); - - Assert.Equal("End", periodEnd.Name); - Assert.Equal("PeriodEndColumn", periodEnd[RelationalAnnotationNames.ColumnName]); - Assert.True(periodEnd.IsShadowProperty()); - Assert.Equal(typeof(DateTime), periodEnd.ClrType); - Assert.Equal(ValueGenerated.OnAddOrUpdate, periodEnd.ValueGenerated); - - var propertyMappedToStart = entity.GetProperty("MappedStart"); - Assert.Equal("PeriodStartColumn", propertyMappedToStart[RelationalAnnotationNames.ColumnName]); - - var propertyMappedToEnd = entity.GetProperty("MappedEnd"); - Assert.Equal("PeriodEndColumn", propertyMappedToEnd[RelationalAnnotationNames.ColumnName]); - } - - [ConditionalFact] - public virtual void - Temporal_table_with_explicit_properties_with_same_name_as_default_periods_but_different_periods_defined_explicity_as_well() - { - var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; - - modelBuilder.Entity() - .Property("PeriodStart") - .HasColumnName("PeriodStartColumn"); - - modelBuilder.Entity() - .Property("PeriodEnd") - .HasColumnName("PeriodEndColumn"); - - modelBuilder.Entity().ToTable( - tb => tb.IsTemporal( - ttb => - { - ttb.UseHistoryTable("HistoryTable", schema: null); - ttb.HasPeriodStart("Start"); - ttb.HasPeriodEnd("End"); - })); - - modelBuilder.FinalizeModel(); - - var entity = model.FindEntityType(typeof(Customer)); - Assert.True(entity.IsTemporal()); - Assert.Equal(7, entity.GetProperties().Count()); - - Assert.Equal("HistoryTable", entity.GetHistoryTableName()); - - var periodStart = entity.GetProperty(entity.GetPeriodStartPropertyName()); - var periodEnd = entity.GetProperty(entity.GetPeriodEndPropertyName()); - - Assert.Equal("Start", periodStart.Name); - Assert.Equal("Start", periodStart[RelationalAnnotationNames.ColumnName]); - Assert.True(periodStart.IsShadowProperty()); - Assert.Equal(typeof(DateTime), periodStart.ClrType); - Assert.Equal(ValueGenerated.OnAddOrUpdate, periodStart.ValueGenerated); - - Assert.Equal("End", periodEnd.Name); - Assert.Equal("End", periodEnd[RelationalAnnotationNames.ColumnName]); - Assert.True(periodEnd.IsShadowProperty()); - Assert.Equal(typeof(DateTime), periodEnd.ClrType); - Assert.Equal(ValueGenerated.OnAddOrUpdate, periodEnd.ValueGenerated); - - var propertyMappedToStart = entity.GetProperty("PeriodStart"); - Assert.Equal("PeriodStartColumn", propertyMappedToStart[RelationalAnnotationNames.ColumnName]); - Assert.Equal(ValueGenerated.Never, propertyMappedToStart.ValueGenerated); - - var propertyMappedToEnd = entity.GetProperty("PeriodEnd"); - Assert.Equal("PeriodEndColumn", propertyMappedToEnd[RelationalAnnotationNames.ColumnName]); - Assert.Equal(ValueGenerated.Never, propertyMappedToEnd.ValueGenerated); - } - - [ConditionalFact] - public virtual void Switching_from_temporal_to_non_temporal_default_settings() - { - var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; - - modelBuilder.Entity().ToTable(tb => tb.IsTemporal()); - modelBuilder.Entity().ToTable(tb => tb.IsTemporal(false)); - - modelBuilder.FinalizeModel(); - - var entity = model.FindEntityType(typeof(Customer)); - Assert.False(entity.IsTemporal()); - Assert.Null(entity.GetPeriodStartPropertyName()); - Assert.Null(entity.GetPeriodEndPropertyName()); - Assert.Equal(3, entity.GetProperties().Count()); - } - - [ConditionalFact] - public virtual void Implicit_many_to_many_converted_from_non_temporal_to_temporal() - { - var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; - - modelBuilder.Entity(); - modelBuilder.Entity(); - - modelBuilder.Entity().ToTable(tb => tb.IsTemporal()); - modelBuilder.Entity().ToTable(tb => tb.IsTemporal()); - - modelBuilder.FinalizeModel(); - - var entity = model.FindEntityType(typeof(ImplicitManyToManyA)); - var joinEntity = entity.GetSkipNavigations().Single().JoinEntityType; - - Assert.True(joinEntity.IsTemporal()); - } - - protected override TestModelBuilder CreateModelBuilder(Action configure = null) - => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderGenericTest.GenericTestModelBuilder(testHelpers, configure); } - public abstract class TestTemporalTableBuilder - where TEntity : class + public class SqlServerGenericManyToOne : SqlServerManyToOne { - public abstract TestTemporalTableBuilder UseHistoryTable(string name, string schema); - public abstract TestTemporalPeriodPropertyBuilder HasPeriodStart(string propertyName); - public abstract TestTemporalPeriodPropertyBuilder HasPeriodEnd(string propertyName); + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderGenericTest.GenericTestModelBuilder(testHelpers, configure); } - public class GenericTestTemporalTableBuilder : TestTemporalTableBuilder, - IInfrastructure> - where TEntity : class + public class SqlServerGenericOneToOne : SqlServerOneToOne { - public GenericTestTemporalTableBuilder(TemporalTableBuilder temporalTableBuilder) - { - TemporalTableBuilder = temporalTableBuilder; - } - - private TemporalTableBuilder TemporalTableBuilder { get; } - - TemporalTableBuilder IInfrastructure>.Instance - => TemporalTableBuilder; - - protected virtual TestTemporalTableBuilder Wrap(TemporalTableBuilder tableBuilder) - => new GenericTestTemporalTableBuilder(tableBuilder); - - public override TestTemporalTableBuilder UseHistoryTable(string name, string schema) - => Wrap(TemporalTableBuilder.UseHistoryTable(name, schema)); - - public override TestTemporalPeriodPropertyBuilder HasPeriodStart(string propertyName) - => new(TemporalTableBuilder.HasPeriodStart(propertyName)); - - public override TestTemporalPeriodPropertyBuilder HasPeriodEnd(string propertyName) - => new(TemporalTableBuilder.HasPeriodEnd(propertyName)); + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderGenericTest.GenericTestModelBuilder(testHelpers, configure); } - public class NonGenericTestTemporalTableBuilder : TestTemporalTableBuilder, IInfrastructure - where TEntity : class + public class SqlServerGenericManyToMany : SqlServerManyToMany { - public NonGenericTestTemporalTableBuilder(TemporalTableBuilder temporalTableBuilder) - { - TemporalTableBuilder = temporalTableBuilder; - } - - private TemporalTableBuilder TemporalTableBuilder { get; } - - TemporalTableBuilder IInfrastructure.Instance - => TemporalTableBuilder; - - protected virtual TestTemporalTableBuilder Wrap(TemporalTableBuilder temporalTableBuilder) - => new NonGenericTestTemporalTableBuilder(temporalTableBuilder); - - public override TestTemporalTableBuilder UseHistoryTable(string name, string schema) - => Wrap(TemporalTableBuilder.UseHistoryTable(name, schema)); - - public override TestTemporalPeriodPropertyBuilder HasPeriodStart(string propertyName) - => new(TemporalTableBuilder.HasPeriodStart(propertyName)); - - public override TestTemporalPeriodPropertyBuilder HasPeriodEnd(string propertyName) - => new(TemporalTableBuilder.HasPeriodEnd(propertyName)); + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderGenericTest.GenericTestModelBuilder(testHelpers, configure); } - public class TestTemporalPeriodPropertyBuilder + public class SqlServerGenericOwnedTypes : SqlServerOwnedTypes { - public TestTemporalPeriodPropertyBuilder(TemporalPeriodPropertyBuilder temporalPeriodPropertyBuilder) - { - TemporalPeriodPropertyBuilder = temporalPeriodPropertyBuilder; - } - - protected TemporalPeriodPropertyBuilder TemporalPeriodPropertyBuilder { get; } - - public TestTemporalPeriodPropertyBuilder HasColumnName(string name) - => new(TemporalPeriodPropertyBuilder.HasColumnName(name)); + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderGenericTest.GenericTestModelBuilder(testHelpers, configure); } } diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs new file mode 100644 index 00000000000..8d304107b5e --- /dev/null +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderNonGenericTest.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +// ReSharper disable InconsistentNaming +namespace Microsoft.EntityFrameworkCore.ModelBuilding; + +public class SqlServerModelBuilderNonGenericTest : SqlServerModelBuilderTestBase +{ + public class SqlServerNonGenericNonRelationship : SqlServerNonRelationship + { + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderNonGenericTest.NonGenericTestModelBuilder(testHelpers, configure); + } + + public class SqlServerNonGenericInheritance : SqlServerInheritance + { + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderNonGenericTest.NonGenericTestModelBuilder(testHelpers, configure); + } + + public class SqlServerNonGenericOneToMany : SqlServerOneToMany + { + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderNonGenericTest.NonGenericTestModelBuilder(testHelpers, configure); + } + + public class SqlServerNonGenericManyToOne : SqlServerManyToOne + { + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderNonGenericTest.NonGenericTestModelBuilder(testHelpers, configure); + } + + public class SqlServerNonGenericOneToOne : SqlServerOneToOne + { + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderNonGenericTest.NonGenericTestModelBuilder(testHelpers, configure); + } + + public class SqlServerNonGenericManyToMany : SqlServerManyToMany + { + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderNonGenericTest.NonGenericTestModelBuilder(testHelpers, configure); + } + + public class SqlServerNonGenericOwnedTypes : SqlServerOwnedTypes + { + protected override TestModelBuilder CreateTestModelBuilder( + TestHelpers testHelpers, + Action? configure) + => new ModelBuilderNonGenericTest.NonGenericTestModelBuilder(testHelpers, configure); + } +} diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs new file mode 100644 index 00000000000..0e63f7fb0fe --- /dev/null +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs @@ -0,0 +1,1498 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +// ReSharper disable InconsistentNaming +namespace Microsoft.EntityFrameworkCore.ModelBuilding; + +public class SqlServerModelBuilderTestBase : RelationalModelBuilderTest +{ + public abstract class SqlServerNonRelationship : RelationalNonRelationshipTestBase + { + [ConditionalFact] + public virtual void Index_has_a_filter_if_nonclustered_unique_with_nullable_properties() + { + var modelBuilder = CreateModelBuilder(); + var entityTypeBuilder = modelBuilder + .Entity(); + var indexBuilder = entityTypeBuilder + .HasIndex(ix => ix.Name) + .IsUnique(); + + var entityType = modelBuilder.Model.FindEntityType(typeof(Customer))!; + var index = entityType.GetIndexes().Single(); + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + indexBuilder.IsUnique(false); + + Assert.Null(index.GetFilter()); + + indexBuilder.IsUnique(); + + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + indexBuilder.IsClustered(); + + Assert.Null(index.GetFilter()); + + indexBuilder.IsClustered(false); + + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).IsRequired(); + + Assert.Null(index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).IsRequired(false); + + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).HasColumnName("RelationalName"); + + Assert.Equal("[RelationalName] IS NOT NULL", index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).HasColumnName("SqlServerName"); + + Assert.Equal("[SqlServerName] IS NOT NULL", index.GetFilter()); + + entityTypeBuilder.Property(e => e.Name).HasColumnName(null); + + Assert.Equal("[Name] IS NOT NULL", index.GetFilter()); + + indexBuilder.HasFilter("Foo"); + + Assert.Equal("Foo", index.GetFilter()); + + indexBuilder.HasFilter(null); + + Assert.Null(index.GetFilter()); + } + + [ConditionalFact] + public virtual void Can_set_store_type_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().HaveColumnType("smallint"); + c.Properties().HaveColumnType("nchar(max)"); + c.Properties(typeof(Nullable<>)).HavePrecision(2); + }); + + modelBuilder.Entity( + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(Quarks))!; + + Assert.Equal("smallint", entityType.FindProperty(Customer.IdProperty.Name)!.GetColumnType()); + Assert.Equal("smallint", entityType.FindProperty("Up")!.GetColumnType()); + Assert.Equal("nchar(max)", entityType.FindProperty("Down")!.GetColumnType()); + var charm = entityType.FindProperty("Charm")!; + Assert.Equal("smallint", charm.GetColumnType()); + Assert.Null(charm.GetPrecision()); + Assert.Equal("nchar(max)", entityType.FindProperty("Strange")!.GetColumnType()); + var top = entityType.FindProperty("Top")!; + Assert.Equal("smallint", top.GetColumnType()); + Assert.Equal(2, top.GetPrecision()); + Assert.Equal("nchar(max)", entityType.FindProperty("Bottom")!.GetColumnType()); + } + + [ConditionalFact] + public virtual void Can_set_fixed_length_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().AreFixedLength(false); + c.Properties().AreFixedLength(); + }); + + modelBuilder.Entity( + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(Quarks))!; + + Assert.False(entityType.FindProperty(Customer.IdProperty.Name)!.IsFixedLength()); + Assert.False(entityType.FindProperty("Up")!.IsFixedLength()); + Assert.True(entityType.FindProperty("Down")!.IsFixedLength()); + Assert.False(entityType.FindProperty("Charm")!.IsFixedLength()); + Assert.True(entityType.FindProperty("Strange")!.IsFixedLength()); + Assert.False(entityType.FindProperty("Top")!.IsFixedLength()); + Assert.True(entityType.FindProperty("Bottom")!.IsFixedLength()); + } + + [ConditionalFact] + public virtual void Can_set_collation_for_property_type() + { + var modelBuilder = CreateModelBuilder( + c => + { + c.Properties().UseCollation("Latin1_General_CS_AS_KS_WS"); + c.Properties().UseCollation("Latin1_General_BIN"); + }); + + modelBuilder.Entity( + b => + { + b.Property("Charm"); + b.Property("Strange"); + b.Property("Top"); + b.Property("Bottom"); + }); + + var model = modelBuilder.FinalizeModel(); + var entityType = model.FindEntityType(typeof(Quarks))!; + + Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty(Customer.IdProperty.Name)!.GetCollation()); + Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Up")!.GetCollation()); + Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Down")!.GetCollation()); + Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Charm")!.GetCollation()); + Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Strange")!.GetCollation()); + Assert.Equal("Latin1_General_CS_AS_KS_WS", entityType.FindProperty("Top")!.GetCollation()); + Assert.Equal("Latin1_General_BIN", entityType.FindProperty("Bottom")!.GetCollation()); + } + + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) + => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); + } + + public abstract class SqlServerInheritance : RelationalInheritanceTestBase + { + [ConditionalFact] // #7240 + public void Can_use_shadow_FK_that_collides_with_convention_shadow_FK_on_other_derived_type() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity(); + modelBuilder.Entity() + .HasOne(p => p.A) + .WithOne() + .HasForeignKey("ParentId"); + + var model = modelBuilder.FinalizeModel(); + + var property1 = model.FindEntityType(typeof(DisjointChildSubclass1))!.FindProperty("ParentId")!; + Assert.True(property1.IsForeignKey()); + Assert.Equal("ParentId", property1.GetColumnName()); + var property2 = model.FindEntityType(typeof(DisjointChildSubclass2))!.FindProperty("ParentId")!; + Assert.True(property2.IsForeignKey()); + Assert.Equal("DisjointChildSubclass2_ParentId", property2.GetColumnName()); + } + + [ConditionalFact] + public void Inherited_clr_properties_are_mapped_to_the_same_column() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity(); + modelBuilder.Ignore(); + modelBuilder.Entity(); + modelBuilder.Entity(); + + var model = modelBuilder.FinalizeModel(); + + var property1 = model.FindEntityType(typeof(DisjointChildSubclass1))!.FindProperty(nameof(Child.Name))!; + Assert.Equal(nameof(Child.Name), property1.GetColumnName()); + var property2 = model.FindEntityType(typeof(DisjointChildSubclass2))!.FindProperty(nameof(Child.Name))!; + Assert.Equal(nameof(Child.Name), property2.GetColumnName()); + } + + [ConditionalFact] //Issue#10659 + public void Index_convention_run_for_fk_when_derived_type_discovered_before_base_type() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Ignore(); + modelBuilder.Entity(); + modelBuilder.Entity(); + + var index = modelBuilder.Model.FindEntityType(typeof(CustomerDetails))!.GetIndexes().Single(); + + Assert.Equal("[CustomerId] IS NOT NULL", index.GetFilter()); + } + + [ConditionalFact] + public void Index_convention_sets_filter_for_unique_index_when_base_type_changed() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Ignore(); + modelBuilder.Entity() + .HasIndex(e => e.CustomerId) + .IsUnique(); + + modelBuilder.Entity(); + + var index = modelBuilder.Model.FindEntityType(typeof(CustomerDetails))!.GetIndexes().Single(); + + Assert.Equal("[CustomerId] IS NOT NULL", index.GetFilter()); + + modelBuilder.Ignore(); + + Assert.Null(index.GetFilter()); + } + + [ConditionalFact] + public virtual void Can_override_TPC_with_TPH() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity

(); + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity() + .UseTpcMappingStrategy() + .UseTphMappingStrategy(); + + var model = modelBuilder.FinalizeModel(); + + Assert.Equal("Discriminator", model.FindEntityType(typeof(PBase))!.GetDiscriminatorPropertyName()); + Assert.Equal(nameof(PBase), model.FindEntityType(typeof(PBase))!.GetDiscriminatorValue()); + Assert.Equal(nameof(P), model.FindEntityType(typeof(P))!.GetDiscriminatorValue()); + Assert.Equal(nameof(Q), model.FindEntityType(typeof(Q))!.GetDiscriminatorValue()); + } + + [ConditionalFact] + public virtual void TPT_identifying_FK_is_created_only_on_declaring_table() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity() + .Ignore(b => b.Bun) + .Ignore(b => b.Pickles); + modelBuilder.Entity( + b => + { + b.ToTable("Ingredients"); + b.Ignore(i => i.BigMak); + }); + modelBuilder.Entity( + b => + { + b.ToTable("Buns"); + b.HasOne(i => i.BigMak).WithOne().HasForeignKey(i => i.Id); + }); + modelBuilder.Entity( + b => + { + b.ToTable("SesameBuns"); + }); + + var model = modelBuilder.FinalizeModel(); + + var principalType = model.FindEntityType(typeof(BigMak))!; + Assert.Empty(principalType.GetForeignKeys()); + Assert.Empty(principalType.GetIndexes()); + Assert.Null(principalType.FindDiscriminatorProperty()); + + var ingredientType = model.FindEntityType(typeof(Ingredient))!; + + var bunType = model.FindEntityType(typeof(Bun))!; + Assert.Empty(bunType.GetIndexes()); + Assert.Null(bunType.FindDiscriminatorProperty()); + var bunFk = bunType.GetDeclaredForeignKeys().Single(fk => !fk.IsBaseLinking()); + Assert.Equal("FK_Buns_BigMak_Id", bunFk.GetConstraintName()); + Assert.Equal( + "FK_Buns_BigMak_Id", bunFk.GetConstraintName( + StoreObjectIdentifier.Create(bunType, StoreObjectType.Table)!.Value, + StoreObjectIdentifier.Create(principalType, StoreObjectType.Table)!.Value)); + Assert.Single(bunFk.GetMappedConstraints()); + + var bunLinkingFk = bunType.GetDeclaredForeignKeys().Single(fk => fk.IsBaseLinking()); + Assert.Equal("FK_Buns_Ingredients_Id", bunLinkingFk.GetConstraintName()); + Assert.Equal( + "FK_Buns_Ingredients_Id", bunLinkingFk.GetConstraintName( + StoreObjectIdentifier.Create(bunType, StoreObjectType.Table)!.Value, + StoreObjectIdentifier.Create(ingredientType, StoreObjectType.Table)!.Value)); + Assert.Single(bunLinkingFk.GetMappedConstraints()); + + var sesameBunType = model.FindEntityType(typeof(SesameBun))!; + Assert.Empty(sesameBunType.GetIndexes()); + var sesameBunFk = sesameBunType.GetDeclaredForeignKeys().Single(); + Assert.True(sesameBunFk.IsBaseLinking()); + Assert.Equal("FK_SesameBuns_Buns_Id", sesameBunFk.GetConstraintName()); + Assert.Equal( + "FK_SesameBuns_Buns_Id", sesameBunFk.GetConstraintName( + StoreObjectIdentifier.Create(sesameBunType, StoreObjectType.Table)!.Value, + StoreObjectIdentifier.Create(bunType, StoreObjectType.Table)!.Value)); + Assert.Single(sesameBunFk.GetMappedConstraints()); + } + + [ConditionalFact] + public virtual void TPC_identifying_FKs_are_created_on_all_tables() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity() + .Ignore(b => b.Bun) + .Ignore(b => b.Pickles); + modelBuilder.Entity( + b => + { + b.ToTable("Ingredients"); + b.Ignore(i => i.BigMak); + b.UseTpcMappingStrategy(); + }); + modelBuilder.Entity( + b => + { + b.ToTable("Buns"); + b.HasOne(i => i.BigMak).WithOne().HasForeignKey(i => i.Id); + b.UseTpcMappingStrategy(); + }); + modelBuilder.Entity( + b => + { + b.ToTable("SesameBuns"); + }); + + var model = modelBuilder.FinalizeModel(); + + var principalType = model.FindEntityType(typeof(BigMak))!; + Assert.Empty(principalType.GetForeignKeys()); + Assert.Empty(principalType.GetIndexes()); + Assert.Null(principalType.FindDiscriminatorProperty()); + + var ingredientType = model.FindEntityType(typeof(Ingredient)); + + var bunType = model.FindEntityType(typeof(Bun))!; + Assert.Empty(bunType.GetIndexes()); + Assert.Null(bunType.FindDiscriminatorProperty()); + var bunFk = bunType.GetDeclaredForeignKeys().Single(); + Assert.Equal("FK_Buns_BigMak_Id", bunFk.GetConstraintName()); + Assert.Equal( + "FK_Buns_BigMak_Id", bunFk.GetConstraintName( + StoreObjectIdentifier.Create(bunType, StoreObjectType.Table)!.Value, + StoreObjectIdentifier.Create(principalType, StoreObjectType.Table)!.Value)); + Assert.Equal(2, bunFk.GetMappedConstraints().Count()); + + Assert.Empty(bunType.GetDeclaredForeignKeys().Where(fk => fk.IsBaseLinking())); + + var sesameBunType = model.FindEntityType(typeof(SesameBun))!; + Assert.Empty(sesameBunType.GetIndexes()); + Assert.Empty(sesameBunType.GetDeclaredForeignKeys()); + Assert.Equal( + "FK_SesameBuns_BigMak_Id", bunFk.GetConstraintName( + StoreObjectIdentifier.Create(sesameBunType, StoreObjectType.Table)!.Value, + StoreObjectIdentifier.Create(principalType, StoreObjectType.Table)!.Value)); + } + + [ConditionalFact] + public virtual void TPT_index_can_use_inherited_properties() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity() + .Ignore(b => b.Bun) + .Ignore(b => b.Pickles); + modelBuilder.Entity( + b => + { + b.ToTable("Ingredients"); + b.Property("NullableProp"); + b.Ignore(i => i.BigMak); + }); + modelBuilder.Entity( + b => + { + b.ToTable("Buns"); + b.HasIndex(bun => bun.BurgerId); + b.HasIndex("NullableProp"); + b.HasOne(i => i.BigMak).WithOne().HasForeignKey(i => i.Id); + }); + + var model = modelBuilder.FinalizeModel(); + + var bunType = model.FindEntityType(typeof(Bun))!; + Assert.All(bunType.GetIndexes(), i => Assert.Null(i.GetFilter())); + } + + [ConditionalFact] + public void Can_add_check_constraints() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity() + .HasBaseType(null) + .HasCheckConstraint("CK_ChildBase_LargeId", "Id > 1000", c => c.HasName("CK_LargeId")); + modelBuilder.Entity() + .HasCheckConstraint("PositiveId", "Id > 0") + .HasCheckConstraint("CK_ChildBase_LargeId", "Id > 1000"); + modelBuilder.Entity() + .HasBaseType(); + modelBuilder.Entity(); + + var model = modelBuilder.FinalizeModel(); + + var @base = model.FindEntityType(typeof(ChildBase))!; + Assert.Equal(2, @base.GetCheckConstraints().Count()); + + var firstCheckConstraint = @base.FindCheckConstraint("PositiveId")!; + Assert.Equal("PositiveId", firstCheckConstraint.ModelName); + Assert.Equal("Id > 0", firstCheckConstraint.Sql); + Assert.Equal("PositiveId", firstCheckConstraint.Name); + + var secondCheckConstraint = @base.FindCheckConstraint("CK_ChildBase_LargeId")!; + Assert.Equal("CK_ChildBase_LargeId", secondCheckConstraint.ModelName); + Assert.Equal("Id > 1000", secondCheckConstraint.Sql); + Assert.Equal("CK_LargeId", secondCheckConstraint.Name); + + var child = model.FindEntityType(typeof(Child))!; + Assert.Equal(@base.GetCheckConstraints(), child.GetCheckConstraints()); + Assert.Empty(child.GetDeclaredCheckConstraints()); + } + + [ConditionalFact] + public void Adding_conflicting_check_constraint_to_derived_type_throws() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity() + .HasCheckConstraint("LargeId", "Id > 100", c => c.HasName("CK_LargeId")); + + Assert.Equal( + RelationalStrings.DuplicateCheckConstraint("LargeId", nameof(Child), nameof(ChildBase)), + Assert.Throws( + () => modelBuilder.Entity().HasCheckConstraint("LargeId", "Id > 1000")).Message); + } + + [ConditionalFact] + public void Adding_conflicting_check_constraint_to_derived_type_before_base_throws() + { + var modelBuilder = CreateModelBuilder(); + modelBuilder.Entity() + .HasBaseType(null) + .HasCheckConstraint("LargeId", "Id > 1000"); + modelBuilder.Entity() + .HasCheckConstraint("LargeId", "Id > 100", c => c.HasName("CK_LargeId")); + + Assert.Equal( + RelationalStrings.DuplicateCheckConstraint("LargeId", nameof(Child), nameof(ChildBase)), + Assert.Throws( + () => modelBuilder.Entity().HasBaseType()).Message); + } + + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) + => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); + + public class Parent + { + public int Id { get; set; } + public DisjointChildSubclass1? A { get; set; } + public IList? B { get; set; } + } + + public abstract class ChildBase + { + public int Id { get; set; } + } + + public abstract class Child : ChildBase + { + public string? Name { get; set; } + } + + public class DisjointChildSubclass1 : Child + { + } + + public class DisjointChildSubclass2 : Child + { + } + } + + public abstract class SqlServerOneToMany : RelationalOneToManyTestBase + { + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) + => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); + } + + public abstract class SqlServerManyToOne : RelationalManyToOneTestBase + { + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) + => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); + } + + public abstract class SqlServerOneToOne : RelationalOneToOneTestBase + { + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) + => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); + } + + public abstract class SqlServerManyToMany : RelationalManyToManyTestBase + { + [ConditionalFact] + public virtual void Join_entity_type_uses_same_schema() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().ToTable("Category", "mySchema").Ignore(c => c.ProductCategories); + modelBuilder.Entity().ToTable("Product", "mySchema"); + modelBuilder.Entity(); + + var model = modelBuilder.FinalizeModel(); + + var productType = model.FindEntityType(typeof(Product))!; + var categoryType = model.FindEntityType(typeof(Category))!; + + var categoriesNavigation = productType.GetSkipNavigations().Single(); + var productsNavigation = categoryType.GetSkipNavigations().Single(); + + var categoriesFk = categoriesNavigation.ForeignKey; + var productsFk = productsNavigation.ForeignKey; + var productCategoryType = categoriesFk.DeclaringEntityType; + + Assert.Equal(typeof(Dictionary), productCategoryType.ClrType); + Assert.Equal("mySchema", productCategoryType.GetSchema()); + Assert.Same(categoriesFk, productCategoryType.GetForeignKeys().Last()); + Assert.Same(productsFk, productCategoryType.GetForeignKeys().First()); + Assert.Equal(2, productCategoryType.GetForeignKeys().Count()); + } + + [ConditionalFact] + public virtual void Join_entity_type_uses_default_schema_if_related_are_different() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().ToTable("Category").Ignore(c => c.ProductCategories); + modelBuilder.Entity().ToTable("Product", "dbo"); + modelBuilder.Entity(); + + var model = modelBuilder.FinalizeModel(); + + var productType = model.FindEntityType(typeof(Product))!; + var categoryType = model.FindEntityType(typeof(Category))!; + + var categoriesNavigation = productType.GetSkipNavigations().Single(); + var productsNavigation = categoryType.GetSkipNavigations().Single(); + + var categoriesFk = categoriesNavigation.ForeignKey; + var productsFk = productsNavigation.ForeignKey; + var productCategoryType = categoriesFk.DeclaringEntityType; + + Assert.Equal(typeof(Dictionary), productCategoryType.ClrType); + Assert.Null(productCategoryType.GetSchema()); + Assert.Same(categoriesFk, productCategoryType.GetForeignKeys().Last()); + Assert.Same(productsFk, productCategoryType.GetForeignKeys().First()); + Assert.Equal(2, productCategoryType.GetForeignKeys().Count()); + } + + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) + => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); + } + + public abstract class SqlServerOwnedTypes : RelationalOwnedTypesTestBase + { + [ConditionalFact] + public virtual void Owned_types_use_table_splitting_by_default() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().OwnsOne( + b => b.AlternateLabel, + b => + { + b.Ignore(l => l.Book); + b.OwnsOne( + l => l.AnotherBookLabel, + ab => + { + ab.Property(l => l.BookId).HasColumnName("BookId2"); + ab.Ignore(l => l.Book); + ab.OwnsOne( + s => s.SpecialBookLabel, + s => + { + s.Property(l => l.BookId).HasColumnName("BookId2"); + s.Ignore(l => l.Book); + s.Ignore(l => l.BookLabel); + }); + }); + }); + + modelBuilder.Entity().OwnsOne(b => b.Label) + .Ignore(l => l.Book) + .OwnsOne(l => l.SpecialBookLabel) + .Ignore(l => l.Book) + .OwnsOne(a => a.AnotherBookLabel) + .Ignore(l => l.Book); + + modelBuilder.Entity().OwnsOne(b => b.Label) + .OwnsOne(l => l.AnotherBookLabel) + .Ignore(l => l.Book) + .OwnsOne(a => a.SpecialBookLabel) + .Ignore(l => l.Book) + .Ignore(l => l.BookLabel); + + modelBuilder.Entity().OwnsOne( + b => b.AlternateLabel, + b => + { + b.Ignore(l => l.Book); + b.OwnsOne( + l => l.SpecialBookLabel, + ab => + { + ab.Property(l => l.BookId).HasColumnName("BookId2"); + ab.Ignore(l => l.Book); + ab.OwnsOne( + s => s.AnotherBookLabel, + s => + { + s.Property(l => l.BookId).HasColumnName("BookId2"); + s.Ignore(l => l.Book); + }); + }); + }); + + var model = (IModel)modelBuilder.Model; + var book = model.FindEntityType(typeof(Book))!; + var bookOwnership1 = book.FindNavigation(nameof(Book.Label))!.ForeignKey; + var bookOwnership2 = book.FindNavigation(nameof(Book.AlternateLabel))!.ForeignKey; + var bookLabel1Ownership1 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))!.ForeignKey; + var bookLabel1Ownership2 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))!.ForeignKey; + var bookLabel2Ownership1 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))!.ForeignKey; + var bookLabel2Ownership2 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))!.ForeignKey; + + Assert.Equal(book.GetTableName(), bookOwnership1.DeclaringEntityType.GetTableName()); + Assert.Equal(book.GetTableName(), bookOwnership2.DeclaringEntityType.GetTableName()); + Assert.Equal(book.GetTableName(), bookLabel1Ownership1.DeclaringEntityType.GetTableName()); + Assert.Equal(book.GetTableName(), bookLabel1Ownership2.DeclaringEntityType.GetTableName()); + Assert.Equal(book.GetTableName(), bookLabel2Ownership1.DeclaringEntityType.GetTableName()); + Assert.Equal(book.GetTableName(), bookLabel2Ownership2.DeclaringEntityType.GetTableName()); + + Assert.NotSame(bookOwnership1.DeclaringEntityType, bookOwnership2.DeclaringEntityType); + Assert.Single(bookOwnership1.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookOwnership1.DeclaringEntityType.GetForeignKeys()); + + Assert.NotSame(bookLabel1Ownership1.DeclaringEntityType, bookLabel2Ownership1.DeclaringEntityType); + Assert.NotSame(bookLabel1Ownership2.DeclaringEntityType, bookLabel2Ownership2.DeclaringEntityType); + Assert.Single(bookLabel1Ownership1.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel1Ownership2.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel2Ownership1.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel2Ownership2.DeclaringEntityType.GetForeignKeys()); + + Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(BookLabel))); + Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(AnotherBookLabel))); + Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(SpecialBookLabel))); + + Assert.Null( + bookOwnership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id))! + .GetColumnName(StoreObjectIdentifier.Table("Label"))); + Assert.Null( + bookLabel2Ownership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id))! + .GetColumnName(StoreObjectIdentifier.Table("AlternateLabel"))); + + modelBuilder.Entity().OwnsOne(b => b.Label).ToTable("Label"); + modelBuilder.Entity().OwnsOne(b => b.AlternateLabel).ToTable("AlternateLabel"); + + model = modelBuilder.FinalizeModel(); + + Assert.Equal( + nameof(BookLabel.Id), + bookOwnership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id))! + .GetColumnName(StoreObjectIdentifier.Table("Label"))); + Assert.Equal( + nameof(BookLabel.AnotherBookLabel) + "_" + nameof(BookLabel.Id), + bookLabel2Ownership1.DeclaringEntityType.FindProperty(nameof(BookLabel.Id))! + .GetColumnName(StoreObjectIdentifier.Table("AlternateLabel"))); + + var alternateTable = model.GetRelationalModel().FindTable("AlternateLabel", null)!; + var bookId = alternateTable.FindColumn("BookId2")!; + + Assert.Equal(4, bookId.PropertyMappings.Count()); + Assert.All(bookId.PropertyMappings, m => Assert.Equal(ValueGenerated.OnUpdateSometimes, m.Property.ValueGenerated)); + } + + [ConditionalFact] + public virtual void Owned_types_can_be_mapped_to_different_tables() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity( + bb => + { + bb.ToTable("BT", "BS", t => + { + t.ExcludeFromMigrations(); + + Assert.Equal("BT", t.Name); + Assert.Equal("BS", t.Schema); + }); + bb.OwnsOne( + b => b.AlternateLabel, tb => + { + tb.Ignore(l => l.Book); + tb.WithOwner() + .HasConstraintName("AlternateLabelFK"); + tb.ToTable("TT", "TS"); + tb.IsMemoryOptimized(); + tb.OwnsOne( + l => l.AnotherBookLabel, ab => + { + ab.Ignore(l => l.Book); + ab.ToTable("AT1", "AS1", t => + { + t.ExcludeFromMigrations(false); + + Assert.Equal("AT1", t.Name); + Assert.Equal("AS1", t.Schema); + }); + ab.OwnsOne(s => s.SpecialBookLabel) + .ToTable("ST11", "SS11") + .Ignore(l => l.Book) + .Ignore(l => l.BookLabel); + + ab.OwnedEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))! + .AddAnnotation("Foo", "Bar"); + }); + tb.OwnsOne( + l => l.SpecialBookLabel, sb => + { + sb.Ignore(l => l.Book); + sb.ToTable("ST2", "SS2"); + sb.OwnsOne(s => s.AnotherBookLabel) + .ToTable("AT21", "AS21") + .Ignore(l => l.Book); + }); + }); + bb.OwnsOne( + b => b.Label, lb => + { + lb.Ignore(l => l.Book); + lb.ToTable("LT", "LS"); + lb.OwnsOne( + l => l.SpecialBookLabel, sb => + { + sb.Ignore(l => l.Book); + sb.ToTable("ST1", "SS1"); + sb.OwnsOne(a => a.AnotherBookLabel) + .ToTable("AT11", "AS11") + .Ignore(l => l.Book); + }); + lb.OwnsOne( + l => l.AnotherBookLabel, ab => + { + ab.Ignore(l => l.Book); + ab.ToTable("AT2", "AS2"); + ab.OwnsOne(a => a.SpecialBookLabel) + .ToTable("ST21", "SS21") + .Ignore(l => l.BookLabel) + .Ignore(l => l.Book); + }); + }); + }); + + modelBuilder.FinalizeModel(); + + var book = model.FindEntityType(typeof(Book))!; + var bookOwnership1 = book.FindNavigation(nameof(Book.Label))!.ForeignKey; + var bookOwnership2 = book.FindNavigation(nameof(Book.AlternateLabel))!.ForeignKey; + var bookLabel1Ownership1 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))!.ForeignKey; + var bookLabel1Ownership2 = bookOwnership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))!.ForeignKey; + var bookLabel2Ownership1 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))!.ForeignKey; + var bookLabel2Ownership2 = bookOwnership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))!.ForeignKey; + var bookLabel1Ownership11 = bookLabel1Ownership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))! + .ForeignKey; + var bookLabel1Ownership21 = bookLabel1Ownership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))! + .ForeignKey; + var bookLabel2Ownership11 = bookLabel2Ownership1.DeclaringEntityType.FindNavigation(nameof(BookLabel.SpecialBookLabel))! + .ForeignKey; + var bookLabel2Ownership21 = bookLabel2Ownership2.DeclaringEntityType.FindNavigation(nameof(BookLabel.AnotherBookLabel))! + .ForeignKey; + + Assert.Equal("AlternateLabelFK", bookOwnership2.GetConstraintName()); + + Assert.Equal("BS", book.GetSchema()); + Assert.Equal("BT", book.GetTableName()); + Assert.True(book.IsTableExcludedFromMigrations()); + Assert.Equal("LS", bookOwnership1.DeclaringEntityType.GetSchema()); + Assert.Equal("LT", bookOwnership1.DeclaringEntityType.GetTableName()); + Assert.False(bookOwnership1.DeclaringEntityType.IsMemoryOptimized()); + Assert.True(bookOwnership1.DeclaringEntityType.IsTableExcludedFromMigrations()); + Assert.Equal("TS", bookOwnership2.DeclaringEntityType.GetSchema()); + Assert.Equal("TT", bookOwnership2.DeclaringEntityType.GetTableName()); + Assert.True(bookOwnership2.DeclaringEntityType.IsMemoryOptimized()); + Assert.True(bookOwnership2.DeclaringEntityType.IsTableExcludedFromMigrations()); + Assert.Equal("AS2", bookLabel1Ownership1.DeclaringEntityType.GetSchema()); + Assert.Equal("AT2", bookLabel1Ownership1.DeclaringEntityType.GetTableName()); + Assert.Equal("SS1", bookLabel1Ownership2.DeclaringEntityType.GetSchema()); + Assert.Equal("ST1", bookLabel1Ownership2.DeclaringEntityType.GetTableName()); + Assert.Equal("AS1", bookLabel2Ownership1.DeclaringEntityType.GetSchema()); + Assert.Equal("AT1", bookLabel2Ownership1.DeclaringEntityType.GetTableName()); + Assert.False(bookLabel2Ownership1.DeclaringEntityType.IsTableExcludedFromMigrations()); + Assert.Equal("SS2", bookLabel2Ownership2.DeclaringEntityType.GetSchema()); + Assert.Equal("ST2", bookLabel2Ownership2.DeclaringEntityType.GetTableName()); + Assert.Equal("SS21", bookLabel1Ownership11.DeclaringEntityType.GetSchema()); + Assert.Equal("ST21", bookLabel1Ownership11.DeclaringEntityType.GetTableName()); + Assert.Equal("AS11", bookLabel1Ownership21.DeclaringEntityType.GetSchema()); + Assert.Equal("AT11", bookLabel1Ownership21.DeclaringEntityType.GetTableName()); + Assert.Equal("SS11", bookLabel2Ownership11.DeclaringEntityType.GetSchema()); + Assert.Equal("ST11", bookLabel2Ownership11.DeclaringEntityType.GetTableName()); + Assert.Equal("AS21", bookLabel2Ownership21.DeclaringEntityType.GetSchema()); + Assert.Equal("AT21", bookLabel2Ownership21.DeclaringEntityType.GetTableName()); + + Assert.Equal("Bar", bookLabel2Ownership11.PrincipalToDependent?["Foo"]); + + Assert.NotSame(bookOwnership1.DeclaringEntityType, bookOwnership2.DeclaringEntityType); + Assert.Single(bookOwnership1.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookOwnership2.DeclaringEntityType.GetForeignKeys()); + + Assert.NotSame(bookLabel1Ownership1.DeclaringEntityType, bookLabel2Ownership1.DeclaringEntityType); + Assert.NotSame(bookLabel1Ownership2.DeclaringEntityType, bookLabel2Ownership2.DeclaringEntityType); + Assert.Single(bookLabel1Ownership1.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel1Ownership2.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel2Ownership1.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel2Ownership2.DeclaringEntityType.GetForeignKeys()); + + Assert.NotSame(bookLabel1Ownership11.DeclaringEntityType, bookLabel2Ownership11.DeclaringEntityType); + Assert.NotSame(bookLabel1Ownership21.DeclaringEntityType, bookLabel2Ownership21.DeclaringEntityType); + Assert.Single(bookLabel1Ownership11.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel1Ownership21.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel2Ownership11.DeclaringEntityType.GetForeignKeys()); + Assert.Single(bookLabel2Ownership21.DeclaringEntityType.GetForeignKeys()); + + Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(BookLabel))); + Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(AnotherBookLabel))); + Assert.Equal(4, model.GetEntityTypes().Count(e => e.ClrType == typeof(SpecialBookLabel))); + + Assert.Equal(ValueGenerated.Never, bookOwnership1.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + Assert.Equal(ValueGenerated.Never, bookOwnership2.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + + Assert.Equal( + ValueGenerated.Never, bookLabel1Ownership1.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + Assert.Equal( + ValueGenerated.Never, bookLabel1Ownership2.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + Assert.Equal( + ValueGenerated.Never, bookLabel2Ownership1.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + Assert.Equal( + ValueGenerated.Never, bookLabel2Ownership2.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + + Assert.Equal( + ValueGenerated.Never, bookLabel1Ownership11.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + Assert.Equal( + ValueGenerated.Never, bookLabel1Ownership21.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + Assert.Equal( + ValueGenerated.Never, bookLabel2Ownership11.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + Assert.Equal( + ValueGenerated.Never, bookLabel2Ownership21.DeclaringEntityType.FindPrimaryKey()!.Properties.Single().ValueGenerated); + } + + [ConditionalFact] + public virtual void Owned_type_collections_can_be_mapped_to_different_tables() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity().OwnsMany( + c => c.Orders, + r => + { + r.HasKey(o => o.OrderId); + r.IsMemoryOptimized(); + r.Ignore(o => o.OrderCombination); + r.Ignore(o => o.Details); + }); + + var ownership = model.FindEntityType(typeof(Customer))!.FindNavigation(nameof(Customer.Orders))!.ForeignKey; + var owned = ownership.DeclaringEntityType; + Assert.True(ownership.IsOwnership); + Assert.Equal(nameof(Order.Customer), ownership.DependentToPrincipal?.Name); + Assert.Equal("FK_Order_Customer_CustomerId", ownership.GetConstraintName()); + + Assert.Single(owned.GetForeignKeys()); + Assert.Single(owned.GetIndexes()); + Assert.Equal( + new[] { nameof(Order.OrderId), nameof(Order.AnotherCustomerId), nameof(Order.CustomerId) }, + owned.GetProperties().Select(p => p.GetColumnName())); + Assert.Equal(nameof(Order), owned.GetTableName()); + Assert.Null(owned.GetSchema()); + Assert.True(owned.IsMemoryOptimized()); + + modelBuilder.Entity().OwnsMany( + c => c.Orders, + r => + { + r.WithOwner(o => o.Customer).HasConstraintName("Owned"); + r.ToTable("bar", "foo"); + }); + + Assert.Equal("bar", owned.GetTableName()); + Assert.Equal("foo", owned.GetSchema()); + Assert.Equal("Owned", ownership.GetConstraintName()); + + modelBuilder.Entity().OwnsMany( + c => c.Orders, + r => r.ToTable("blah")); + + modelBuilder.FinalizeModel(); + + Assert.Equal("blah", owned.GetTableName()); + Assert.Null(owned.GetSchema()); + } + + [ConditionalFact] + public virtual void Owned_type_collections_can_be_mapped_to_a_view() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().OwnsMany( + c => c.Orders, + r => + { + r.HasKey(o => o.OrderId); + r.Ignore(o => o.OrderCombination); + r.Ignore(o => o.Details); + r.ToView("bar", "foo"); + }); + + var model = modelBuilder.FinalizeModel(); + + var owner = model.FindEntityType(typeof(Customer))!; + var ownership = owner.FindNavigation(nameof(Customer.Orders))!.ForeignKey; + var owned = ownership.DeclaringEntityType; + Assert.True(ownership.IsOwnership); + Assert.Equal(nameof(Order.Customer), ownership.DependentToPrincipal?.Name); + Assert.Empty(ownership.GetMappedConstraints()); + + Assert.Equal(nameof(Customer), owner.GetTableName()); + Assert.Null(owner.GetSchema()); + + Assert.Null(owned.GetForeignKeys().Single().GetConstraintName()); + Assert.Single(owned.GetIndexes()); + Assert.Null(owned.FindPrimaryKey()!.GetName()); + Assert.Equal( + new[] { nameof(Order.OrderId), nameof(Order.AnotherCustomerId), nameof(Order.CustomerId) }, + owned.GetProperties().Select(p => p.GetColumnName())); + Assert.Null(owned.GetTableName()); + Assert.Null(owned.GetSchema()); + Assert.Equal("bar", owned.GetViewName()); + Assert.Equal("foo", owned.GetViewSchema()); + } + + [ConditionalFact] + public virtual void Owner_can_be_mapped_to_a_view() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Entity().OwnsMany( + c => c.Orders, + r => + { + r.HasKey(o => o.OrderId); + r.Ignore(o => o.OrderCombination); + r.Ignore(o => o.Details); + }) + .ToView("bar", "foo"); + + var model = modelBuilder.FinalizeModel(); + + var owner = model.FindEntityType(typeof(Customer))!; + var ownership = owner.FindNavigation(nameof(Customer.Orders))!.ForeignKey; + var owned = ownership.DeclaringEntityType; + Assert.True(ownership.IsOwnership); + Assert.Equal(nameof(Order.Customer), ownership.DependentToPrincipal?.Name); + Assert.Empty(ownership.GetMappedConstraints()); + + Assert.Null(owner.GetTableName()); + Assert.Null(owner.GetSchema()); + Assert.Equal("bar", owner.GetViewName()); + Assert.Equal("foo", owner.GetViewSchema()); + + Assert.Null(owned.GetForeignKeys().Single().GetConstraintName()); + Assert.Equal("IX_Order_CustomerId", owned.GetIndexes().Single().GetDatabaseName()); + Assert.Equal("PK_Order", owned.FindPrimaryKey()!.GetName()); + Assert.Equal( + new[] { nameof(Order.OrderId), nameof(Order.AnotherCustomerId), nameof(Order.CustomerId) }, + owned.GetProperties().Select(p => p.GetColumnName())); + Assert.Equal(nameof(Order), owned.GetTableName()); + Assert.Null(owned.GetSchema()); + } + + public override void Can_configure_owned_type() + { + var modelBuilder = CreateModelBuilder(); + + modelBuilder.Ignore(); + modelBuilder.Ignore(); + + var ownedBuilder = modelBuilder.Entity().OwnsOne(c => c.Details) + .ToTable("OtherCustomerDetails") + .HasCheckConstraint("CK_CustomerDetails_T", "AlternateKey <> 0", c => c.HasName("CK_Guid")); + ownedBuilder.Property(d => d.CustomerId); + ownedBuilder.HasIndex(d => d.CustomerId); + ownedBuilder.WithOwner(d => (OtherCustomer?)d.Customer) + .HasPrincipalKey(c => c.AlternateKey); + + modelBuilder.Entity().OwnsOne( + c => c.Details, b => + { + b.ToTable("SpecialCustomerDetails"); + b.HasCheckConstraint("CK_CustomerDetails_T", "AlternateKey <> 0", c => c.HasName("CK_Guid")); + b.Property(d => d.CustomerId); + b.HasIndex(d => d.CustomerId); + b.WithOwner(d => (SpecialCustomer?)d.Customer) + .HasPrincipalKey(c => c.AlternateKey); + }); + + var model = modelBuilder.FinalizeModel(); + + var owner1 = model.FindEntityType(typeof(OtherCustomer))!; + Assert.Equal(typeof(OtherCustomer).FullName, owner1.Name); + AssertOwnership(owner1); + + var owner2 = model.FindEntityType(typeof(SpecialCustomer))!; + Assert.Equal(typeof(SpecialCustomer).FullName, owner2.Name); + AssertOwnership(owner2); + + Assert.Null(model.FindEntityType(typeof(CustomerDetails))); + Assert.Equal(2, model.GetEntityTypes().Count(e => e.ClrType == typeof(CustomerDetails))); + + static void AssertOwnership(IEntityType owner) + { + var ownership1 = owner.FindNavigation(nameof(Customer.Details))!.ForeignKey; + Assert.True(ownership1.IsOwnership); + Assert.Equal(nameof(Customer.Details), ownership1.PrincipalToDependent?.Name); + Assert.Equal("CustomerAlternateKey", ownership1.Properties.Single().Name); + Assert.Equal(nameof(Customer.AlternateKey), ownership1.PrincipalKey.Properties.Single().Name); + var owned = ownership1.DeclaringEntityType; + Assert.Equal(owner.ShortName() + "Details", owned.GetTableName()); + var checkConstraint = owned.GetCheckConstraints().Single(); + Assert.Same(owned, checkConstraint.EntityType); + Assert.Equal("CK_CustomerDetails_T", checkConstraint.ModelName); + Assert.Equal("AlternateKey <> 0", checkConstraint.Sql); + Assert.Equal("CK_Guid", checkConstraint.Name); + Assert.Single(owned.GetForeignKeys()); + var index = owned.GetIndexes().Single(); + Assert.Same(owned, index.DeclaringEntityType); + Assert.Equal(nameof(CustomerDetails.CustomerId), index.Properties.Single().Name); + Assert.Equal( + new[] { "CustomerAlternateKey", nameof(CustomerDetails.CustomerId), nameof(CustomerDetails.Id) }, + owned.GetProperties().Select(p => p.Name)); + } + } + + public override void Can_configure_owned_type_key() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity().OwnsOne(c => c.Details) + .ToTable("Details") + .HasKey(c => c.Id); + + modelBuilder.FinalizeModel(); + + var owner = model.FindEntityType(typeof(Customer))!; + var owned = owner.FindNavigation(nameof(Customer.Details))!.ForeignKey.DeclaringEntityType; + Assert.Equal( + new[] { nameof(CustomerDetails.Id), nameof(CustomerDetails.CustomerId) }, + owned.GetProperties().Select(p => p.Name).ToArray()); + Assert.Equal(nameof(CustomerDetails.Id), owned.FindPrimaryKey()!.Properties.Single().Name); + } + + [ConditionalFact] + public virtual void Temporal_table_default_settings() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity().ToTable(tb => + { + tb.IsTemporal(); + Assert.Null(tb.Name); + Assert.Null(tb.Schema); + }); + modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer))!; + Assert.True(entity.IsTemporal()); + Assert.Equal("CustomerHistory", entity.GetHistoryTableName()); + Assert.Null(entity.GetHistoryTableSchema()); + + var periodStart = entity.GetProperty(entity.GetPeriodStartPropertyName()!); + var periodEnd = entity.GetProperty(entity.GetPeriodEndPropertyName()!); + + Assert.Equal("PeriodStart", periodStart.Name); + Assert.True(periodStart.IsShadowProperty()); + Assert.Equal(typeof(DateTime), periodStart.ClrType); + Assert.Equal(ValueGenerated.OnAddOrUpdate, periodStart.ValueGenerated); + + Assert.Equal("PeriodEnd", periodEnd.Name); + Assert.True(periodEnd.IsShadowProperty()); + Assert.Equal(typeof(DateTime), periodEnd.ClrType); + Assert.Equal(ValueGenerated.OnAddOrUpdate, periodEnd.ValueGenerated); + } + + [ConditionalFact] + public virtual void Temporal_table_with_history_table_configuration() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity().ToTable( + tb => tb.IsTemporal( + ttb => + { + ttb.UseHistoryTable("HistoryTable", "historySchema"); + ttb.HasPeriodStart("MyPeriodStart").HasColumnName("PeriodStartColumn"); + ttb.HasPeriodEnd("MyPeriodEnd").HasColumnName("PeriodEndColumn"); + })); + + modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer))!; + Assert.True(entity.IsTemporal()); + Assert.Equal(5, entity.GetProperties().Count()); + + Assert.Equal("HistoryTable", entity.GetHistoryTableName()); + Assert.Equal("historySchema", entity.GetHistoryTableSchema()); + + var periodStart = entity.GetProperty(entity.GetPeriodStartPropertyName()!); + var periodEnd = entity.GetProperty(entity.GetPeriodEndPropertyName()!); + + Assert.Equal("MyPeriodStart", periodStart.Name); + Assert.Equal("PeriodStartColumn", periodStart[RelationalAnnotationNames.ColumnName]); + Assert.True(periodStart.IsShadowProperty()); + Assert.Equal(typeof(DateTime), periodStart.ClrType); + Assert.Equal(ValueGenerated.OnAddOrUpdate, periodStart.ValueGenerated); + + Assert.Equal("MyPeriodEnd", periodEnd.Name); + Assert.Equal("PeriodEndColumn", periodEnd[RelationalAnnotationNames.ColumnName]); + Assert.True(periodEnd.IsShadowProperty()); + Assert.Equal(typeof(DateTime), periodEnd.ClrType); + Assert.Equal(ValueGenerated.OnAddOrUpdate, periodEnd.ValueGenerated); + } + + [ConditionalFact] + public virtual void Temporal_table_with_changed_configuration() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity().ToTable( + tb => tb.IsTemporal( + ttb => + { + ttb.UseHistoryTable("HistoryTable", "historySchema"); + ttb.HasPeriodStart("MyPeriodStart"); + ttb.HasPeriodEnd("MyPeriodEnd"); + })); + + modelBuilder.Entity().ToTable( + tb => tb.IsTemporal( + ttb => + { + ttb.UseHistoryTable("ChangedHistoryTable", "changedHistorySchema"); + ttb.HasPeriodStart("ChangedMyPeriodStart"); + ttb.HasPeriodEnd("ChangedMyPeriodEnd"); + })); + + modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer))!; + Assert.True(entity.IsTemporal()); + Assert.Equal(5, entity.GetProperties().Count()); + + Assert.Equal("ChangedHistoryTable", entity.GetHistoryTableName()); + Assert.Equal("changedHistorySchema", entity.GetHistoryTableSchema()); + + var periodStart = entity.GetProperty(entity.GetPeriodStartPropertyName()!); + var periodEnd = entity.GetProperty(entity.GetPeriodEndPropertyName()!); + + Assert.Equal("ChangedMyPeriodStart", periodStart.Name); + Assert.Equal("ChangedMyPeriodStart", periodStart[RelationalAnnotationNames.ColumnName]); + Assert.True(periodStart.IsShadowProperty()); + Assert.Equal(typeof(DateTime), periodStart.ClrType); + Assert.Equal(ValueGenerated.OnAddOrUpdate, periodStart.ValueGenerated); + + Assert.Equal("ChangedMyPeriodEnd", periodEnd.Name); + Assert.Equal("ChangedMyPeriodEnd", periodEnd[RelationalAnnotationNames.ColumnName]); + Assert.True(periodEnd.IsShadowProperty()); + Assert.Equal(typeof(DateTime), periodEnd.ClrType); + Assert.Equal(ValueGenerated.OnAddOrUpdate, periodEnd.ValueGenerated); + } + + [ConditionalFact] + public virtual void Temporal_table_with_period_column_names_changed_configuration() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity().ToTable( + tb => tb.IsTemporal( + ttb => + { + ttb.UseHistoryTable("HistoryTable", "historySchema"); + ttb.HasPeriodStart("MyPeriodStart").HasColumnName("PeriodStartColumn"); + ttb.HasPeriodEnd("MyPeriodEnd").HasColumnName("PeriodEndColumn"); + })); + + modelBuilder.Entity().ToTable( + tb => tb.IsTemporal( + ttb => + { + ttb.UseHistoryTable("ChangedHistoryTable", "changedHistorySchema"); + ttb.HasPeriodStart("MyPeriodStart").HasColumnName("ChangedPeriodStartColumn"); + ttb.HasPeriodEnd("MyPeriodEnd").HasColumnName("ChangedPeriodEndColumn"); + })); + + modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer))!; + Assert.True(entity.IsTemporal()); + Assert.Equal(5, entity.GetProperties().Count()); + + Assert.Equal("ChangedHistoryTable", entity.GetHistoryTableName()); + Assert.Equal("changedHistorySchema", entity.GetHistoryTableSchema()); + + var periodStart = entity.GetProperty(entity.GetPeriodStartPropertyName()!); + var periodEnd = entity.GetProperty(entity.GetPeriodEndPropertyName()!); + + Assert.Equal("MyPeriodStart", periodStart.Name); + Assert.Equal("ChangedPeriodStartColumn", periodStart[RelationalAnnotationNames.ColumnName]); + Assert.True(periodStart.IsShadowProperty()); + Assert.Equal(typeof(DateTime), periodStart.ClrType); + Assert.Equal(ValueGenerated.OnAddOrUpdate, periodStart.ValueGenerated); + + Assert.Equal("MyPeriodEnd", periodEnd.Name); + Assert.Equal("ChangedPeriodEndColumn", periodEnd[RelationalAnnotationNames.ColumnName]); + Assert.True(periodEnd.IsShadowProperty()); + Assert.Equal(typeof(DateTime), periodEnd.ClrType); + Assert.Equal(ValueGenerated.OnAddOrUpdate, periodEnd.ValueGenerated); + } + + [ConditionalFact] + public virtual void Temporal_table_with_explicit_properties_mapped_to_the_period_columns() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity().ToTable( + tb => tb.IsTemporal( + ttb => + { + ttb.UseHistoryTable("HistoryTable", schema: null); + ttb.HasPeriodStart("Start").HasColumnName("PeriodStartColumn"); + ttb.HasPeriodEnd("End").HasColumnName("PeriodEndColumn"); + })); + + modelBuilder.Entity() + .Property("MappedStart") + .HasColumnName("PeriodStartColumn") + .ValueGeneratedOnAddOrUpdate(); + + modelBuilder.Entity() + .Property("MappedEnd") + .HasColumnName("PeriodEndColumn") + .ValueGeneratedOnAddOrUpdate(); + + modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer))!; + Assert.True(entity.IsTemporal()); + Assert.Equal(7, entity.GetProperties().Count()); + + Assert.Equal("HistoryTable", entity.GetHistoryTableName()); + + var periodStart = entity.GetProperty(entity.GetPeriodStartPropertyName()!); + var periodEnd = entity.GetProperty(entity.GetPeriodEndPropertyName()!); + + Assert.Equal("Start", periodStart.Name); + Assert.Equal("PeriodStartColumn", periodStart[RelationalAnnotationNames.ColumnName]); + Assert.True(periodStart.IsShadowProperty()); + Assert.Equal(typeof(DateTime), periodStart.ClrType); + Assert.Equal(ValueGenerated.OnAddOrUpdate, periodStart.ValueGenerated); + + Assert.Equal("End", periodEnd.Name); + Assert.Equal("PeriodEndColumn", periodEnd[RelationalAnnotationNames.ColumnName]); + Assert.True(periodEnd.IsShadowProperty()); + Assert.Equal(typeof(DateTime), periodEnd.ClrType); + Assert.Equal(ValueGenerated.OnAddOrUpdate, periodEnd.ValueGenerated); + + var propertyMappedToStart = entity.GetProperty("MappedStart"); + Assert.Equal("PeriodStartColumn", propertyMappedToStart[RelationalAnnotationNames.ColumnName]); + + var propertyMappedToEnd = entity.GetProperty("MappedEnd"); + Assert.Equal("PeriodEndColumn", propertyMappedToEnd[RelationalAnnotationNames.ColumnName]); + } + + [ConditionalFact] + public virtual void + Temporal_table_with_explicit_properties_with_same_name_as_default_periods_but_different_periods_defined_explicity_as_well() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity() + .Property("PeriodStart") + .HasColumnName("PeriodStartColumn"); + + modelBuilder.Entity() + .Property("PeriodEnd") + .HasColumnName("PeriodEndColumn"); + + modelBuilder.Entity().ToTable( + tb => tb.IsTemporal( + ttb => + { + ttb.UseHistoryTable("HistoryTable", schema: null); + ttb.HasPeriodStart("Start"); + ttb.HasPeriodEnd("End"); + })); + + modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer))!; + Assert.True(entity.IsTemporal()); + Assert.Equal(7, entity.GetProperties().Count()); + + Assert.Equal("HistoryTable", entity.GetHistoryTableName()); + + var periodStart = entity.GetProperty(entity.GetPeriodStartPropertyName()!); + var periodEnd = entity.GetProperty(entity.GetPeriodEndPropertyName()!); + + Assert.Equal("Start", periodStart.Name); + Assert.Equal("Start", periodStart[RelationalAnnotationNames.ColumnName]); + Assert.True(periodStart.IsShadowProperty()); + Assert.Equal(typeof(DateTime), periodStart.ClrType); + Assert.Equal(ValueGenerated.OnAddOrUpdate, periodStart.ValueGenerated); + + Assert.Equal("End", periodEnd.Name); + Assert.Equal("End", periodEnd[RelationalAnnotationNames.ColumnName]); + Assert.True(periodEnd.IsShadowProperty()); + Assert.Equal(typeof(DateTime), periodEnd.ClrType); + Assert.Equal(ValueGenerated.OnAddOrUpdate, periodEnd.ValueGenerated); + + var propertyMappedToStart = entity.GetProperty("PeriodStart"); + Assert.Equal("PeriodStartColumn", propertyMappedToStart[RelationalAnnotationNames.ColumnName]); + Assert.Equal(ValueGenerated.Never, propertyMappedToStart.ValueGenerated); + + var propertyMappedToEnd = entity.GetProperty("PeriodEnd"); + Assert.Equal("PeriodEndColumn", propertyMappedToEnd[RelationalAnnotationNames.ColumnName]); + Assert.Equal(ValueGenerated.Never, propertyMappedToEnd.ValueGenerated); + } + + [ConditionalFact] + public virtual void Switching_from_temporal_to_non_temporal_default_settings() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity().ToTable(tb => tb.IsTemporal()); + modelBuilder.Entity().ToTable(tb => tb.IsTemporal(false)); + + modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(Customer))!; + Assert.False(entity.IsTemporal()); + Assert.Null(entity.GetPeriodStartPropertyName()); + Assert.Null(entity.GetPeriodEndPropertyName()); + Assert.Equal(3, entity.GetProperties().Count()); + } + + [ConditionalFact] + public virtual void Implicit_many_to_many_converted_from_non_temporal_to_temporal() + { + var modelBuilder = CreateModelBuilder(); + var model = modelBuilder.Model; + + modelBuilder.Entity(); + modelBuilder.Entity(); + + modelBuilder.Entity().ToTable(tb => tb.IsTemporal()); + modelBuilder.Entity().ToTable(tb => tb.IsTemporal()); + + modelBuilder.FinalizeModel(); + + var entity = model.FindEntityType(typeof(ImplicitManyToManyA))!; + var joinEntity = entity.GetSkipNavigations().Single().JoinEntityType!; + + Assert.True(joinEntity.IsTemporal()); + } + + protected override TestModelBuilder CreateModelBuilder(Action? configure = null) + => CreateTestModelBuilder(SqlServerTestHelpers.Instance, configure); + } + + public abstract class TestTemporalTableBuilder + where TEntity : class + { + public abstract TestTemporalTableBuilder UseHistoryTable(string name, string? schema); + public abstract TestTemporalPeriodPropertyBuilder HasPeriodStart(string propertyName); + public abstract TestTemporalPeriodPropertyBuilder HasPeriodEnd(string propertyName); + } + + public class GenericTestTemporalTableBuilder : TestTemporalTableBuilder, + IInfrastructure> + where TEntity : class + { + public GenericTestTemporalTableBuilder(TemporalTableBuilder temporalTableBuilder) + { + TemporalTableBuilder = temporalTableBuilder; + } + + private TemporalTableBuilder TemporalTableBuilder { get; } + + TemporalTableBuilder IInfrastructure>.Instance + => TemporalTableBuilder; + + protected virtual TestTemporalTableBuilder Wrap(TemporalTableBuilder tableBuilder) + => new GenericTestTemporalTableBuilder(tableBuilder); + + public override TestTemporalTableBuilder UseHistoryTable(string name, string? schema) + => Wrap(TemporalTableBuilder.UseHistoryTable(name, schema)); + + public override TestTemporalPeriodPropertyBuilder HasPeriodStart(string propertyName) + => new(TemporalTableBuilder.HasPeriodStart(propertyName)); + + public override TestTemporalPeriodPropertyBuilder HasPeriodEnd(string propertyName) + => new(TemporalTableBuilder.HasPeriodEnd(propertyName)); + } + + public class NonGenericTestTemporalTableBuilder : TestTemporalTableBuilder, IInfrastructure + where TEntity : class + { + public NonGenericTestTemporalTableBuilder(TemporalTableBuilder temporalTableBuilder) + { + TemporalTableBuilder = temporalTableBuilder; + } + + private TemporalTableBuilder TemporalTableBuilder { get; } + + TemporalTableBuilder IInfrastructure.Instance + => TemporalTableBuilder; + + protected virtual TestTemporalTableBuilder Wrap(TemporalTableBuilder temporalTableBuilder) + => new NonGenericTestTemporalTableBuilder(temporalTableBuilder); + + public override TestTemporalTableBuilder UseHistoryTable(string name, string? schema) + => Wrap(TemporalTableBuilder.UseHistoryTable(name, schema)); + + public override TestTemporalPeriodPropertyBuilder HasPeriodStart(string propertyName) + => new(TemporalTableBuilder.HasPeriodStart(propertyName)); + + public override TestTemporalPeriodPropertyBuilder HasPeriodEnd(string propertyName) + => new(TemporalTableBuilder.HasPeriodEnd(propertyName)); + } + + public class TestTemporalPeriodPropertyBuilder + { + public TestTemporalPeriodPropertyBuilder(TemporalPeriodPropertyBuilder temporalPeriodPropertyBuilder) + { + TemporalPeriodPropertyBuilder = temporalPeriodPropertyBuilder; + } + + protected TemporalPeriodPropertyBuilder TemporalPeriodPropertyBuilder { get; } + + public TestTemporalPeriodPropertyBuilder HasColumnName(string name) + => new(TemporalPeriodPropertyBuilder.HasColumnName(name)); + } +} diff --git a/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs index d92736fdf73..04b7c74fd11 100644 --- a/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs @@ -16,12 +16,14 @@ public DataAnnotationSqliteTest(DataAnnotationSqliteFixture fixture, ITestOutput protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); + protected override TestHelpers TestHelpers => SqliteTestHelpers.Instance; + public override IModel Non_public_annotations_are_enabled() { var model = base.Non_public_annotations_are_enabled(); var relational = GetProperty(model, "PersonFirstName"); - Assert.Equal("dsdsd", relational.GetColumnBaseName()); + Assert.Equal("dsdsd", relational.GetColumnName()); Assert.Equal("nvarchar(128)", relational.GetColumnType()); return model; @@ -32,7 +34,7 @@ public override IModel Field_annotations_are_enabled() var model = base.Field_annotations_are_enabled(); var relational = GetProperty(model, "_personFirstName"); - Assert.Equal("dsdsd", relational.GetColumnBaseName()); + Assert.Equal("dsdsd", relational.GetColumnName()); Assert.Equal("nvarchar(128)", relational.GetColumnType()); return model; @@ -43,7 +45,7 @@ public override IModel Key_and_column_work_together() var model = base.Key_and_column_work_together(); var relational = GetProperty(model, "PersonFirstName"); - Assert.Equal("dsdsd", relational.GetColumnBaseName()); + Assert.Equal("dsdsd", relational.GetColumnName()); Assert.Equal("nvarchar(128)", relational.GetColumnType()); return model; diff --git a/test/EFCore.Tests/ApiConsistencyTest.cs b/test/EFCore.Tests/ApiConsistencyTest.cs index dc852cb12b5..2248ee79d0c 100644 --- a/test/EFCore.Tests/ApiConsistencyTest.cs +++ b/test/EFCore.Tests/ApiConsistencyTest.cs @@ -93,18 +93,30 @@ protected override void Initialize() public override HashSet UnmatchedMetadataMethods { get; } = new() { - typeof(OwnedNavigationBuilder<,>).GetMethod( + typeof(OwnedNavigationBuilder).GetMethod( nameof(OwnedNavigationBuilder.OwnsOne), 0, new[] { typeof(string), typeof(string) }), - typeof(OwnedNavigationBuilder<,>).GetMethod( + typeof(OwnedNavigationBuilder).GetMethod( nameof(OwnedNavigationBuilder.OwnsOne), 0, new[] { typeof(string), typeof(Type), typeof(string) }), - typeof(OwnedNavigationBuilder<,>).GetMethod( + typeof(OwnedNavigationBuilder).GetMethod( nameof(OwnedNavigationBuilder.OwnsOne), 0, new[] { typeof(Type), typeof(string) }), - typeof(OwnedNavigationBuilder<,>).GetMethod( + typeof(OwnedNavigationBuilder).GetMethod( nameof(OwnedNavigationBuilder.OwnsMany), 0, new[] { typeof(string), typeof(string) }), - typeof(OwnedNavigationBuilder<,>).GetMethod( + typeof(OwnedNavigationBuilder).GetMethod( nameof(OwnedNavigationBuilder.OwnsMany), 0, new[] { typeof(string), typeof(Type), typeof(string) }), - typeof(OwnedNavigationBuilder<,>).GetMethod( + typeof(OwnedNavigationBuilder).GetMethod( nameof(OwnedNavigationBuilder.OwnsMany), 0, new[] { typeof(Type), typeof(string) }), + typeof(OwnedNavigationBuilder).GetMethod( + nameof(OwnedNavigationBuilder.OwnsOne), 0, new[] { typeof(string), typeof(string), typeof(Action) }), + typeof(OwnedNavigationBuilder).GetMethod( + nameof(OwnedNavigationBuilder.OwnsOne), 0, new[] { typeof(string), typeof(Type), typeof(string), typeof(Action) }), + typeof(OwnedNavigationBuilder).GetMethod( + nameof(OwnedNavigationBuilder.OwnsOne), 0, new[] { typeof(Type), typeof(string), typeof(Action) }), + typeof(OwnedNavigationBuilder).GetMethod( + nameof(OwnedNavigationBuilder.OwnsMany), 0, new[] { typeof(string), typeof(string), typeof(Action) }), + typeof(OwnedNavigationBuilder).GetMethod( + nameof(OwnedNavigationBuilder.OwnsMany), 0, new[] { typeof(string), typeof(Type), typeof(string), typeof(Action) }), + typeof(OwnedNavigationBuilder).GetMethod( + nameof(OwnedNavigationBuilder.OwnsMany), 0, new[] { typeof(Type), typeof(string), typeof(Action) }), typeof(IConventionPropertyBase).GetMethod(nameof(IConventionPropertyBase.SetField), new[] { typeof(string), typeof(bool) }), typeof(IReadOnlyAnnotatable).GetMethod(nameof(IReadOnlyAnnotatable.FindAnnotation)), typeof(IReadOnlyAnnotatable).GetMethod(nameof(IReadOnlyAnnotatable.GetAnnotations)), diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs index 4ec6e41d27b..a44cae7bfb5 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderGenericTest.cs @@ -114,7 +114,7 @@ protected override TestModelBuilder CreateTestModelBuilder( => new GenericTestModelBuilder(testHelpers, configure); } - protected class GenericTestModelBuilder : TestModelBuilder + public class GenericTestModelBuilder : TestModelBuilder { public GenericTestModelBuilder(TestHelpers testHelpers, Action? configure) : base(testHelpers, configure) diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs index 4aee1e9fb34..31814560084 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderNonGenericTest.cs @@ -154,7 +154,7 @@ protected override TestModelBuilder CreateTestModelBuilder( => new NonGenericTestModelBuilder(testHelpers, configure); } - private class NonGenericTestModelBuilder : TestModelBuilder + public class NonGenericTestModelBuilder : TestModelBuilder { public NonGenericTestModelBuilder(TestHelpers testHelpers, Action? configure) : base(testHelpers, configure) diff --git a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs index af7bf6582e5..099584ddbbe 100644 --- a/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/NonRelationshipTestBase.cs @@ -17,11 +17,11 @@ public abstract class NonRelationshipTestBase : ModelBuilderTestBase public void Can_set_model_annotation() { var modelBuilder = CreateModelBuilder(); - var model = modelBuilder.Model; - modelBuilder = modelBuilder.HasAnnotation("Fus", "Ro"); Assert.NotNull(modelBuilder); + + var model = modelBuilder.FinalizeModel(); Assert.Equal("Ro", model.FindAnnotation("Fus").Value); } @@ -69,9 +69,9 @@ public virtual void Entity_key_on_shadow_property_is_discovered_by_convention() modelBuilder.Entity(); modelBuilder.Ignore(); - var entity = modelBuilder.Model.FindEntityType(typeof(Order)); + var model = modelBuilder.FinalizeModel(); - modelBuilder.FinalizeModel(); + var entity = model.FindEntityType(typeof(Order)); Assert.Equal("Id", entity.FindPrimaryKey().Properties.Single().Name); }