From 339f161c65dc57282c51f4c5732e9a0d79ee08a2 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Mon, 9 May 2022 10:20:05 -0700 Subject: [PATCH] Add entity-splitting model building support Allow specifying different column names per table in TPT, TPC or entity splitting Part of #620 Fixes #19811 --- ...nalCSharpRuntimeAnnotationCodeGenerator.cs | 149 ++- .../RelationalEntityTypeBuilderExtensions.cs | 1058 +++++++++++++---- .../RelationalModelValidator.cs | 28 +- .../Metadata/Builders/ColumnBuilder.cs | 95 ++ .../Metadata/Builders/ColumnBuilder`.cs | 26 + .../OwnedNavigationSplitTableBuilder.cs | 129 ++ .../OwnedNavigationSplitTableBuilder``.cs | 57 + .../OwnedNavigationSplitViewBuilder.cs | 98 ++ .../OwnedNavigationSplitViewBuilder``.cs | 46 + .../Builders/OwnedNavigationTableBuilder.cs | 31 +- .../Builders/OwnedNavigationTableBuilder`.cs | 5 +- .../Builders/OwnedNavigationTableBuilder``.cs | 58 + .../Builders/OwnedNavigationViewBuilder.cs | 101 ++ .../Builders/OwnedNavigationViewBuilder``.cs | 46 + .../Metadata/Builders/SplitTableBuilder.cs | 129 ++ .../Metadata/Builders/SplitTableBuilder`.cs | 53 + .../Metadata/Builders/SplitViewBuilder.cs | 98 ++ .../Metadata/Builders/SplitViewBuilder`.cs | 42 + .../Metadata/Builders/TableBuilder.cs | 29 + .../Metadata/Builders/TableBuilder`.cs | 20 +- .../Metadata/Builders/ViewBuilder.cs | 101 ++ .../Metadata/Builders/ViewBuilder`.cs | 42 + .../RelationalRuntimeModelConvention.cs | 13 +- .../IRelationalEntityTypeOverrides.cs | 29 + .../Internal/IRelationalPropertyOverrides.cs | 8 + .../Internal/InternalTriggerBuilder.cs | 1 - .../Internal/RelationalEntityTypeOverrides.cs | 125 ++ .../Internal/RelationalPropertyOverrides.cs | 35 +- .../RuntimeRelationalEntityTypeOverrides.cs | 45 + .../RuntimeRelationalPropertyOverrides.cs | 10 +- .../CSharpRuntimeModelCodeGeneratorTest.cs | 6 +- .../DataAnnotationInMemoryTest.cs | 2 + .../TestUtilities/TestSqlLoggerFactory.cs | 13 - .../RelationalModelBuilderTest.cs | 41 +- .../RelationalTestModelBuilderExtensions.cs | 24 +- .../DataAnnotationTestBase.cs | 25 +- .../TestUtilities/TestHelpers.cs | 11 +- .../DataAnnotationSqlServerTest.cs | 19 +- .../DataAnnotationSqliteTest.cs | 2 + 39 files changed, 2484 insertions(+), 366 deletions(-) create mode 100644 src/EFCore.Relational/Metadata/Builders/ColumnBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/ColumnBuilder`.cs 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/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/ViewBuilder.cs create mode 100644 src/EFCore.Relational/Metadata/Builders/ViewBuilder`.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/IRelationalEntityTypeOverrides.cs create mode 100644 src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeOverrides.cs create mode 100644 src/EFCore.Relational/Metadata/RuntimeRelationalEntityTypeOverrides.cs diff --git a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs index dd359245331..098aa59bdcd 100644 --- a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs @@ -326,6 +326,25 @@ public override void Generate(IEntityType entityType, CSharpRuntimeAnnotationCod annotations[RelationalAnnotationNames.SqlQuery] = entityType.GetSqlQuery(); annotations[RelationalAnnotationNames.FunctionName] = entityType.GetFunctionName(); + if (annotations.TryGetAndRemove( + RelationalAnnotationNames.RelationalOverrides, + out Dictionary overrides)) + { + AddNamespace(typeof(Dictionary), parameters.Namespaces); + AddNamespace(typeof(RuntimeRelationalEntityTypeOverrides), 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 Dictionary();"); + + foreach (var (key, value) in overrides.OrderBy(pair => pair.Key.Name, StringComparer.Ordinal)) + { + Create(value, key, overridesVariable, parameters); + } + + GenerateSimpleAnnotation(RelationalAnnotationNames.RelationalOverrides, overridesVariable, parameters); + } + if (annotations.TryGetAndRemove( RelationalAnnotationNames.Triggers, out SortedDictionary triggers)) @@ -346,6 +365,65 @@ public override void Generate(IEntityType entityType, CSharpRuntimeAnnotationCod base.Generate(entityType, parameters); } + + private void Create( + IRelationalEntityTypeOverrides overrides, + StoreObjectIdentifier storeObject, + string overridesVariable, + CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + { + 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 RuntimeRelationalEntityTypeOverrides(").IncrementIndent() + .Append(parameters.TargetName).AppendLine(","); + + AppendLiteral(storeObject, mainBuilder, code); + mainBuilder.AppendLine(");").DecrementIndent(); + + CreateAnnotations( + overrides, + GenerateOverrides, + parameters with { TargetName = overrideVariable }); + + mainBuilder.Append(overridesVariable).Append("["); + AppendLiteral(storeObject, mainBuilder, code); + + mainBuilder + .Append("] = ") + .Append(overrideVariable).AppendLine(";"); + + 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; + } + } + } private void Create(ITrigger trigger, string triggersVariable, CSharpRuntimeAnnotationCodeGeneratorParameters parameters) { @@ -410,16 +488,18 @@ public override void Generate(IProperty property, CSharpRuntimeAnnotationCodeGen if (annotations.TryGetAndRemove( RelationalAnnotationNames.RelationalOverrides, - out SortedDictionary overrides)) + out Dictionary overrides)) { - parameters.Namespaces.Add(typeof(SortedDictionary).Namespace!); + AddNamespace(typeof(Dictionary), parameters.Namespaces); + AddNamespace(typeof(RuntimeRelationalPropertyOverrides), 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 Dictionary();"); - foreach (var (key, value) in overrides) + foreach (var (key, value) in overrides.OrderBy(pair => pair.Key.Name, StringComparer.Ordinal)) { - Create((IRelationalPropertyOverrides)value, key, overridesVariable, parameters); + Create(value, key, overridesVariable, parameters); } GenerateSimpleAnnotation(RelationalAnnotationNames.RelationalOverrides, overridesVariable, parameters); @@ -443,40 +523,51 @@ private void Create( .Append("var ").Append(overrideVariable).AppendLine(" = new RuntimeRelationalPropertyOverrides(").IncrementIndent() .Append(parameters.TargetName).AppendLine(",") .Append(code.Literal(overrides.ColumnNameOverridden)).AppendLine(",") - .Append(code.UnknownLiteral(overrides.ColumnName)).AppendLine(");").DecrementIndent(); + .Append(code.UnknownLiteral(overrides.ColumnName)).AppendLine(","); + + AppendLiteral(storeObject, mainBuilder, code); + mainBuilder.AppendLine(");").DecrementIndent(); CreateAnnotations( overrides, 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("["); + AppendLiteral(storeObject, mainBuilder, code); mainBuilder .Append("] = ") .Append(overrideVariable).AppendLine(";"); + + 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.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs index c899390b9bb..c96dc8fe9d3 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs @@ -124,7 +124,7 @@ public static EntityTypeBuilder ToTable( { Check.NotNull(buildAction, nameof(buildAction)); - buildAction(new TableBuilder(null, null, entityTypeBuilder)); + buildAction(new (null, null, entityTypeBuilder)); return entityTypeBuilder; } @@ -149,7 +149,7 @@ public static EntityTypeBuilder ToTable( entityTypeBuilder.Metadata.SetTableName(name); entityTypeBuilder.Metadata.SetSchema(null); - buildAction(new TableBuilder(name, null, entityTypeBuilder)); + buildAction(new (name, null, entityTypeBuilder)); return entityTypeBuilder; } @@ -193,7 +193,7 @@ public static EntityTypeBuilder ToTable( entityTypeConventionBuilder.ToTable(entityTypeBuilder.Metadata.GetDefaultTableName()); } - buildAction(new TableBuilder(null, null, entityTypeBuilder)); + buildAction(new (null, null, entityTypeBuilder)); return entityTypeBuilder; } @@ -220,7 +220,7 @@ public static EntityTypeBuilder ToTable( entityTypeBuilder.Metadata.SetTableName(name); entityTypeBuilder.Metadata.SetSchema(null); - buildAction(new TableBuilder(name, null, entityTypeBuilder)); + buildAction(new (name, null, entityTypeBuilder)); return entityTypeBuilder; } @@ -271,7 +271,7 @@ public static EntityTypeBuilder ToTable( entityTypeBuilder.Metadata.SetTableName(name); entityTypeBuilder.Metadata.SetSchema(schema); - buildAction(new TableBuilder(name, schema, entityTypeBuilder)); + buildAction(new (name, schema, entityTypeBuilder)); return entityTypeBuilder; } @@ -319,7 +319,7 @@ public static EntityTypeBuilder ToTable( entityTypeBuilder.Metadata.SetTableName(name); entityTypeBuilder.Metadata.SetSchema(schema); - buildAction(new TableBuilder(name, schema, entityTypeBuilder)); + buildAction(new (name, schema, entityTypeBuilder)); return entityTypeBuilder; } @@ -330,19 +330,19 @@ public static EntityTypeBuilder ToTable( /// /// See Modeling entity types and relationships for more information and examples. /// - /// The builder for 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 OwnedNavigationBuilder ToTable( - this OwnedNavigationBuilder referenceOwnershipBuilder, + this OwnedNavigationBuilder ownedNavigationBuilder, string? name) { Check.NullButNotEmpty(name, nameof(name)); - referenceOwnershipBuilder.OwnedEntityType.SetTableName(name); - referenceOwnershipBuilder.OwnedEntityType.SetSchema(null); + ownedNavigationBuilder.OwnedEntityType.SetTableName(name); + ownedNavigationBuilder.OwnedEntityType.SetSchema(null); - return referenceOwnershipBuilder; + return ownedNavigationBuilder; } /// @@ -351,18 +351,18 @@ public static OwnedNavigationBuilder ToTable( /// /// See Modeling entity types and relationships for more information and examples. /// - /// The builder for 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 OwnedNavigationBuilder ToTable( - this OwnedNavigationBuilder referenceOwnershipBuilder, + this OwnedNavigationBuilder ownedNavigationBuilder, Action buildAction) { Check.NotNull(buildAction, nameof(buildAction)); - buildAction(new OwnedNavigationTableBuilder(null, null, referenceOwnershipBuilder)); + buildAction(new (null, null, ownedNavigationBuilder)); - return referenceOwnershipBuilder; + return ownedNavigationBuilder; } /// @@ -371,20 +371,22 @@ public static OwnedNavigationBuilder ToTable( /// /// 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. /// 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) + public static OwnedNavigationBuilder ToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, + Action> buildAction) where TOwnerEntity : class - where TRelatedEntity : class + where TDependentEntity : class { Check.NotNull(buildAction, nameof(buildAction)); - buildAction(new OwnedNavigationTableBuilder(null, null, referenceOwnershipBuilder)); + buildAction(new (null, null, ownedNavigationBuilder)); - return referenceOwnershipBuilder; + return ownedNavigationBuilder; } /// @@ -393,15 +395,17 @@ public static OwnedNavigationBuilder ToTable /// 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 table. /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToTable( - this OwnedNavigationBuilder referenceOwnershipBuilder, + public static OwnedNavigationBuilder ToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, string? name) where TOwnerEntity : class - where TRelatedEntity : class - => (OwnedNavigationBuilder)((OwnedNavigationBuilder)referenceOwnershipBuilder).ToTable(name); + where TDependentEntity : class + => (OwnedNavigationBuilder)((OwnedNavigationBuilder)ownedNavigationBuilder).ToTable(name); /// /// Configures the table that the entity type maps to when targeting a relational database. @@ -409,23 +413,23 @@ public static OwnedNavigationBuilder ToTable /// See Modeling entity types and relationships for more information and examples. /// - /// The builder for 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 OwnedNavigationBuilder ToTable( - this OwnedNavigationBuilder referenceOwnershipBuilder, + this OwnedNavigationBuilder ownedNavigationBuilder, 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)); + ownedNavigationBuilder.OwnedEntityType.SetTableName(name); + ownedNavigationBuilder.OwnedEntityType.SetSchema(null); + buildAction(new (name, null, ownedNavigationBuilder)); - return referenceOwnershipBuilder; + return ownedNavigationBuilder; } /// @@ -434,25 +438,27 @@ public static OwnedNavigationBuilder ToTable( /// /// 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 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, + public static OwnedNavigationBuilder ToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, string? name, - Action> buildAction) + Action> buildAction) where TOwnerEntity : class - where TRelatedEntity : class + where TDependentEntity : class { Check.NullButNotEmpty(name, nameof(name)); Check.NotNull(buildAction, nameof(buildAction)); + + ownedNavigationBuilder.OwnedEntityType.SetTableName(name); + ownedNavigationBuilder.OwnedEntityType.SetSchema(null); + buildAction(new (name, null, ownedNavigationBuilder)); - referenceOwnershipBuilder.OwnedEntityType.SetTableName(name); - referenceOwnershipBuilder.OwnedEntityType.SetSchema(null); - buildAction(new OwnedNavigationTableBuilder(name, null, referenceOwnershipBuilder)); - - return referenceOwnershipBuilder; + return ownedNavigationBuilder; } /// @@ -461,22 +467,22 @@ public static OwnedNavigationBuilder ToTable /// See Modeling entity types and relationships for more information and examples. /// - /// The builder for 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 OwnedNavigationBuilder ToTable( - this OwnedNavigationBuilder referenceOwnershipBuilder, + this OwnedNavigationBuilder ownedNavigationBuilder, string name, string? schema) { Check.NotNull(name, nameof(name)); Check.NullButNotEmpty(schema, nameof(schema)); - referenceOwnershipBuilder.OwnedEntityType.SetTableName(name); - referenceOwnershipBuilder.OwnedEntityType.SetSchema(schema); + ownedNavigationBuilder.OwnedEntityType.SetTableName(name); + ownedNavigationBuilder.OwnedEntityType.SetSchema(schema); - return referenceOwnershipBuilder; + return ownedNavigationBuilder; } /// @@ -485,13 +491,13 @@ public static OwnedNavigationBuilder ToTable( /// /// See Modeling entity types and relationships for more information and examples. /// - /// The builder for 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 OwnedNavigationBuilder ToTable( - this OwnedNavigationBuilder referenceOwnershipBuilder, + this OwnedNavigationBuilder ownedNavigationBuilder, string name, string? schema, Action buildAction) @@ -500,11 +506,11 @@ public static OwnedNavigationBuilder ToTable( Check.NullButNotEmpty(schema, nameof(schema)); Check.NotNull(buildAction, nameof(buildAction)); - referenceOwnershipBuilder.OwnedEntityType.SetTableName(name); - referenceOwnershipBuilder.OwnedEntityType.SetSchema(schema); - buildAction(new OwnedNavigationTableBuilder(name, schema, referenceOwnershipBuilder)); + ownedNavigationBuilder.OwnedEntityType.SetTableName(name); + ownedNavigationBuilder.OwnedEntityType.SetSchema(schema); + buildAction(new (name, schema, ownedNavigationBuilder)); - return referenceOwnershipBuilder; + return ownedNavigationBuilder; } /// @@ -513,17 +519,19 @@ public static OwnedNavigationBuilder ToTable( /// /// 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 table. /// The schema of the table. /// The same builder instance so that multiple calls can be chained. - public static OwnedNavigationBuilder ToTable( - this OwnedNavigationBuilder referenceOwnershipBuilder, + public static OwnedNavigationBuilder ToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, string name, string? schema) where TOwnerEntity : class - where TRelatedEntity : class - => (OwnedNavigationBuilder)((OwnedNavigationBuilder)referenceOwnershipBuilder).ToTable( + where TDependentEntity : class + => (OwnedNavigationBuilder)((OwnedNavigationBuilder)ownedNavigationBuilder).ToTable( name, schema); /// @@ -532,28 +540,218 @@ public static OwnedNavigationBuilder ToTable /// 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 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, + public static OwnedNavigationBuilder ToTable( + this OwnedNavigationBuilder ownedNavigationBuilder, string name, string? schema, - Action> buildAction) + Action> buildAction) where TOwnerEntity : class - where TRelatedEntity : class + where TDependentEntity : 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)); + ownedNavigationBuilder.OwnedEntityType.SetTableName(name); + ownedNavigationBuilder.OwnedEntityType.SetSchema(schema); + buildAction(new (name, schema, ownedNavigationBuilder)); - return referenceOwnershipBuilder; + 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; } /// @@ -607,181 +805,529 @@ public static OwnedNavigationBuilder ToTable + /// 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); + } + + /// + /// Mark the table that this entity type is mapped to as excluded from migrations. + /// + /// + /// See Database migrations for more information and examples. + /// + /// The builder for the entity type being configured. + /// A value indicating whether the table should be managed by migrations. + /// Indicates whether the configuration was specified using a data annotation. + /// + /// The same builder instance if the configuration was applied, + /// otherwise. + /// + public static IConventionEntityTypeBuilder? ExcludeTableFromMigrations( + this IConventionEntityTypeBuilder entityTypeBuilder, + bool? excludedFromMigrations, + bool fromDataAnnotation = false) + { + if (!entityTypeBuilder.CanExcludeTableFromMigrations(excludedFromMigrations, fromDataAnnotation)) + { + return null; + } + + entityTypeBuilder.Metadata.SetIsTableExcludedFromMigrations(excludedFromMigrations, fromDataAnnotation); + return entityTypeBuilder; + } + + /// + /// Returns a value indicating whether the table that this entity type is mapped to can be excluded from migrations + /// using the specified configuration source. + /// + /// + /// See Database migrations for more information and examples. + /// + /// The builder for the entity type being configured. + /// A value indicating whether the table should be managed by migrations. + /// Indicates whether the configuration was specified using a data annotation. + /// if the configuration can be applied. + public static bool CanExcludeTableFromMigrations( + this IConventionEntityTypeBuilder entityTypeBuilder, + bool? excludedFromMigrations, + bool fromDataAnnotation = false) + => 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, (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; } /// - /// Returns a value indicating whether the table name can be set for this entity type - /// using the specified configuration source. + /// 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 table. - /// Indicates whether the configuration was specified using a data annotation. - /// if the configuration can be applied. - public static bool CanSetTable( - this IConventionEntityTypeBuilder entityTypeBuilder, + /// 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, - bool fromDataAnnotation = false) - { - Check.NullButNotEmpty(name, nameof(name)); - - return entityTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.TableName, name, fromDataAnnotation); - } + string? schema) + where TOwnerEntity : class + where TDependentEntity : class + => (OwnedNavigationBuilder)ToView( + (OwnedNavigationBuilder)ownedNavigationBuilder, name, schema); /// - /// Configures the schema of the table that the entity type maps to when targeting a relational database. + /// 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 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; - } + /// 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); - entityTypeBuilder.Metadata.SetSchema(schema, 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 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); /// - /// Returns a value indicating whether the schema of the table name can be set for this entity type - /// using the specified configuration source. + /// 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 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, + /// 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, - bool fromDataAnnotation = false) + Action buildAction) { + Check.NotNull(name, nameof(name)); Check.NullButNotEmpty(schema, nameof(schema)); - return entityTypeBuilder.CanSetAnnotation(RelationalAnnotationNames.Schema, schema, fromDataAnnotation); + ownedNavigationBuilder.OwnedEntityType.SetViewName(name); + ownedNavigationBuilder.OwnedEntityType.SetViewSchema(schema); + ownedNavigationBuilder.OwnedEntityType.SetAnnotation(RelationalAnnotationNames.ViewDefinitionSql, null); + buildAction(new(StoreObjectIdentifier.View(name, schema), ownedNavigationBuilder)); + + return ownedNavigationBuilder; } /// - /// Mark the table that this entity type is mapped to as excluded from migrations. + /// Configures the view that the entity type maps to when targeting a relational database. /// /// - /// See Database migrations for more information and examples. + /// See Modeling entity types and relationships for more information and examples. /// - /// The builder for the entity type being configured. - /// A value indicating whether the table should be managed by migrations. - /// Indicates whether the configuration was specified using a data annotation. - /// - /// The same builder instance if the configuration was applied, - /// otherwise. - /// - public static IConventionEntityTypeBuilder? ExcludeTableFromMigrations( - this IConventionEntityTypeBuilder entityTypeBuilder, - bool? excludedFromMigrations, - bool fromDataAnnotation = false) + /// 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 { - if (!entityTypeBuilder.CanExcludeTableFromMigrations(excludedFromMigrations, fromDataAnnotation)) - { - return null; - } + Check.NotNull(name, nameof(name)); + Check.NullButNotEmpty(schema, nameof(schema)); - entityTypeBuilder.Metadata.SetIsTableExcludedFromMigrations(excludedFromMigrations, fromDataAnnotation); - return entityTypeBuilder; - } + ownedNavigationBuilder.OwnedEntityType.SetViewName(name); + ownedNavigationBuilder.OwnedEntityType.SetViewSchema(schema); + ownedNavigationBuilder.OwnedEntityType.SetAnnotation(RelationalAnnotationNames.ViewDefinitionSql, null); + buildAction(new(StoreObjectIdentifier.View(name, schema), ownedNavigationBuilder)); - /// - /// Returns a value indicating whether the table that this entity type is mapped to can be excluded from migrations - /// using the specified configuration source. - /// - /// - /// See Database migrations for more information and examples. - /// - /// The builder for the entity type being configured. - /// A value indicating whether the table should be managed by migrations. - /// Indicates whether the configuration was specified using a data annotation. - /// if the configuration can be applied. - public static bool CanExcludeTableFromMigrations( - this IConventionEntityTypeBuilder entityTypeBuilder, - bool? excludedFromMigrations, - bool fromDataAnnotation = false) - => entityTypeBuilder.CanSetAnnotation - (RelationalAnnotationNames.IsTableExcludedFromMigrations, excludedFromMigrations, fromDataAnnotation); + return ownedNavigationBuilder; + } /// - /// Configures the view that the entity type maps to when targeting a relational database. + /// 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 ToView( + public static EntityTypeBuilder SplitToView( this EntityTypeBuilder entityTypeBuilder, - string? name) - => entityTypeBuilder.ToView(name, null); + string name, + Action buildAction) + => entityTypeBuilder.SplitToView(name, null, buildAction); /// - /// Configures the view that the entity type maps to when targeting a relational database. + /// 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 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 referenceOwnershipBuilder, - string? name) + public static EntityTypeBuilder SplitToView( + this EntityTypeBuilder entityTypeBuilder, + string name, + Action> buildAction) where TEntity : class - => (EntityTypeBuilder)ToView((EntityTypeBuilder)referenceOwnershipBuilder, name); + => entityTypeBuilder.SplitToView(name, null, buildAction); /// - /// Configures the view that the entity type maps to when targeting a relational database. + /// 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 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) + public static EntityTypeBuilder SplitToView( + this EntityTypeBuilder ownedNavigationBuilder, + string name, + string? schema, + Action buildAction) { - Check.NullButNotEmpty(name, nameof(name)); + Check.NotNull(name, nameof(name)); Check.NullButNotEmpty(schema, nameof(schema)); + Check.NotNull(buildAction, nameof(buildAction)); - entityTypeBuilder.Metadata.SetViewName(name); - entityTypeBuilder.Metadata.SetViewSchema(schema); - entityTypeBuilder.Metadata.SetAnnotation(RelationalAnnotationNames.ViewDefinitionSql, null); + buildAction(new(StoreObjectIdentifier.View(name, schema), ownedNavigationBuilder)); - return entityTypeBuilder; + return ownedNavigationBuilder; } /// - /// Configures the view that the entity type maps to when targeting a relational database. + /// 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. @@ -790,87 +1336,119 @@ public static EntityTypeBuilder ToView( /// 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( + public static EntityTypeBuilder SplitToView( this EntityTypeBuilder entityTypeBuilder, - string? name, - string? schema) + string name, + string? schema, + Action> buildAction) where TEntity : class - => (EntityTypeBuilder)ToView((EntityTypeBuilder)entityTypeBuilder, name, schema); + { + 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 the view that the entity type maps to when targeting a relational database. + /// 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 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 referenceOwnershipBuilder, - string? name) - => referenceOwnershipBuilder.ToView(name, null); + public static OwnedNavigationBuilder SplitToView( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + Action buildAction) + => ownedNavigationBuilder.SplitToView(name, null, buildAction); /// - /// Configures the view that the entity type maps to when targeting a relational database. + /// 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 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 referenceOwnershipBuilder, - string? name) + public static OwnedNavigationBuilder SplitToView( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + Action> buildAction) where TOwnerEntity : class - where TRelatedEntity : class - => (OwnedNavigationBuilder)ToView((OwnedNavigationBuilder)referenceOwnershipBuilder, name); + where TDependentEntity : class + => ownedNavigationBuilder.SplitToView(name, null, buildAction); /// - /// Configures the view that the entity type maps to when targeting a relational database. + /// 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 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 referenceOwnershipBuilder, - string? name, - string? schema) + public static OwnedNavigationBuilder SplitToView( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + string? schema, + Action buildAction) { - Check.NullButNotEmpty(name, nameof(name)); + Check.NotNull(name, nameof(name)); Check.NullButNotEmpty(schema, nameof(schema)); + Check.NotNull(buildAction, nameof(buildAction)); - referenceOwnershipBuilder.OwnedEntityType.SetViewName(name); - referenceOwnershipBuilder.OwnedEntityType.SetViewSchema(schema); - referenceOwnershipBuilder.OwnedEntityType.SetAnnotation(RelationalAnnotationNames.ViewDefinitionSql, null); + buildAction(new (StoreObjectIdentifier.View(name, schema), ownedNavigationBuilder)); - return referenceOwnershipBuilder; + return ownedNavigationBuilder; } /// - /// Configures the view that the entity type maps to when targeting a relational database. + /// 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 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 referenceOwnershipBuilder, - string? name, - string? schema) + public static OwnedNavigationBuilder SplitToView( + this OwnedNavigationBuilder ownedNavigationBuilder, + string name, + string? schema, + Action> buildAction) where TOwnerEntity : class - where TRelatedEntity : class - => (OwnedNavigationBuilder)ToView( - (OwnedNavigationBuilder)referenceOwnershipBuilder, name, schema); + 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. @@ -1322,15 +1900,17 @@ 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 +1918,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 +1937,20 @@ 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) where TOwnerEntity : class - where TRelatedEntity : class - => (OwnedNavigationBuilder)ToFunction( - (OwnedNavigationBuilder)referenceOwnershipBuilder, name, configureFunction); + where TDependentEntity : class + => (OwnedNavigationBuilder)ToFunction( + (OwnedNavigationBuilder)ownedNavigationBuilder, name, configureFunction); /// /// Configures the function that the entity type maps to when targeting a relational database. @@ -1374,18 +1958,20 @@ 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) where TOwnerEntity : class - where TRelatedEntity : class - => (OwnedNavigationBuilder)ToFunction( - (OwnedNavigationBuilder)referenceOwnershipBuilder, function, configureFunction); + where TDependentEntity : class + => (OwnedNavigationBuilder)ToFunction( + (OwnedNavigationBuilder)ownedNavigationBuilder, function, configureFunction); [return: NotNullIfNotNull("name")] private static IMutableDbFunction? ToFunction(string? name, IMutableEntityType entityType) @@ -1589,7 +2175,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 +2248,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 +2285,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/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index 53c17d48f14..4e3a53d6801 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -1374,13 +1374,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) @@ -1399,8 +1396,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; @@ -1534,18 +1532,18 @@ 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) + var storeObject = storeObjectOverride.StoreObject; + var name = storeObject.Name; + var schema = storeObject.Schema; + switch (storeObject.StoreObjectType) { case StoreObjectType.Table: if (!entityType.GetDerivedTypesInclusive().Any( @@ -1592,7 +1590,7 @@ protected virtual void ValidatePropertyOverrides( break; default: - throw new NotSupportedException(storeOverride.StoreObjectType.ToString()); + throw new NotSupportedException(storeObject.StoreObjectType.ToString()); } } } diff --git a/src/EFCore.Relational/Metadata/Builders/ColumnBuilder.cs b/src/EFCore.Relational/Metadata/Builders/ColumnBuilder.cs new file mode 100644 index 00000000000..6c02de3c8dd --- /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 +{ + /// + /// 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); + PropertyBuilder = propertyBuilder; + } + + /// + /// The table-specific overrides being configured. + /// + public virtual IRelationalPropertyOverrides 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; } + + /// + /// The property builder. + /// + public virtual 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 ColumnBuilder HasColumnName(string? name) + { + Check.NullButNotEmpty(name, nameof(name)); + + InternalOverrides.SetColumnName(name, ConfigurationSource.Explicit); + + return this; + } + + #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/ColumnBuilder`.cs b/src/EFCore.Relational/Metadata/Builders/ColumnBuilder`.cs new file mode 100644 index 00000000000..e38ab312eb4 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/ColumnBuilder`.cs @@ -0,0 +1,26 @@ +// 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 : ColumnBuilder +{ + /// + /// 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) + : base(storeObject, 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..918a77540ba --- /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 +{ + /// + /// 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); + + Overrides = RelationalEntityTypeOverrides.GetOrCreate(ownedNavigationBuilder.OwnedEntityType, storeObject); + OwnedNavigationBuilder = ownedNavigationBuilder; + } + + /// + /// The specified table name. + /// + public virtual string Name => Overrides.StoreObject.Name; + + /// + /// The specified table schema. + /// + public virtual string? Schema => Overrides.StoreObject.Schema; + + /// + /// The table-specific overrides being configured. + /// + public virtual IRelationalEntityTypeOverrides Overrides { get; } + + /// + /// The entity type builder. + /// + public virtual 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) + { + Overrides.SetIsTableExcludedFromMigrations(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)Overrides.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(Overrides.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(Overrides.StoreObject, OwnedNavigationBuilder.Property(typeof(TProperty), propertyName)); + + #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..47fbe75fc41 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitTableBuilder``.cs @@ -0,0 +1,57 @@ +// 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 + 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) + { + OwnedNavigationBuilder = ownedNavigationBuilder; + } + + /// + /// The entity type builder. + /// + public new virtual 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 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(Overrides.StoreObject, OwnedNavigationBuilder.Property(propertyExpression)); +} diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitViewBuilder.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitViewBuilder.cs new file mode 100644 index 00000000000..c343b1ded44 --- /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 +{ + /// + /// 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); + + Overrides = RelationalEntityTypeOverrides.GetOrCreate(ownedNavigationBuilder.OwnedEntityType, storeObject); + OwnedNavigationBuilder = ownedNavigationBuilder; + } + + /// + /// The specified view name. + /// + public virtual string Name => Overrides.StoreObject.Name; + + /// + /// The specified view schema. + /// + public virtual string? Schema => Overrides.StoreObject.Schema; + + /// + /// The view-specific overrides being configured. + /// + public virtual IRelationalEntityTypeOverrides Overrides { get; } + + /// + /// The entity type builder. + /// + public virtual 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 ColumnBuilder Property(string propertyName) + => new(Overrides.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 ColumnBuilder Property(string propertyName) + => new(Overrides.StoreObject, OwnedNavigationBuilder.Property(typeof(TProperty), propertyName)); + + #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..faeb3453515 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationSplitViewBuilder``.cs @@ -0,0 +1,46 @@ +// 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 + 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) + { + OwnedNavigationBuilder = ownedNavigationBuilder; + } + + /// + /// The entity type builder. + /// + public new virtual 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. + /// + /// + /// 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(Overrides.StoreObject, OwnedNavigationBuilder.Property(propertyExpression)); +} diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder.cs index f8c5214cbe4..df12f76066c 100644 --- a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder.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; @@ -60,6 +60,35 @@ public virtual OwnedNavigationTableBuilder ExcludeFromMigrations(bool excluded = return this; } + /// + /// 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(StoreObjectIdentifier.Table(GetName(), Schema), 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(StoreObjectIdentifier.Table(GetName(), Schema), OwnedNavigationBuilder.Property(typeof(TProperty), 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 string GetName() + => Name ?? throw new InvalidOperationException("Table name must be specified for table-specific overrides."); + #region Hidden System.Object members /// diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder`.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder`.cs index af93328a20f..2b7b00d8bef 100644 --- a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder`.cs +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder`.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; @@ -8,6 +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. +[Obsolete("Use OwnedNavigationTableBuilder instead")] public class OwnedNavigationTableBuilder : OwnedNavigationTableBuilder where TEntity : class { @@ -22,7 +23,7 @@ public OwnedNavigationTableBuilder(string? name, string? schema, OwnedNavigation : base(name, schema, referenceOwnershipBuilder) { } - + /// /// Configures the table to be ignored by migrations. /// diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder``.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder``.cs new file mode 100644 index 00000000000..c777381262a --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder``.cs @@ -0,0 +1,58 @@ +// 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 + 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( + string? name, + string? schema, + OwnedNavigationBuilder ownedNavigationBuilder) + : base(name, schema, ownedNavigationBuilder) + { + OwnedNavigationBuilder = ownedNavigationBuilder; + } + + /// + /// The entity type builder. + /// + public new virtual 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 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(StoreObjectIdentifier.Table(GetName(), Schema), OwnedNavigationBuilder.Property(propertyExpression)); +} diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationViewBuilder.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationViewBuilder.cs new file mode 100644 index 00000000000..49b7f64522d --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationViewBuilder.cs @@ -0,0 +1,101 @@ +// 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 +{ + /// + /// 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; } + + /// + /// The entity type builder. + /// + public virtual 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 ColumnBuilder 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 ColumnBuilder Property(string propertyName) + => new(StoreObject, OwnedNavigationBuilder.Property(typeof(TProperty), propertyName)); + + #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..509a4c90ab5 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationViewBuilder``.cs @@ -0,0 +1,46 @@ +// 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 + 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) + { + OwnedNavigationBuilder = ownedNavigationBuilder; + } + + /// + /// The entity type builder. + /// + public new virtual 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. + /// + /// + /// 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(StoreObject, OwnedNavigationBuilder.Property(propertyExpression)); +} diff --git a/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder.cs b/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder.cs new file mode 100644 index 00000000000..f7c2a954a95 --- /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 +{ + /// + /// 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); + + Overrides = RelationalEntityTypeOverrides.GetOrCreate(entityTypeBuilder.Metadata, storeObject); + EntityTypeBuilder = entityTypeBuilder; + } + + /// + /// The specified table name. + /// + public virtual string Name => Overrides.StoreObject.Name; + + /// + /// The specified table schema. + /// + public virtual string? Schema => Overrides.StoreObject.Schema; + + /// + /// The table-specific overrides being configured. + /// + public virtual IRelationalEntityTypeOverrides Overrides { get; } + + /// + /// The entity type builder. + /// + public virtual 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) + { + Overrides.SetIsTableExcludedFromMigrations(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)Overrides.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(Overrides.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(Overrides.StoreObject, EntityTypeBuilder.Property(typeof(TProperty), propertyName)); + + #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..10c402b854c --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/SplitTableBuilder`.cs @@ -0,0 +1,53 @@ +// 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 + 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) + { + EntityTypeBuilder = entityTypeBuilder; + } + + /// + /// The entity type builder. + /// + public new virtual 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 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(Overrides.StoreObject, EntityTypeBuilder.Property(propertyExpression)); +} diff --git a/src/EFCore.Relational/Metadata/Builders/SplitViewBuilder.cs b/src/EFCore.Relational/Metadata/Builders/SplitViewBuilder.cs new file mode 100644 index 00000000000..53ae5329ce7 --- /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 +{ + /// + /// 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); + + Overrides = RelationalEntityTypeOverrides.GetOrCreate(entityTypeBuilder.Metadata, storeObject); + EntityTypeBuilder = entityTypeBuilder; + } + + /// + /// The specified view name. + /// + public virtual string Name => Overrides.StoreObject.Name; + + /// + /// The specified view schema. + /// + public virtual string? Schema => Overrides.StoreObject.Schema; + + /// + /// The view-specific overrides being configured. + /// + public virtual IRelationalEntityTypeOverrides Overrides { get; } + + /// + /// The entity type builder. + /// + public virtual 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 ColumnBuilder Property(string propertyName) + => new(Overrides.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 ColumnBuilder Property(string propertyName) + => new(Overrides.StoreObject, EntityTypeBuilder.Property(typeof(TProperty), propertyName)); + + #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..422cbc55009 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/SplitViewBuilder`.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.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 + 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) + { + EntityTypeBuilder = entityTypeBuilder; + } + + /// + /// The entity type builder. + /// + public new virtual 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. + /// + /// + /// 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(Overrides.StoreObject, EntityTypeBuilder.Property(propertyExpression)); +} diff --git a/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs b/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs index 9dd2053f25b..e88cb70f5b5 100644 --- a/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs @@ -77,6 +77,35 @@ 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(StoreObjectIdentifier.Table(GetName(), Schema), 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(StoreObjectIdentifier.Table(GetName(), Schema), EntityTypeBuilder.Property(typeof(TProperty), 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 string GetName() + => Name ?? throw new InvalidOperationException("Table name must be specified for table-specific overrides."); + #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..c2442c31867 100644 --- a/src/EFCore.Relational/Metadata/Builders/TableBuilder`.cs +++ b/src/EFCore.Relational/Metadata/Builders/TableBuilder`.cs @@ -18,10 +18,16 @@ 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) + public TableBuilder(string? name, string? schema, EntityTypeBuilder entityTypeBuilder) : base(name, schema, entityTypeBuilder) { + EntityTypeBuilder = entityTypeBuilder; } + + /// + /// The entity type builder. + /// + public new virtual EntityTypeBuilder EntityTypeBuilder { get; } /// /// Configures the table to be ignored by migrations. @@ -33,4 +39,16 @@ 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(StoreObjectIdentifier.Table(GetName(), Schema), + EntityTypeBuilder.Property(propertyExpression)); } diff --git a/src/EFCore.Relational/Metadata/Builders/ViewBuilder.cs b/src/EFCore.Relational/Metadata/Builders/ViewBuilder.cs new file mode 100644 index 00000000000..27ae423dcaa --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/ViewBuilder.cs @@ -0,0 +1,101 @@ +// 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 +{ + /// + /// 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; } + + /// + /// The entity type builder. + /// + public virtual 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 ColumnBuilder 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 ColumnBuilder Property(string propertyName) + => new(StoreObject, EntityTypeBuilder.Property(typeof(TProperty), propertyName)); + + #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..3f144adc23b --- /dev/null +++ b/src/EFCore.Relational/Metadata/Builders/ViewBuilder`.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.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 + 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) + { + EntityTypeBuilder = entityTypeBuilder; + } + + /// + /// The entity type builder. + /// + public new virtual 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. + /// + /// + /// 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(StoreObject, EntityTypeBuilder.Property(propertyExpression)); +} diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs index 3c8dd8dd923..4a24a745d68 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs @@ -285,14 +285,14 @@ protected override void ProcessPropertyAnnotations( if (annotations.TryGetValue(RelationalAnnotationNames.RelationalOverrides, out var overrides)) { - var runtimePropertyOverrides = new SortedDictionary(); - foreach (var (storeObjectIdentifier, value) in (SortedDictionary?)overrides!) + var propertyOverrides = (Dictionary)overrides!; + var runtimePropertyOverrides = new Dictionary(); + foreach (var (storeObjectIdentifier, value) in propertyOverrides.OrderBy(pair => pair.Key.Name, StringComparer.Ordinal)) { - var runtimeOverrides = Create((IRelationalPropertyOverrides)value, runtimeProperty); + var runtimeOverrides = Create(value, runtimeProperty); runtimePropertyOverrides[storeObjectIdentifier] = runtimeOverrides; - CreateAnnotations( - (IRelationalPropertyOverrides)value, runtimeOverrides, + CreateAnnotations(value, runtimeOverrides, static (convention, annotations, source, target, runtime) => convention.ProcessPropertyOverridesAnnotations(annotations, source, target, runtime)); } @@ -308,7 +308,8 @@ private static RuntimeRelationalPropertyOverrides Create( => new( runtimeProperty, propertyOverrides.ColumnNameOverridden, - propertyOverrides.ColumnName); + propertyOverrides.ColumnName, + propertyOverrides.StoreObject); /// /// Updates the relational property overrides annotations that will be set on the read-only object. diff --git a/src/EFCore.Relational/Metadata/Internal/IRelationalEntityTypeOverrides.cs b/src/EFCore.Relational/Metadata/Internal/IRelationalEntityTypeOverrides.cs new file mode 100644 index 00000000000..6b79511d7e4 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/IRelationalEntityTypeOverrides.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.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 IRelationalEntityTypeOverrides : 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. + /// + IEntityType 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. + /// + StoreObjectIdentifier StoreObject { get; } +} diff --git a/src/EFCore.Relational/Metadata/Internal/IRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/Internal/IRelationalPropertyOverrides.cs index 7d11d6db339..5ee0319ae06 100644 --- a/src/EFCore.Relational/Metadata/Internal/IRelationalPropertyOverrides.cs +++ b/src/EFCore.Relational/Metadata/Internal/IRelationalPropertyOverrides.cs @@ -18,6 +18,14 @@ public interface IRelationalPropertyOverrides : IAnnotatable /// 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. + /// + StoreObjectIdentifier StoreObject { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to 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/RelationalEntityTypeOverrides.cs b/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeOverrides.cs new file mode 100644 index 00000000000..2e10708aea2 --- /dev/null +++ b/src/EFCore.Relational/Metadata/Internal/RelationalEntityTypeOverrides.cs @@ -0,0 +1,125 @@ +// 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 RelationalEntityTypeOverrides : ConventionAnnotatable, IRelationalEntityTypeOverrides +{ + /// + /// 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 RelationalEntityTypeOverrides(IReadOnlyEntityType entityType, in StoreObjectIdentifier storeObject) + { + EntityType = entityType; + StoreObject = 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 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 static IRelationalEntityTypeOverrides? Find(IReadOnlyEntityType entityType, in StoreObjectIdentifier storeObject) + { + var tableOverrides = (Dictionary?) + entityType[RelationalAnnotationNames.RelationalOverrides]; + return tableOverrides != null + && tableOverrides.TryGetValue(storeObject, out var overrides) + ? overrides + : null; + } + + /// + /// 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) + { + var tableOverrides = (Dictionary?) + entityType[RelationalAnnotationNames.RelationalOverrides]; + return tableOverrides?.OrderBy(pair => pair.Key.Name, StringComparer.Ordinal) + .Select(pair => (IRelationalEntityTypeOverrides)pair.Value); + } + + /// + /// 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 RelationalEntityTypeOverrides GetOrCreate( + IMutableEntityType entityType, + in StoreObjectIdentifier storeObject) + { + var tableOverrides = (Dictionary?) + entityType[RelationalAnnotationNames.RelationalOverrides]; + if (tableOverrides == null) + { + tableOverrides = new Dictionary(); + entityType[RelationalAnnotationNames.RelationalOverrides] = tableOverrides; + } + + if (!tableOverrides.TryGetValue(storeObject, out var overrides)) + { + overrides = new RelationalEntityTypeOverrides(entityType, storeObject); + tableOverrides.Add(storeObject, overrides); + } + + return (RelationalEntityTypeOverrides)overrides; + } + + /// + /// 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 RelationalEntityTypeOverrides GetOrCreate( + IConventionEntityType entityType, + in StoreObjectIdentifier storeObject) + => GetOrCreate((IMutableEntityType)entityType, storeObject); + + /// + IEntityType IRelationalEntityTypeOverrides.EntityType + { + [DebuggerStepThrough] + get => (IEntityType)EntityType; + } +} diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs index b6d60e29198..9695f47bdf2 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalPropertyOverrides.cs @@ -21,9 +21,10 @@ 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) { Property = property; + StoreObject = storeObject; } /// @@ -34,6 +35,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 @@ -97,14 +106,28 @@ public virtual bool ColumnNameOverridden /// public static IRelationalPropertyOverrides? Find(IReadOnlyProperty property, in StoreObjectIdentifier storeObject) { - var tableOverrides = (SortedDictionary?) + var tableOverrides = (Dictionary?) property[RelationalAnnotationNames.RelationalOverrides]; return tableOverrides != null && tableOverrides.TryGetValue(storeObject, out var overrides) - ? (IRelationalPropertyOverrides)overrides + ? overrides : null; } + /// + /// 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) + { + var tableOverrides = (Dictionary?) + property[RelationalAnnotationNames.RelationalOverrides]; + return tableOverrides?.OrderBy(pair => pair.Key.Name, StringComparer.Ordinal) + .Select(pair => pair.Value); + } + /// /// 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 @@ -115,17 +138,17 @@ public static RelationalPropertyOverrides GetOrCreate( IMutableProperty property, in StoreObjectIdentifier storeObject) { - var tableOverrides = (SortedDictionary?) + var tableOverrides = (Dictionary?) property[RelationalAnnotationNames.RelationalOverrides]; if (tableOverrides == null) { - tableOverrides = new SortedDictionary(); + tableOverrides = new Dictionary(); property[RelationalAnnotationNames.RelationalOverrides] = tableOverrides; } if (!tableOverrides.TryGetValue(storeObject, out var overrides)) { - overrides = new RelationalPropertyOverrides(property); + overrides = new RelationalPropertyOverrides(property, storeObject); tableOverrides.Add(storeObject, overrides); } diff --git a/src/EFCore.Relational/Metadata/RuntimeRelationalEntityTypeOverrides.cs b/src/EFCore.Relational/Metadata/RuntimeRelationalEntityTypeOverrides.cs new file mode 100644 index 00000000000..64b2025c0dd --- /dev/null +++ b/src/EFCore.Relational/Metadata/RuntimeRelationalEntityTypeOverrides.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. + +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +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 class RuntimeRelationalEntityTypeOverrides : AnnotatableBase, IRelationalEntityTypeOverrides +{ + /// + /// Initializes a new instance of the class. + /// + /// The entity type for which the overrides are applied. + /// The store object for which the configuration is applied. + public RuntimeRelationalEntityTypeOverrides( + RuntimeEntityType entityType, + in StoreObjectIdentifier storeObject) + { + EntityType = entityType; + StoreObject = storeObject; + } + + /// + /// Gets the etity type for which the overrides should be applied. + /// + public virtual RuntimeEntityType EntityType { get; } + + /// + /// Gets store object for which the configuration is applied. + /// + public virtual StoreObjectIdentifier StoreObject { get; } + + /// + IEntityType IRelationalEntityTypeOverrides.EntityType + { + [DebuggerStepThrough] + get => EntityType; + } +} diff --git a/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs b/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs index 835f880b28c..d365615380e 100644 --- a/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs +++ b/src/EFCore.Relational/Metadata/RuntimeRelationalPropertyOverrides.cs @@ -19,12 +19,15 @@ public class RuntimeRelationalPropertyOverrides : AnnotatableBase, IRelationalPr /// The property for which the overrides are applied. /// Whether the column name is overridden. /// The column name. + /// The store object for which the configuration is applied. public RuntimeRelationalPropertyOverrides( RuntimeProperty property, bool columnNameOverridden, - string? columnName) + string? columnName, + in StoreObjectIdentifier storeObject) { Property = property; + StoreObject = storeObject; if (columnNameOverridden) { SetAnnotation(RelationalAnnotationNames.ColumnName, columnName); @@ -36,6 +39,11 @@ public RuntimeRelationalPropertyOverrides( /// public virtual RuntimeProperty Property { get; } + /// + /// Gets store object for which the configuration is applied. + /// + public virtual StoreObjectIdentifier StoreObject { get; } + /// IProperty IRelationalPropertyOverrides.Property { diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 89b102108f1..07c72f1dbcd 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -1064,6 +1064,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) using System.Reflection; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using NetTopologySuite.Geometries; @@ -1089,11 +1090,12 @@ 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 Dictionary(); var idPrincipalDerived = new RuntimeRelationalPropertyOverrides( id, true, - ""DerivedId""); + ""DerivedId"", + StoreObjectIdentifier.Table(""PrincipalDerived"", null)); overrides[StoreObjectIdentifier.Table(""PrincipalDerived"", null)] = idPrincipalDerived; id.AddAnnotation(""Relational:RelationalOverrides"", overrides); id.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); 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/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs index 6ff38786a8d..b9a7d081301 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs @@ -70,27 +70,29 @@ public override TestTableBuilder ExcludeFromMigrations(bool excluded = => Wrap(TableBuilder.ExcludeFromMigrations(excluded)); } - 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 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 +100,21 @@ 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 class NonGenericTestOwnedNavigationTableBuilder : TestOwnedNavigationTableBuilder, IInfrastructure - where TEntity : class + public class NonGenericTestOwnedNavigationTableBuilder : + TestOwnedNavigationTableBuilder, IInfrastructure + where TOwnerEntity : class + where TDependentEntity : class { public NonGenericTestOwnedNavigationTableBuilder(OwnedNavigationTableBuilder tableBuilder) { @@ -127,10 +132,10 @@ 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)); } diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs index 58b29e08a5e..0f820771100 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs @@ -304,7 +304,7 @@ public static ModelBuilderTest.TestOwnedNavigationBuilder ToTable( this ModelBuilderTest.TestOwnedNavigationBuilder builder, - Action> buildAction) + Action> buildAction) where TOwnerEntity : class where TRelatedEntity : class { @@ -312,11 +312,13 @@ public static ModelBuilderTest.TestOwnedNavigationBuilder> 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))); + b => buildAction( + new RelationalModelBuilderTest.NonGenericTestOwnedNavigationTableBuilder(b))); break; } @@ -326,7 +328,7 @@ public static ModelBuilderTest.TestOwnedNavigationBuilder ToTable( this ModelBuilderTest.TestOwnedNavigationBuilder builder, string? name, - Action> buildAction) + Action> buildAction) where TOwnerEntity : class where TRelatedEntity : class { @@ -335,12 +337,14 @@ public static ModelBuilderTest.TestOwnedNavigationBuilder> genericBuilder: genericBuilder.Instance.ToTable( name, - b => buildAction(new RelationalModelBuilderTest.GenericTestOwnedNavigationTableBuilder(b))); + b => buildAction( + new RelationalModelBuilderTest.GenericTestOwnedNavigationTableBuilder(b))); break; case IInfrastructure nongenericBuilder: nongenericBuilder.Instance.ToTable( name, - b => buildAction(new RelationalModelBuilderTest.NonGenericTestOwnedNavigationTableBuilder(b))); + b => buildAction( + new RelationalModelBuilderTest.NonGenericTestOwnedNavigationTableBuilder(b))); break; } @@ -351,7 +355,7 @@ public static ModelBuilderTest.TestOwnedNavigationBuilder builder, string name, string? schema, - Action> buildAction) + Action> buildAction) where TOwnerEntity : class where TRelatedEntity : class { @@ -360,12 +364,14 @@ public static ModelBuilderTest.TestOwnedNavigationBuilder> 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))); + b => buildAction( + new RelationalModelBuilderTest.NonGenericTestOwnedNavigationTableBuilder(b))); break; } 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..879a398badd 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() @@ -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)).GetColumnBaseName()); } - 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.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs index d92736fdf73..11957a4fb0f 100644 --- a/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/DataAnnotationSqliteTest.cs @@ -16,6 +16,8 @@ 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();