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

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

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