From 3f362f205e519f63c5552306cc9cb7fdfa0eddff Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Mon, 8 Aug 2022 17:07:48 -0700 Subject: [PATCH] Move table configuration methods to (OwnedNavigation)TableBuilder ConventionsBuilder -> ConventionSetBuilder AreRowsAffectedReturned -> IsRowsAffectedReturned IStoreStoredProcedureReturn -> IStoreStoredProcedureReturnValue Fixes #28205 --- .../Design/CSharpSnapshotGenerator.cs | 140 +++++++++--------- .../Internal/CSharpDbContextGenerator.cs | 41 ++++- .../RelationalScaffoldingModelFactory.cs | 13 +- .../Design/AnnotationCodeGenerator.cs | 8 - ...nalCSharpRuntimeAnnotationCodeGenerator.cs | 2 +- .../RelationalEntityTypeBuilderExtensions.cs | 10 ++ .../RelationalModelValidator.cs | 2 +- .../Builders/OwnedNavigationTableBuilder.cs | 39 +++++ .../Builders/OwnedNavigationTableBuilder``.cs | 11 ++ .../Metadata/Builders/TableBuilder.cs | 39 +++++ .../Metadata/Builders/TableBuilder`.cs | 11 ++ .../RelationalRuntimeModelConvention.cs | 2 +- .../Metadata/IConventionStoredProcedure.cs | 2 +- .../Metadata/IMutableStoredProcedure.cs | 2 +- .../Metadata/IReadOnlyStoredProcedure.cs | 2 +- .../Metadata/IStoreStoredProcedure.cs | 2 +- ...cs => IStoreStoredProcedureReturnValue.cs} | 4 +- .../InternalStoredProcedureBuilder.cs | 4 +- .../Metadata/Internal/RelationalModel.cs | 4 +- .../Metadata/Internal/StoreStoredProcedure.cs | 2 +- ....cs => StoreStoredProcedureReturnValue.cs} | 12 +- .../Metadata/Internal/StoredProcedure.cs | 20 +-- .../Metadata/RuntimeStoredProcedure.cs | 18 +-- .../SqlServerEntityTypeBuilderExtensions.cs | 4 + .../SqlServerTableBuilderExtensions.cs | 82 ++++++++++ ...ionsBuilder.cs => ConventionSetBuilder.cs} | 4 +- src/EFCore/ModelConfigurationBuilder.cs | 8 +- .../Design/CSharpMigrationsGeneratorTest.cs | 10 +- .../Migrations/ModelSnapshotSqlServerTest.cs | 40 ++--- .../Internal/CSharpDbContextGeneratorTest.cs | 2 +- .../Internal/CSharpEntityTypeGeneratorTest.cs | 6 +- .../CSharpRuntimeModelCodeGeneratorTest.cs | 15 +- .../Migrations/MigrationsTestBase.cs | 35 ++--- .../RelationalModelValidatorTest.cs | 24 ++- .../RelationalBuilderExtensionsTest.cs | 6 +- .../Metadata/RelationalModelTest.cs | 34 +++-- .../Internal/MigrationsModelDifferTest.cs | 27 ++-- .../RelationalModelBuilderTest.cs | 45 ++++-- .../RelationalTestModelBuilderExtensions.cs | 90 ----------- .../RelationalApiConsistencyTest.cs | 2 +- .../MemoryOptimizedTablesTest.cs | 4 +- .../Migrations/MigrationsSqlServerTest.cs | 27 ++-- .../SqlServerAnnotationCodeGeneratorTest.cs | 2 +- .../SqlServerModelValidatorTest.cs | 2 +- ...rverMemoryOptimizedTablesConventionTest.cs | 4 +- .../SqlServerBuilderExtensionsTest.cs | 29 +--- .../Migrations/SqlServerModelDifferTest.cs | 4 +- .../SqlServerModelBuilderTestBase.cs | 110 ++++++++++++-- .../SqlServerTestModelBuilderExtensions.cs | 70 ++++++++- 49 files changed, 670 insertions(+), 406 deletions(-) rename src/EFCore.Relational/Metadata/{IStoreStoredProcedureReturn.cs => IStoreStoredProcedureReturnValue.cs} (92%) rename src/EFCore.Relational/Metadata/Internal/{StoreStoredProcedureReturn.cs => StoreStoredProcedureReturnValue.cs} (87%) rename src/EFCore/Metadata/Builders/{ConventionsBuilder.cs => ConventionSetBuilder.cs} (96%) diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index 11144e254e8..e967f66dedd 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -164,8 +164,6 @@ protected virtual void GenerateEntityType( GenerateEntityTypeAnnotations(entityTypeBuilderName, entityType, stringBuilder); - GenerateCheckConstraints(entityTypeBuilderName, entityType, stringBuilder); - if (ownerNavigation != null) { GenerateRelationships(entityTypeBuilderName, entityType, stringBuilder); @@ -847,12 +845,32 @@ private void GenerateTableMapping( Dictionary annotations) { annotations.TryGetAndRemove(RelationalAnnotationNames.TableName, out IAnnotation tableNameAnnotation); - annotations.TryGetAndRemove(RelationalAnnotationNames.Schema, out IAnnotation schemaAnnotation); var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table); var tableName = (string?)tableNameAnnotation?.Value ?? table?.Name; - if (tableNameAnnotation == null - && entityType.BaseType != null - && entityType.BaseType.GetTableName() == tableName) + var explicitName = tableNameAnnotation != null + || entityType.BaseType == null + || entityType.BaseType.GetTableName() != tableName; + + annotations.TryGetAndRemove(RelationalAnnotationNames.Schema, out IAnnotation schemaAnnotation); + var schema = (string?)schemaAnnotation?.Value ?? table?.Schema; + + annotations.TryGetAndRemove(RelationalAnnotationNames.IsTableExcludedFromMigrations, out IAnnotation isExcludedAnnotation); + var isExcludedFromMigrations = (isExcludedAnnotation?.Value as bool?) == true; + + annotations.TryGetAndRemove(RelationalAnnotationNames.Comment, out IAnnotation commentAnnotation); + var comment = (string?)commentAnnotation?.Value; + + var hasTriggers = entityType.GetTriggers().Any(t => t.TableName == tableName! && t.TableSchema == schema); + var hasOverrides = table != null + && entityType.GetProperties().Select(p => p.FindOverrides(table.Value)).Any(o => o != null); + var requiresTableBuilder = isExcludedFromMigrations + || comment != null + || hasTriggers + || hasOverrides + || entityType.GetCheckConstraints().Any(); + + if (!explicitName + && !requiresTableBuilder) { return; } @@ -861,51 +879,50 @@ private void GenerateTableMapping( .AppendLine() .Append(entityTypeBuilderName) .Append(".ToTable("); - - var schema = (string?)schemaAnnotation?.Value ?? table?.Schema; - if (tableName == null - && (schemaAnnotation == null || schema == null)) + + if (explicitName) { - stringBuilder.Append("(string)"); - } + if (tableName == null + && (schemaAnnotation == null || schema == null)) + { + stringBuilder.Append("(string)"); + } - stringBuilder.Append(Code.Literal(tableName)); + stringBuilder.Append(Code.Literal(tableName)); - annotations.TryGetAndRemove(RelationalAnnotationNames.IsTableExcludedFromMigrations, out IAnnotation isExcludedAnnotation); - var isExcludedFromMigrations = (isExcludedAnnotation?.Value as bool?) == true; - if (isExcludedAnnotation is not null) - { - annotations.Remove(isExcludedAnnotation.Name); - } - - var hasTriggers = entityType.GetTriggers().Any(t => t.TableName == tableName! && t.TableSchema == schema); - var hasOverrides = table != null - && entityType.GetProperties().Select(p => p.FindOverrides(table.Value)).Any(o => o != null); - var requiresTableBuilder = isExcludedFromMigrations - || hasTriggers - || hasOverrides; + if (isExcludedAnnotation is not null) + { + annotations.Remove(isExcludedAnnotation.Name); + } - if (schema != null - || (schemaAnnotation != null && tableName != null)) - { - stringBuilder - .Append(", "); - if (schema == null && !requiresTableBuilder) + if (schema != null + || (schemaAnnotation != null && tableName != null)) { - stringBuilder.Append("(string)"); - } + stringBuilder + .Append(", "); - stringBuilder.Append(Code.Literal(schema)); + if (schema == null && !requiresTableBuilder) + { + stringBuilder.Append("(string)"); + } + + stringBuilder.Append(Code.Literal(schema)); + } } if (requiresTableBuilder) { using (stringBuilder.Indent()) { + if (explicitName) + { + stringBuilder.Append(", "); + } + stringBuilder - .AppendLine(", t =>") - .Append("{"); + .AppendLine("t =>") + .Append("{"); using (stringBuilder.Indent()) { @@ -915,12 +932,21 @@ private void GenerateTableMapping( .AppendLine() .AppendLine("t.ExcludeFromMigrations();"); } + + if (comment != null) + { + stringBuilder + .AppendLine() + .AppendLine($"t.{nameof(TableBuilder.HasComment)}({Code.Literal(comment!)});"); + } if (hasTriggers) { GenerateTriggers("t", entityType, tableName!, schema, stringBuilder); } + GenerateCheckConstraints("t", entityType, stringBuilder); + if (hasOverrides) { GeneratePropertyOverrides("t", entityType, table!.Value, stringBuilder); @@ -1123,10 +1149,10 @@ protected virtual void GenerateCheckConstraint( .Append(".HasCheckConstraint(") .Append(Code.Literal(checkConstraint.ModelName)) .Append(", ") - .Append(Code.Literal(checkConstraint.Sql)); + .Append(Code.Literal(checkConstraint.Sql)) + .Append(")"); GenerateCheckConstraintAnnotations(checkConstraint, stringBuilder); - stringBuilder.AppendLine(");"); } /// @@ -1143,43 +1169,25 @@ protected virtual void GenerateCheckConstraintAnnotations( var annotations = Dependencies.AnnotationCodeGenerator .FilterIgnoredAnnotations(checkConstraint.GetAnnotations()) .ToDictionary(a => a.Name, a => a); - if (annotations.Count > 0 - || hasNonDefaultName) + using (stringBuilder.Indent()) { - if (annotations.Count > 0) - { - stringBuilder - .Append(", c =>") - .AppendLine() - .IncrementIndent() - .AppendLine("{") - .IncrementIndent(); - } - else - { - stringBuilder.Append(", c => "); - } - if (hasNonDefaultName) { stringBuilder - .Append("c.HasName(") + .AppendLine() + .Append(".HasName(") .Append(Code.Literal(checkConstraint.Name!)) .Append(")"); } if (annotations.Count > 0) { - if (hasNonDefaultName) - { - stringBuilder.AppendLine(";"); - } - - GenerateAnnotations("c", checkConstraint, stringBuilder, annotations, inChainedCall: false); - stringBuilder - .DecrementIndent() - .Append("}") - .DecrementIndent(); + GenerateAnnotations("t", checkConstraint, stringBuilder, annotations, inChainedCall: true); + stringBuilder.IncrementIndent(); + } + else + { + stringBuilder.AppendLine(";"); } } } diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs index eb2aee49edc..26c864b31fc 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs @@ -368,10 +368,11 @@ private void GenerateEntityType(IEntityType entityType) annotations.Remove(RelationalAnnotationNames.TableName); annotations.Remove(RelationalAnnotationNames.Schema); + annotations.Remove(RelationalAnnotationNames.Comment); annotations.Remove(RelationalAnnotationNames.ViewName); annotations.Remove(RelationalAnnotationNames.ViewSchema); - annotations.Remove(ScaffoldingAnnotationNames.DbSetName); annotations.Remove(RelationalAnnotationNames.ViewDefinitionSql); + annotations.Remove(ScaffoldingAnnotationNames.DbSetName); if (_useDataAnnotations) { @@ -427,7 +428,6 @@ private void GenerateEntityType(IEntityType entityType) } var triggers = entityType.GetTriggers().ToArray(); - if (triggers.Length > 0) { using (_builder.Indent()) @@ -560,15 +560,42 @@ private void GenerateTableName(IEntityType entityType) var explicitSchema = schema != null && schema != defaultSchema; var explicitTable = explicitSchema || tableName != null && tableName != entityType.GetDbSetName(); - if (explicitTable) + var comment = entityType.GetComment(); + var multiLine = comment != null; + if (explicitTable + || multiLine) { - var parameterString = _code.Literal(tableName!); - if (explicitSchema) + var parameterString = ""; + if (explicitTable) { - parameterString += ", " + _code.Literal(schema!); + parameterString = _code.Literal(tableName!); + if (explicitSchema) + { + parameterString += ", " + _code.Literal(schema!); + } } - var lines = new List { $".{nameof(RelationalEntityTypeBuilderExtensions.ToTable)}({parameterString})" }; + var lines = new List(); + if (!multiLine) + { + lines.Add($".{nameof(RelationalEntityTypeBuilderExtensions.ToTable)}({parameterString})"); + } + else + { + if (parameterString != "") + { + parameterString += ", "; + } + lines.Add($".{nameof(RelationalEntityTypeBuilderExtensions.ToTable)}({parameterString}tb =>"); + lines.Add("{"); + + if (comment != null) + { + lines.Add($"tb.{nameof(TableBuilder.HasComment)}({_code.Literal(comment!)});"); + } + + lines.Add("})"); + } AppendMultiLineFluentApi(entityType, lines); } diff --git a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs index 2c8ac8d9769..8e8f7bb385a 100644 --- a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs +++ b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs @@ -304,12 +304,13 @@ protected virtual ModelBuilder VisitTables(ModelBuilder modelBuilder, ICollectio } else { - builder.ToTable(table.Name, table.Schema); - } - - if (table.Comment != null) - { - builder.HasComment(table.Comment); + builder.ToTable(table.Name, table.Schema, tb => + { + if (table.Comment != null) + { + tb.HasComment(table.Comment); + } + }); } VisitColumns(builder, table.Columns); diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index 7c05933fc8d..9f8faa9d0ee 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -49,10 +49,6 @@ private static readonly MethodInfo ModelUseCollationMethodInfo = typeof(RelationalModelBuilderExtensions).GetRuntimeMethod( nameof(RelationalModelBuilderExtensions.UseCollation), new[] { typeof(ModelBuilder), typeof(string) })!; - private static readonly MethodInfo EntityTypeHasCommentMethodInfo - = typeof(RelationalEntityTypeBuilderExtensions).GetRuntimeMethod( - nameof(RelationalEntityTypeBuilderExtensions.HasComment), new[] { typeof(EntityTypeBuilder), typeof(string) })!; - private static readonly MethodInfo EntityTypeUseTpcMappingStrategyMethodInfo = typeof(RelationalEntityTypeBuilderExtensions).GetRuntimeMethod( nameof(RelationalEntityTypeBuilderExtensions.UseTpcMappingStrategy), new[] { typeof(EntityTypeBuilder) })!; @@ -282,10 +278,6 @@ public virtual IReadOnlyList GenerateFluentApiCalls( { var methodCallCodeFragments = new List(); - GenerateSimpleFluentApiCall( - annotations, - RelationalAnnotationNames.Comment, EntityTypeHasCommentMethodInfo, methodCallCodeFragments); - if (annotations.TryGetValue(RelationalAnnotationNames.MappingStrategy, out var mappingStrategyAnnotation) && mappingStrategyAnnotation.Value is string mappingStrategy) { diff --git a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs index b2b7d5e5618..afdef39f68a 100644 --- a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs @@ -491,7 +491,7 @@ private void Create(IStoredProcedure storedProcedure, string sprocVariable, CSha .Append(parameters.TargetName).AppendLine(",") .Append(code.Literal(storedProcedure.Name)).AppendLine(",") .Append(code.Literal(storedProcedure.Schema)).AppendLine(",") - .Append(code.Literal(storedProcedure.AreRowsAffectedReturned)).AppendLine(",") + .Append(code.Literal(storedProcedure.IsRowsAffectedReturned)).AppendLine(",") .Append(code.Literal(storedProcedure.AreTransactionsSuppressed)) .AppendLine(");") .DecrementIndent() diff --git a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs index 20c71f82714..84cbfd3b1ed 100644 --- a/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs +++ b/src/EFCore.Relational/Extensions/RelationalEntityTypeBuilderExtensions.cs @@ -784,6 +784,7 @@ public static bool CanSetFunction( /// The name of the check constraint. /// The logical constraint sql used in the check constraint. /// A builder to further configure the entity type. + [Obsolete("Configure this using ToTable(t => t.HasCheckConstraint()) instead.")] // Don't remove, used in snapshot public static EntityTypeBuilder HasCheckConstraint( this EntityTypeBuilder entityTypeBuilder, string name, @@ -809,6 +810,7 @@ public static EntityTypeBuilder HasCheckConstraint( /// The logical constraint sql used in the check constraint. /// An action that performs configuration of the check constraint. /// A builder to further configure the entity type. + [Obsolete("Configure this using ToTable(t => t.HasCheckConstraint()) instead.")] // Don't remove, used in snapshot public static EntityTypeBuilder HasCheckConstraint( this EntityTypeBuilder entityTypeBuilder, string name, @@ -836,6 +838,7 @@ public static EntityTypeBuilder HasCheckConstraint( /// The name of the check constraint. /// The logical constraint sql used in the check constraint. /// A builder to further configure the entity type. + [Obsolete("Configure this using ToTable(t => t.HasCheckConstraint()) instead.")] // Don't remove, used in snapshot public static EntityTypeBuilder HasCheckConstraint( this EntityTypeBuilder entityTypeBuilder, string name, @@ -855,6 +858,7 @@ public static EntityTypeBuilder HasCheckConstraint( /// The logical constraint sql used in the check constraint. /// An action that performs configuration of the check constraint. /// A builder to further configure the entity type. + [Obsolete("Configure this using ToTable(t => t.HasCheckConstraint()) instead.")] // Don't remove, used in snapshot public static EntityTypeBuilder HasCheckConstraint( this EntityTypeBuilder entityTypeBuilder, string name, @@ -873,6 +877,7 @@ public static EntityTypeBuilder HasCheckConstraint( /// The name of the check constraint. /// The logical constraint sql used in the check constraint. /// A builder to further configure the navigation. + [Obsolete("Configure this using ToTable(t => t.HasCheckConstraint()) instead.")] // Don't remove, used in snapshot public static OwnedNavigationBuilder HasCheckConstraint( this OwnedNavigationBuilder ownedNavigationBuilder, string name, @@ -899,6 +904,7 @@ public static OwnedNavigationBuilder HasCheckConstraint( /// The name of the check constraint. /// The logical constraint sql used in the check constraint. /// A builder to further configure the navigation. + [Obsolete("Configure this using ToTable(t => t.HasCheckConstraint()) instead.")] // Don't remove, used in snapshot public static OwnedNavigationBuilder HasCheckConstraint( this OwnedNavigationBuilder ownedNavigationBuilder, string name, @@ -919,6 +925,7 @@ public static OwnedNavigationBuilder HasCheckCon /// The logical constraint sql used in the check constraint. /// An action that performs configuration of the check constraint. /// A builder to further configure the navigation. + [Obsolete("Configure this using ToTable(t => t.HasCheckConstraint()) instead.")] // Don't remove, used in snapshot public static OwnedNavigationBuilder HasCheckConstraint( this OwnedNavigationBuilder ownedNavigationBuilder, string name, @@ -948,6 +955,7 @@ public static OwnedNavigationBuilder HasCheckConstraint( /// The logical constraint sql used in the check constraint. /// An action that performs configuration of the check constraint. /// A builder to further configure the navigation. + [Obsolete("Configure this using ToTable(t => t.HasCheckConstraint()) instead.")] // Don't remove, used in snapshot public static OwnedNavigationBuilder HasCheckConstraint( this OwnedNavigationBuilder ownedNavigationBuilder, string name, @@ -1015,6 +1023,7 @@ public static bool CanHaveCheckConstraint( /// The builder for the entity type being configured. /// The comment for the table. /// A builder to further configure the entity type. + [Obsolete("Configure this using ToTable(t => t.HasComment()) instead.")] // Don't remove, used in snapshot public static EntityTypeBuilder HasComment( this EntityTypeBuilder entityTypeBuilder, string? comment) @@ -1033,6 +1042,7 @@ public static EntityTypeBuilder HasComment( /// The entity type builder. /// The comment for the table. /// A builder to further configure the entity type. + [Obsolete("Configure this using ToTable(t => t.HasComment()) instead.")] // Don't remove, used in snapshot public static EntityTypeBuilder HasComment( this EntityTypeBuilder entityTypeBuilder, string? comment) diff --git a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs index b2b77e1a73e..3730b403b31 100644 --- a/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs +++ b/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs @@ -562,7 +562,7 @@ private static void ValidateSproc(IStoredProcedure sproc, string mappingStrategy var missedConcurrencyToken = originalValueProperties.Values.FirstOrDefault(p => p.IsConcurrencyToken); if (missedConcurrencyToken != null && storeObjectIdentifier.StoreObjectType != StoreObjectType.InsertStoredProcedure - && (sproc.AreRowsAffectedReturned + && (sproc.IsRowsAffectedReturned || sproc.FindRowsAffectedParameter() != null || sproc.FindRowsAffectedResultColumn() != null)) { diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder.cs index 90afc43a3e1..84b5abeb0cf 100644 --- a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder.cs @@ -82,6 +82,45 @@ public virtual TriggerBuilder HasTrigger(string modelName) Schema, ConfigurationSource.Explicit)!); + /// + /// Configures a database check constraint when targeting a relational database. + /// + /// + /// See Database check constraints for more information and examples. + /// + /// The name of the check constraint. + /// The logical constraint sql used in the check constraint. + /// A builder to configure the check constraint. + public virtual CheckConstraintBuilder HasCheckConstraint( + string name, + string? sql) + { + Check.NotEmpty(name, nameof(name)); + Check.NullButNotEmpty(sql, nameof(sql)); + + var checkConstraint = InternalCheckConstraintBuilder.HasCheckConstraint( + (IConventionEntityType)Metadata, + name, + sql, + ConfigurationSource.Explicit)!; + + return new((IMutableCheckConstraint)checkConstraint); + } + + /// + /// Configures a comment to be applied to the table + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The comment for the table. + /// A builder to further configure the table. + public virtual OwnedNavigationTableBuilder HasComment(string? comment) + { + Metadata.SetComment(comment); + return this; + } + /// /// Maps the property to a column on the current table and returns an object that can be used /// to provide table-specific configuration if the property is mapped to more than one table. diff --git a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder``.cs b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder``.cs index ae017286cce..69a98451cd0 100644 --- a/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder``.cs +++ b/src/EFCore.Relational/Metadata/Builders/OwnedNavigationTableBuilder``.cs @@ -44,6 +44,17 @@ private OwnedNavigationBuilder OwnedNavigationBu public new virtual OwnedNavigationTableBuilder ExcludeFromMigrations(bool excluded = true) => (OwnedNavigationTableBuilder)base.ExcludeFromMigrations(excluded); + /// + /// Configures a comment to be applied to the table + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The comment for the table. + /// A builder to further configure the table. + public new virtual OwnedNavigationTableBuilder HasComment(string? comment) + => (OwnedNavigationTableBuilder)base.HasComment(comment); + /// /// 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. diff --git a/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs b/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs index 234f8ebb710..2c63d1de812 100644 --- a/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs +++ b/src/EFCore.Relational/Metadata/Builders/TableBuilder.cs @@ -81,6 +81,45 @@ public virtual TriggerBuilder HasTrigger(string modelName) Name, Schema, ConfigurationSource.Explicit)!); + + /// + /// Configures a database check constraint when targeting a relational database. + /// + /// + /// See Database check constraints for more information and examples. + /// + /// The name of the check constraint. + /// The logical constraint sql used in the check constraint. + /// A builder to configure the check constraint. + public virtual CheckConstraintBuilder HasCheckConstraint( + string name, + string? sql) + { + Check.NotEmpty(name, nameof(name)); + Check.NullButNotEmpty(sql, nameof(sql)); + + var checkConstraint = InternalCheckConstraintBuilder.HasCheckConstraint( + (IConventionEntityType)EntityTypeBuilder.Metadata, + name, + sql, + ConfigurationSource.Explicit)!; + + return new((IMutableCheckConstraint)checkConstraint); + } + + /// + /// Configures a comment to be applied to the table + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The comment for the table. + /// A builder to further configure the table. + public virtual TableBuilder HasComment(string? comment) + { + EntityTypeBuilder.Metadata.SetComment(comment); + return this; + } /// /// Maps the property to a column on the current table and returns an object that can be used diff --git a/src/EFCore.Relational/Metadata/Builders/TableBuilder`.cs b/src/EFCore.Relational/Metadata/Builders/TableBuilder`.cs index df3f91f09a8..e53ba2ae842 100644 --- a/src/EFCore.Relational/Metadata/Builders/TableBuilder`.cs +++ b/src/EFCore.Relational/Metadata/Builders/TableBuilder`.cs @@ -26,6 +26,17 @@ public TableBuilder(in StoreObjectIdentifier? storeObject, EntityTypeBuilder EntityTypeBuilder => (EntityTypeBuilder)((IInfrastructure)this).Instance; + /// + /// Configures a comment to be applied to the table + /// + /// + /// See Modeling entity types and relationships for more information and examples. + /// + /// The comment for the table. + /// A builder to further configure the table. + public new virtual TableBuilder HasComment(string? comment) + => (TableBuilder)base.HasComment(comment); + /// /// Configures the table to be ignored by migrations. /// diff --git a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs index a628a28c48a..ef0b3f366e4 100644 --- a/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs +++ b/src/EFCore.Relational/Metadata/Conventions/RelationalRuntimeModelConvention.cs @@ -498,7 +498,7 @@ private RuntimeStoredProcedure Create(IStoredProcedure storedProcedure, RuntimeE runtimeEntityType, storedProcedure.Name, storedProcedure.Schema, - storedProcedure.AreRowsAffectedReturned, + storedProcedure.IsRowsAffectedReturned, storedProcedure.AreTransactionsSuppressed); foreach (var parameter in storedProcedure.Parameters) diff --git a/src/EFCore.Relational/Metadata/IConventionStoredProcedure.cs b/src/EFCore.Relational/Metadata/IConventionStoredProcedure.cs index e50a9e60e21..7d02674d502 100644 --- a/src/EFCore.Relational/Metadata/IConventionStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/IConventionStoredProcedure.cs @@ -145,7 +145,7 @@ public interface IConventionStoredProcedure : IReadOnlyStoredProcedure, IConvent /// A value indicating whether the number of rows affected is returned. /// Indicates whether the configuration was specified using a data annotation. /// The configured value. - bool SetAreRowsAffectedReturned(bool rowsAffectedReturned, bool fromDataAnnotation = false); + bool SetIsRowsAffectedReturned(bool rowsAffectedReturned, bool fromDataAnnotation = false); /// /// Prevents automatically creating a transaction when executing this stored procedure. diff --git a/src/EFCore.Relational/Metadata/IMutableStoredProcedure.cs b/src/EFCore.Relational/Metadata/IMutableStoredProcedure.cs index f2779df60f4..17fddb3dec5 100644 --- a/src/EFCore.Relational/Metadata/IMutableStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/IMutableStoredProcedure.cs @@ -35,7 +35,7 @@ public interface IMutableStoredProcedure : IReadOnlyStoredProcedure, IMutableAnn /// /// Gets or sets a value indicating whether this stored procedure returns the number of rows affected. /// - new bool AreRowsAffectedReturned { get; set; } + new bool IsRowsAffectedReturned { get; set; } /// /// Gets the parameters for this stored procedure. diff --git a/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedure.cs b/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedure.cs index b8a30347cd4..cb3bd5012ed 100644 --- a/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/IReadOnlyStoredProcedure.cs @@ -33,7 +33,7 @@ public interface IReadOnlyStoredProcedure : IReadOnlyAnnotatable /// /// Gets a value indicating whether this stored procedure returns the number of rows affected. /// - bool AreRowsAffectedReturned { get; } + bool IsRowsAffectedReturned { get; } /// /// Returns the store identifier of this stored procedure. diff --git a/src/EFCore.Relational/Metadata/IStoreStoredProcedure.cs b/src/EFCore.Relational/Metadata/IStoreStoredProcedure.cs index c4f8c692c6a..239c7ff8f26 100644 --- a/src/EFCore.Relational/Metadata/IStoreStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/IStoreStoredProcedure.cs @@ -23,7 +23,7 @@ public interface IStoreStoredProcedure : ITableBase /// /// Gets the return for this stored procedure. /// - IStoreStoredProcedureReturn? Return { get; } + IStoreStoredProcedureReturnValue? ReturnValue { get; } /// /// Gets the parameters for this stored procedure. diff --git a/src/EFCore.Relational/Metadata/IStoreStoredProcedureReturn.cs b/src/EFCore.Relational/Metadata/IStoreStoredProcedureReturnValue.cs similarity index 92% rename from src/EFCore.Relational/Metadata/IStoreStoredProcedureReturn.cs rename to src/EFCore.Relational/Metadata/IStoreStoredProcedureReturnValue.cs index 6674dcfde6c..4b2f148d255 100644 --- a/src/EFCore.Relational/Metadata/IStoreStoredProcedureReturn.cs +++ b/src/EFCore.Relational/Metadata/IStoreStoredProcedureReturnValue.cs @@ -8,7 +8,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata; /// /// Represents the return value of a stored procedure. /// -public interface IStoreStoredProcedureReturn : IColumnBase +public interface IStoreStoredProcedureReturnValue : IColumnBase { /// /// Gets the containing stored procedure. @@ -37,7 +37,7 @@ string ToDebugString(MetadataDebugStringOptions options = MetadataDebugStringOpt var singleLine = (options & MetadataDebugStringOptions.SingleLine) != 0; if (singleLine) { - builder.Append($"StoreStoredProcedureReturn: {Table.Name}."); + builder.Append($"StoreStoredProcedureReturnValue: {Table.Name}."); } builder.Append(Name).Append(" ("); diff --git a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs index a734c2cb3a4..2cd5e7877b5 100644 --- a/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs +++ b/src/EFCore.Relational/Metadata/Internal/InternalStoredProcedureBuilder.cs @@ -396,7 +396,7 @@ public virtual bool CanHaveRowsAffectedResultColumn(ConfigurationSource configur return null; } - Metadata.SetAreRowsAffectedReturned(rowsAffectedReturned); + Metadata.SetIsRowsAffectedReturned(rowsAffectedReturned); return this; } @@ -407,7 +407,7 @@ public virtual bool CanHaveRowsAffectedResultColumn(ConfigurationSource configur /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool CanHaveRowsAffectedReturn(bool rowsAffectedReturned, ConfigurationSource configurationSource) - => Metadata.AreRowsAffectedReturned == rowsAffectedReturned + => Metadata.IsRowsAffectedReturned == rowsAffectedReturned || configurationSource.Overrides(Metadata.GetConfigurationSource()); /// diff --git a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs index b8aea580f44..63b40a7f339 100644 --- a/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs +++ b/src/EFCore.Relational/Metadata/Internal/RelationalModel.cs @@ -1249,10 +1249,10 @@ static StoreStoredProcedure GetOrCreateStoreStoredProcedure( if (storeStoredProcedure == null) { storeStoredProcedure = new StoreStoredProcedure(storedProcedure, model); - if (storedProcedure.AreRowsAffectedReturned) + if (storedProcedure.IsRowsAffectedReturned) { var typeMapping = relationalTypeMappingSource.FindMapping(typeof(int))!; - storeStoredProcedure.Return = new StoreStoredProcedureReturn( + storeStoredProcedure.ReturnValue = new StoreStoredProcedureReturnValue( "", typeMapping.StoreType, storeStoredProcedure, diff --git a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedure.cs b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedure.cs index 3f23ac7f4d1..614c2c8e4af 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedure.cs @@ -43,7 +43,7 @@ public StoreStoredProcedure(IRuntimeStoredProcedure sproc, RelationalModel model /// 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 IStoreStoredProcedureReturn? Return { get; set; } + public virtual IStoreStoredProcedureReturnValue? ReturnValue { get; set; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureReturn.cs b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureReturnValue.cs similarity index 87% rename from src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureReturn.cs rename to src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureReturnValue.cs index cfac9fdf733..60f1226df99 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureReturn.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoreStoredProcedureReturnValue.cs @@ -9,7 +9,7 @@ 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 StoreStoredProcedureReturn : ColumnBase, IStoreStoredProcedureReturn +public class StoreStoredProcedureReturnValue : ColumnBase, IStoreStoredProcedureReturnValue { private readonly RelationalTypeMapping? _storeTypeMapping; @@ -19,7 +19,7 @@ public class StoreStoredProcedureReturn : ColumnBase, IStoreS /// 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 StoreStoredProcedureReturn( + public StoreStoredProcedureReturnValue( string name, string type, StoreStoredProcedure storedProcedure, @@ -54,7 +54,7 @@ public override RelationalTypeMapping StoreTypeMapping /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override string ToString() - => ((IStoreStoredProcedureReturn)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); + => ((IStoreStoredProcedureReturnValue)this).ToDebugString(MetadataDebugStringOptions.SingleLineDefault); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -65,11 +65,11 @@ public override string ToString() [EntityFrameworkInternal] public virtual DebugView DebugView => new( - () => ((IStoreStoredProcedureReturn)this).ToDebugString(), - () => ((IStoreStoredProcedureReturn)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); + () => ((IStoreStoredProcedureReturnValue)this).ToDebugString(), + () => ((IStoreStoredProcedureReturnValue)this).ToDebugString(MetadataDebugStringOptions.LongDefault)); /// - IStoreStoredProcedure IStoreStoredProcedureReturn.StoredProcedure + IStoreStoredProcedure IStoreStoredProcedureReturnValue.StoredProcedure { [DebuggerStepThrough] get => StoredProcedure; diff --git a/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs b/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs index cb99405a6d5..41a8e86c189 100644 --- a/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs @@ -23,7 +23,7 @@ public class StoredProcedure : private string? _name; private InternalStoredProcedureBuilder? _builder; private bool _areTransactionsSuppressed; - private bool _areRowsAffectedReturned; + private bool _isRowsAffectedReturned; private IStoreStoredProcedure? _storeStoredProcedure; private ConfigurationSource _configurationSource; @@ -472,10 +472,10 @@ public virtual bool SetAreTransactionsSuppressed(bool areTransactionsSuppressed, /// 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 AreRowsAffectedReturned + public virtual bool IsRowsAffectedReturned { - get => _areRowsAffectedReturned; - set => SetAreRowsAffectedReturned(value); + get => _isRowsAffectedReturned; + set => SetIsRowsAffectedReturned(value); } /// @@ -484,7 +484,7 @@ public virtual bool AreRowsAffectedReturned /// 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 SetAreRowsAffectedReturned(bool areRowsAffectedReturned) + public virtual bool SetIsRowsAffectedReturned(bool areRowsAffectedReturned) { EnsureMutable(); @@ -494,7 +494,7 @@ public virtual bool SetAreRowsAffectedReturned(bool areRowsAffectedReturned) ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); } - _areRowsAffectedReturned = areRowsAffectedReturned; + _isRowsAffectedReturned = areRowsAffectedReturned; return areRowsAffectedReturned; } @@ -629,7 +629,7 @@ public virtual StoredProcedureParameter AddRowsAffectedParameter() { if (_rowsAffectedParameter != null || _rowsAffectedResultColumn != null - || _areRowsAffectedReturned) + || _isRowsAffectedReturned) { throw new InvalidOperationException(RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter( ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); @@ -705,7 +705,7 @@ public virtual StoredProcedureResultColumn AddRowsAffectedResultColumn() { if (_rowsAffectedResultColumn != null || _rowsAffectedParameter != null - || _areRowsAffectedReturned) + || _isRowsAffectedReturned) { throw new InvalidOperationException(RelationalStrings.StoredProcedureDuplicateRowsAffectedResultColumn( ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName())); @@ -859,8 +859,8 @@ bool IConventionStoredProcedure.SetAreTransactionsSuppressed(bool areTransaction /// [DebuggerStepThrough] - bool IConventionStoredProcedure.SetAreRowsAffectedReturned(bool rowsAffectedReturned, bool fromDataAnnotation) - => SetAreRowsAffectedReturned(rowsAffectedReturned); + bool IConventionStoredProcedure.SetIsRowsAffectedReturned(bool rowsAffectedReturned, bool fromDataAnnotation) + => SetIsRowsAffectedReturned(rowsAffectedReturned); /// [DebuggerStepThrough] diff --git a/src/EFCore.Relational/Metadata/RuntimeStoredProcedure.cs b/src/EFCore.Relational/Metadata/RuntimeStoredProcedure.cs index b60a8c5c1ee..534a9f056ab 100644 --- a/src/EFCore.Relational/Metadata/RuntimeStoredProcedure.cs +++ b/src/EFCore.Relational/Metadata/RuntimeStoredProcedure.cs @@ -18,7 +18,7 @@ public class RuntimeStoredProcedure : AnnotatableBase, IRuntimeStoredProcedure private readonly List _resultColumns = new(); private readonly string? _schema; private readonly string _name; - private readonly bool _areRowsAffectedReturned; + private readonly bool _isRowsAffectedReturned; private readonly bool _areTransactionsSuppressed; private IStoreStoredProcedure? _storeStoredProcedure; @@ -28,20 +28,20 @@ public class RuntimeStoredProcedure : AnnotatableBase, IRuntimeStoredProcedure /// The mapped entity type. /// The name. /// The schema. - /// Whether this stored procedure returns the number of rows affected. - /// Whether the automatic transactions are surpressed. + /// Whether this stored procedure returns the number of rows affected. + /// Whether the automatic transactions are surpressed. public RuntimeStoredProcedure( RuntimeEntityType entityType, string name, string? schema, - bool areRowsAffectedReturned, - bool areTransactionsSuppressed) + bool rowsAffectedReturned, + bool transactionsSuppressed) { EntityType = entityType; _name = name; _schema = schema; - _areRowsAffectedReturned = areRowsAffectedReturned; - _areTransactionsSuppressed = areTransactionsSuppressed; + _isRowsAffectedReturned = rowsAffectedReturned; + _areTransactionsSuppressed = transactionsSuppressed; } /// @@ -159,10 +159,10 @@ bool IReadOnlyStoredProcedure.AreTransactionsSuppressed } /// - bool IReadOnlyStoredProcedure.AreRowsAffectedReturned + bool IReadOnlyStoredProcedure.IsRowsAffectedReturned { [DebuggerStepThrough] - get => _areRowsAffectedReturned; + get => _isRowsAffectedReturned; } /// diff --git a/src/EFCore.SqlServer/Extensions/SqlServerEntityTypeBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerEntityTypeBuilderExtensions.cs index 82d24774bbb..30c00ffcbb6 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerEntityTypeBuilderExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerEntityTypeBuilderExtensions.cs @@ -26,6 +26,7 @@ public static class SqlServerEntityTypeBuilderExtensions /// The builder for the entity type being configured. /// A value indicating whether the table is memory-optimized. /// The same builder instance so that multiple calls can be chained. + [Obsolete("Configure this using ToTable(t => t.IsMemoryOptimized()) instead.")] public static EntityTypeBuilder IsMemoryOptimized( this EntityTypeBuilder entityTypeBuilder, bool memoryOptimized = true) @@ -46,6 +47,7 @@ public static EntityTypeBuilder IsMemoryOptimized( /// The builder for the entity type being configured. /// A value indicating whether the table is memory-optimized. /// The same builder instance so that multiple calls can be chained. + [Obsolete("Configure this using ToTable(t => t.IsMemoryOptimized()) instead.")] public static EntityTypeBuilder IsMemoryOptimized( this EntityTypeBuilder entityTypeBuilder, bool memoryOptimized = true) @@ -62,6 +64,7 @@ public static EntityTypeBuilder IsMemoryOptimized( /// The builder for the entity type being configured. /// A value indicating whether the table is memory-optimized. /// The same builder instance so that multiple calls can be chained. + [Obsolete("Configure this using ToTable(t => t.IsMemoryOptimized()) instead.")] public static OwnedNavigationBuilder IsMemoryOptimized( this OwnedNavigationBuilder collectionOwnershipBuilder, bool memoryOptimized = true) @@ -83,6 +86,7 @@ public static OwnedNavigationBuilder IsMemoryOptimized( /// The builder for the entity type being configured. /// A value indicating whether the table is memory-optimized. /// The same builder instance so that multiple calls can be chained. + [Obsolete("Configure this using ToTable(t => t.IsMemoryOptimized()) instead.")] public static OwnedNavigationBuilder IsMemoryOptimized( this OwnedNavigationBuilder collectionOwnershipBuilder, bool memoryOptimized = true) diff --git a/src/EFCore.SqlServer/Extensions/SqlServerTableBuilderExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerTableBuilderExtensions.cs index ed44547fb7d..5c8a3c77696 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerTableBuilderExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerTableBuilderExtensions.cs @@ -178,4 +178,86 @@ public static OwnedNavigationTableBuilder IsTemp return tableBuilder; } + + /// + /// Configures the table that the entity maps to when targeting SQL Server as memory-optimized. + /// + /// + /// See Using SQL Server memory-optimized tables with EF Core + /// for more information and examples. + /// + /// The builder for the table being configured. + /// A value indicating whether the table is memory-optimized. + /// The same builder instance so that multiple calls can be chained. + public static TableBuilder IsMemoryOptimized( + this TableBuilder tableBuilder, + bool memoryOptimized = true) + { + tableBuilder.Metadata.SetIsMemoryOptimized(memoryOptimized); + + return tableBuilder; + } + + /// + /// Configures the table that the entity maps to when targeting SQL Server as memory-optimized. + /// + /// + /// See Using SQL Server memory-optimized tables with EF Core + /// for more information and examples. + /// + /// The entity type being configured. + /// The builder for the table being configured. + /// A value indicating whether the table is memory-optimized. + /// The same builder instance so that multiple calls can be chained. + public static TableBuilder IsMemoryOptimized( + this TableBuilder tableBuilder, + bool memoryOptimized = true) + where TEntity : class + { + tableBuilder.Metadata.SetIsMemoryOptimized(memoryOptimized); + + return tableBuilder; + } + + /// + /// Configures the table that the entity maps to when targeting SQL Server as memory-optimized. + /// + /// + /// See Using SQL Server memory-optimized tables with EF Core + /// for more information and examples. + /// + /// The builder for the table being configured. + /// A value indicating whether the table is memory-optimized. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationTableBuilder IsMemoryOptimized( + this OwnedNavigationTableBuilder tableBuilder, + bool memoryOptimized = true) + { + tableBuilder.Metadata.SetIsMemoryOptimized(memoryOptimized); + + return tableBuilder; + } + + /// + /// Configures the table that the entity maps to when targeting SQL Server as memory-optimized. + /// + /// + /// See Using SQL Server memory-optimized tables with EF Core + /// for more information and examples. + /// + /// 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 memory-optimized. + /// The same builder instance so that multiple calls can be chained. + public static OwnedNavigationTableBuilder IsMemoryOptimized( + this OwnedNavigationTableBuilder tableBuilder, + bool memoryOptimized = true) + where TOwnerEntity : class + where TDependentEntity : class + { + tableBuilder.Metadata.SetIsMemoryOptimized(memoryOptimized); + + return tableBuilder; + } } diff --git a/src/EFCore/Metadata/Builders/ConventionsBuilder.cs b/src/EFCore/Metadata/Builders/ConventionSetBuilder.cs similarity index 96% rename from src/EFCore/Metadata/Builders/ConventionsBuilder.cs rename to src/EFCore/Metadata/Builders/ConventionSetBuilder.cs index b19780faa44..bbb1a1602ba 100644 --- a/src/EFCore/Metadata/Builders/ConventionsBuilder.cs +++ b/src/EFCore/Metadata/Builders/ConventionSetBuilder.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Builders; /// /// See Modeling entity types and relationships for more information and examples. /// -public class ConventionsBuilder +public class ConventionSetBuilder { private readonly IServiceProvider _serviceProvider; private readonly ConventionSet _conventionSet; @@ -27,7 +27,7 @@ public class ConventionsBuilder /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - public ConventionsBuilder(ConventionSet conventionSet, IServiceProvider serviceProvider) + public ConventionSetBuilder(ConventionSet conventionSet, IServiceProvider serviceProvider) { Check.NotNull(conventionSet, nameof(conventionSet)); diff --git a/src/EFCore/ModelConfigurationBuilder.cs b/src/EFCore/ModelConfigurationBuilder.cs index 4b6349eaf90..5d8d16b5846 100644 --- a/src/EFCore/ModelConfigurationBuilder.cs +++ b/src/EFCore/ModelConfigurationBuilder.cs @@ -25,7 +25,7 @@ public class ModelConfigurationBuilder { private readonly ModelConfiguration _modelConfiguration = new(); private readonly ConventionSet _conventions; - private readonly ConventionsBuilder _conventionsBuilder; + private readonly ConventionSetBuilder _conventionSetBuilder; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -39,7 +39,7 @@ public ModelConfigurationBuilder(ConventionSet conventions, IServiceProvider ser Check.NotNull(conventions, nameof(conventions)); _conventions = conventions; - _conventionsBuilder = new ConventionsBuilder(conventions, serviceProvider); + _conventionSetBuilder = new ConventionSetBuilder(conventions, serviceProvider); } /// @@ -55,8 +55,8 @@ protected virtual ModelConfiguration ModelConfiguration /// /// Gets the builder for the conventions that will be used in the model. /// - public virtual ConventionsBuilder Conventions - => _conventionsBuilder; + public virtual ConventionSetBuilder Conventions + => _conventionSetBuilder; /// /// Prevents the conventions from the given type from discovering properties of the given or derived types. diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index e8c76a17815..ebccb55e6e6 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -140,11 +140,11 @@ public void Test_new_annotations_handled_for_entity_types() }, { RelationalAnnotationNames.Comment, ("My Comment", - _toTable - + ";" - + _nl - + _nl - + @"entityTypeBuilder.HasComment(""My Comment"")") + _nl + + @"entityTypeBuilder.ToTable(""WithAnnotations"", t =>" + _nl + + " {" + _nl + + @" t.HasComment(""My Comment"")" + _nl + + " })") }, { #pragma warning disable CS0612 // Type or member is obsolete diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 3b78902af6b..7d06744c188 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -1283,12 +1283,10 @@ public virtual void CheckConstraint_is_stored_in_snapshot_as_fluent_api() => Test( builder => { - builder.Entity() - .HasCheckConstraint("AlternateId", "AlternateId > Id", ck => - { - ck.HasName("CK_Customer_AlternateId"); - ck.HasAnnotation("foo", "bar"); - }); + builder.Entity().ToTable(tb => + tb.HasCheckConstraint("AlternateId", "AlternateId > Id") + .HasName("CK_Customer_AlternateId") + .HasAnnotation("foo", "bar")); builder.Ignore(); }, AddBoilerPlate( @@ -1307,13 +1305,11 @@ public virtual void CheckConstraint_is_stored_in_snapshot_as_fluent_api() b.HasKey(""Id""); - b.ToTable(""EntityWithTwoProperties""); - - b.HasCheckConstraint(""AlternateId"", ""AlternateId > Id"", c => + b.ToTable(""EntityWithTwoProperties"", t => { - c.HasName(""CK_Customer_AlternateId""); - - c.HasAnnotation(""foo"", ""bar""); + t.HasCheckConstraint(""AlternateId"", ""AlternateId > Id"") + .HasName(""CK_Customer_AlternateId"") + .HasAnnotation(""foo"", ""bar""); }); });"), o => @@ -1329,7 +1325,7 @@ public virtual void CheckConstraint_is_only_stored_in_snapshot_once_for_TPH() builder => { builder.Entity() - .HasCheckConstraint("CK_BaseEntity_AlternateId", "AlternateId > Id"); + .ToTable(tb => tb.HasCheckConstraint("CK_BaseEntity_AlternateId", "AlternateId > Id")); builder.Entity(); }, AddBoilerPlate( @@ -1363,9 +1359,12 @@ public virtual void CheckConstraint_is_only_stored_in_snapshot_once_for_TPH() b.Property(""Name"") .HasColumnType(""nvarchar(max)""); - b.HasDiscriminator().HasValue(""DerivedEntity""); + b.ToTable(t => + { + t.HasCheckConstraint(""CK_BaseEntity_AlternateId"", ""AlternateId > Id""); + }); - b.HasCheckConstraint(""CK_BaseEntity_AlternateId"", ""AlternateId > Id""); + b.HasDiscriminator().HasValue(""DerivedEntity""); });"), o => { @@ -1605,7 +1604,7 @@ public virtual void EntityType_Fluent_APIs_are_properly_generated() => Test( builder => { - builder.Entity().IsMemoryOptimized(); + builder.Entity().ToTable(tb => tb.IsMemoryOptimized()); builder.Ignore(); }, AddBoilerPlate( @@ -3495,7 +3494,7 @@ public virtual void Snapshot_with_OwnedNavigationBuilder_HasCheckConstraint_comp modelBuilder.Entity() .OwnsMany( o => o.OwnedEntities, - ownee => ownee.HasCheckConstraint("CK_TestOwnee_TestEnum_Enum_Constraint", "[TestEnum] IN (0, 1, 2)")); + ownee => ownee.ToTable(tb => tb.HasCheckConstraint("CK_TestOwnee_TestEnum_Enum_Constraint", "[TestEnum] IN (0, 1, 2)"))); }, @"// using Microsoft.EntityFrameworkCore; @@ -3548,9 +3547,10 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.HasKey(""TestOwnerId"", ""Id""); - b1.ToTable(""TestOwnee""); - - b1.HasCheckConstraint(""CK_TestOwnee_TestEnum_Enum_Constraint"", ""[TestEnum] IN (0, 1, 2)""); + b1.ToTable(""TestOwnee"", t => + { + t.HasCheckConstraint(""CK_TestOwnee_TestEnum_Enum_Constraint"", ""[TestEnum] IN (0, 1, 2)""); + }); b1.WithOwner() .HasForeignKey(""TestOwnerId""); diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs index 70c3919df2d..21ceebf0746 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs @@ -328,7 +328,7 @@ public void Entity_comments_use_fluent_api() "Entity", x => { - x.HasComment("An entity comment"); + x.ToTable(tb => tb.HasComment("An entity comment")); }), new ModelCodeGenerationOptions(), code => Assert.Contains( diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs index 1d1ecdabe84..363181bb2ee 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs @@ -1255,7 +1255,7 @@ public void Comments_are_generated() "Entity", x => { - x.HasComment("Entity Comment"); + x.ToTable(tb => tb.HasComment("Entity Comment")); x.Property("Id").HasComment("Property Comment"); }) , @@ -1296,10 +1296,10 @@ public void Comments_complex_are_generated() "Entity", x => { - x.HasComment( + x.ToTable(tb => tb.HasComment( @"Entity Comment On multiple lines -With XML content
"); +With XML content
")); x.Property("Id").HasComment( @"Property Comment On multiple lines diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 17e3fa07584..6e682558f61 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -2251,15 +2251,14 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) eb.OwnsMany( typeof(OwnedType).FullName, "ManyOwned", ob => { - ob.IsMemoryOptimized(); - ob.ToTable("ManyOwned", t => t.ExcludeFromMigrations()); + ob.ToTable("ManyOwned", t => t.IsMemoryOptimized().ExcludeFromMigrations()); }); eb.HasMany(e => e.Principals).WithMany(e => (ICollection>>)e.Deriveds) .UsingEntity( jb => { - jb.HasComment("Join table"); + jb.ToTable(tb => tb.HasComment("Join table")); jb.Property("rowid") .IsRowVersion() .HasComment("RowVersion") @@ -2757,7 +2756,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Equal(new[] { "PrincipalBaseId", "PrincipalDerivedId", "Id" }, insertSproc.Parameters.Select(p => p.PropertyName)); Assert.Empty(insertSproc.ResultColumns); Assert.True(insertSproc.AreTransactionsSuppressed); - Assert.False(insertSproc.AreRowsAffectedReturned); + Assert.False(insertSproc.IsRowsAffectedReturned); Assert.Equal("bar1", insertSproc["foo"]); Assert.Same(principalBase, insertSproc.EntityType); Assert.Equal("BaseId", insertSproc.Parameters.Last().Name); @@ -2770,7 +2769,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Equal(new[] { "PrincipalBaseId", "PrincipalDerivedId", "Id" }, updateSproc.Parameters.Select(p => p.PropertyName)); Assert.Empty(updateSproc.ResultColumns); Assert.False(updateSproc.AreTransactionsSuppressed); - Assert.False(updateSproc.AreRowsAffectedReturned); + Assert.False(updateSproc.IsRowsAffectedReturned); Assert.Empty(updateSproc.GetAnnotations()); Assert.Same(principalBase, updateSproc.EntityType); Assert.Equal("Id", updateSproc.Parameters.Last().Name); @@ -2782,7 +2781,7 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) Assert.Equal(new[] { "Id" }, deleteSproc.Parameters.Select(p => p.Name)); Assert.Empty(deleteSproc.ResultColumns); Assert.False(deleteSproc.AreTransactionsSuppressed); - Assert.True(deleteSproc.AreRowsAffectedReturned); + Assert.True(deleteSproc.IsRowsAffectedReturned); Assert.Same(principalBase, deleteSproc.EntityType); Assert.Equal("Id", deleteSproc.Parameters.Last().Name); Assert.Null(id.FindOverrides(StoreObjectIdentifier.Create(principalBase, StoreObjectType.DeleteStoredProcedure).Value)); @@ -4045,8 +4044,8 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) eb.Property("Id"); eb.HasKey("Id"); - eb.HasCheckConstraint("idConstraint", "Id <> 0"); - eb.HasCheckConstraint("anotherConstraint", "Id <> -1"); + eb.ToTable(tb => tb.HasCheckConstraint("idConstraint", "Id <> 0")); + eb.ToTable(tb => tb.HasCheckConstraint("anotherConstraint", "Id <> -1")); }); } } diff --git a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs index 5a94fb821c8..4a6f1e852d1 100644 --- a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs @@ -62,7 +62,11 @@ await Test( builder => builder.Entity( "People", e => { - e.ToTable("People", "dbo2"); + e.ToTable("People", "dbo2", tb => + { + tb.HasCheckConstraint("CK_People_EmployerId", $"{DelimitIdentifier("EmployerId")} > 0"); + tb.HasComment("Table comment"); + }); e.Property("CustomId"); e.Property("EmployerId") @@ -74,10 +78,7 @@ await Test( e.HasKey("CustomId"); e.HasAlternateKey("SSN"); - e.HasCheckConstraint("CK_People_EmployerId", $"{DelimitIdentifier("EmployerId")} > 0"); e.HasOne("Employers").WithMany("People").HasForeignKey("EmployerId"); - - e.HasComment("Table comment"); }), model => { @@ -158,7 +159,7 @@ public virtual Task Create_table_with_comments() { e.Property("Id"); e.Property("Name").HasComment("Column comment"); - e.HasComment("Table comment"); + e.ToTable(tb => tb.HasComment("Table comment")); }), model => { @@ -184,7 +185,7 @@ public virtual Task Create_table_with_multiline_comments() { e.Property("Id"); e.Property("Name").HasComment(columnComment); - e.HasComment(tableComment); + e.ToTable(tb => tb.HasComment(tableComment)); }), model => { @@ -235,7 +236,7 @@ public virtual Task Alter_table_add_comment() => Test( builder => builder.Entity("People").Property("Id"), builder => { }, - builder => builder.Entity("People").HasComment("Table comment"), + builder => builder.Entity("People").ToTable(tb => tb.HasComment("Table comment")), model => { var table = Assert.Single(model.Tables); @@ -253,8 +254,7 @@ public virtual Task Alter_table_add_comment_non_default_schema() .Property("Id"), builder => { }, builder => builder.Entity("People") - .ToTable("People", "SomeOtherSchema") - .HasComment("Table comment"), + .ToTable("People", "SomeOtherSchema", tb => tb.HasComment("Table comment")), model => { var table = Assert.Single(model.Tables); @@ -268,8 +268,8 @@ public virtual Task Alter_table_add_comment_non_default_schema() public virtual Task Alter_table_change_comment() => Test( builder => builder.Entity("People").Property("Id"), - builder => builder.Entity("People").HasComment("Table comment1"), - builder => builder.Entity("People").HasComment("Table comment2"), + builder => builder.Entity("People").ToTable(tb => tb.HasComment("Table comment1")), + builder => builder.Entity("People").ToTable(tb => tb.HasComment("Table comment2")), model => { var table = Assert.Single(model.Tables); @@ -283,7 +283,7 @@ public virtual Task Alter_table_change_comment() public virtual Task Alter_table_remove_comment() => Test( builder => builder.Entity("People").Property("Id"), - builder => builder.Entity("People").HasComment("Table comment1"), + builder => builder.Entity("People").ToTable(tb => tb.HasComment("Table comment1")), builder => { }, model => Assert.Null(Assert.Single(model.Tables).Comment)); @@ -652,7 +652,7 @@ public virtual Task Add_column_with_check_constraint() "People", e => { e.Property("DriverLicense"); - e.HasCheckConstraint("CK_People_Foo", $"{DelimitIdentifier("DriverLicense")} > 0"); + e.ToTable(tb => tb.HasCheckConstraint("CK_People_Foo", $"{DelimitIdentifier("DriverLicense")} > 0")); }), model => { @@ -1498,7 +1498,8 @@ public virtual Task Add_check_constraint_with_name() e.Property("DriverLicense"); }), builder => { }, - builder => builder.Entity("People").HasCheckConstraint("CK_People_Foo", $"{DelimitIdentifier("DriverLicense")} > 0"), + builder => builder.Entity("People") + .ToTable(tb => tb.HasCheckConstraint("CK_People_Foo", $"{DelimitIdentifier("DriverLicense")} > 0")), model => { // TODO: no scaffolding support for check constraints, https://github.com/aspnet/EntityFrameworkCore/issues/15408 @@ -1513,8 +1514,8 @@ public virtual Task Alter_check_constraint() e.Property("Id"); e.Property("DriverLicense"); }), - builder => builder.Entity("People").HasCheckConstraint("CK_People_Foo", $"{DelimitIdentifier("DriverLicense")} > 0"), - builder => builder.Entity("People").HasCheckConstraint("CK_People_Foo", $"{DelimitIdentifier("DriverLicense")} > 1"), + builder => builder.Entity("People").ToTable(tb => tb.HasCheckConstraint("CK_People_Foo", $"{DelimitIdentifier("DriverLicense")} > 0")), + builder => builder.Entity("People").ToTable(tb => tb.HasCheckConstraint("CK_People_Foo", $"{DelimitIdentifier("DriverLicense")} > 1")), model => { // TODO: no scaffolding support for check constraints, https://github.com/aspnet/EntityFrameworkCore/issues/15408 @@ -1529,7 +1530,7 @@ public virtual Task Drop_check_constraint() e.Property("Id"); e.Property("DriverLicense"); }), - builder => builder.Entity("People").HasCheckConstraint("CK_People_Foo", $"{DelimitIdentifier("DriverLicense")} > 0"), + builder => builder.Entity("People").ToTable(tb => tb.HasCheckConstraint("CK_People_Foo", $"{DelimitIdentifier("DriverLicense")} > 0")), builder => { }, model => { diff --git a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs index 502c51d1e00..59993486dfb 100644 --- a/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs +++ b/test/EFCore.Relational.Tests/Infrastructure/RelationalModelValidatorTest.cs @@ -350,8 +350,8 @@ public virtual void Detects_incompatible_comments_with_shared_table() var modelBuilder = CreateConventionModelBuilder(); modelBuilder.Entity().HasOne().WithOne(b => b.A).HasPrincipalKey(a => a.Id).HasForeignKey(b => b.Id).IsRequired(); - modelBuilder.Entity().ToTable("Table").HasComment("My comment"); - modelBuilder.Entity().ToTable("Table").HasComment("my comment"); + modelBuilder.Entity().ToTable("Table", tb => tb.HasComment("My comment")); + modelBuilder.Entity().ToTable("Table", tb => tb.HasComment("my comment")); VerifyError( RelationalStrings.IncompatibleTableCommentMismatch( @@ -365,7 +365,7 @@ public virtual void Passes_on_null_comments() var modelBuilder = CreateConventionModelBuilder(); modelBuilder.Entity().HasOne().WithOne(b => b.A).HasPrincipalKey(a => a.Id).HasForeignKey(b => b.Id).IsRequired(); - modelBuilder.Entity().ToTable("Table").HasComment("My comment"); + modelBuilder.Entity().ToTable("Table", tb => tb.HasComment("My comment")); modelBuilder.Entity().ToTable("Table"); Validate(modelBuilder); @@ -468,10 +468,8 @@ public virtual void Detects_incompatible_shared_check_constraints_with_shared_ta var modelBuilder = CreateConventionModelBuilder(); modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired(); - modelBuilder.Entity().HasCheckConstraint("SomeCK", "Id > 0", c => c.HasName("CK_Table_SomeCK")); - modelBuilder.Entity().ToTable("Table"); - modelBuilder.Entity().HasCheckConstraint("SomeOtherCK", "Id > 10", c => c.HasName("CK_Table_SomeCK")); - modelBuilder.Entity().ToTable("Table"); + modelBuilder.Entity().ToTable("Table", tb => tb.HasCheckConstraint("SomeCK", "Id > 0").HasName("CK_Table_SomeCK")); + modelBuilder.Entity().ToTable("Table", tb => tb.HasCheckConstraint("SomeOtherCK", "Id > 10").HasName("CK_Table_SomeCK")); VerifyError( RelationalStrings.DuplicateCheckConstraintSqlMismatch( @@ -485,10 +483,8 @@ public virtual void Passes_for_incompatible_uniquified_check_constraints_with_sh var modelBuilder = CreateConventionModelBuilder(); modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired(); - modelBuilder.Entity().HasCheckConstraint("CK_Table_SomeCK", "Id > 0"); - modelBuilder.Entity().ToTable("Table"); - modelBuilder.Entity().HasCheckConstraint("CK_Table_SomeCK", "Id > 10"); - modelBuilder.Entity().ToTable("Table"); + modelBuilder.Entity().ToTable("Table", tb => tb.HasCheckConstraint("CK_Table_SomeCK", "Id > 0")); + modelBuilder.Entity().ToTable("Table", tb => tb.HasCheckConstraint("CK_Table_SomeCK", "Id > 10")); var model = Validate(modelBuilder); @@ -502,10 +498,8 @@ public virtual void Passes_for_compatible_shared_check_constraints_with_shared_t var modelBuilder = CreateConventionModelBuilder(); modelBuilder.Entity().HasOne().WithOne(b => b.A).HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired(); - modelBuilder.Entity().HasCheckConstraint("CK_Table_SomeCK", "Id > 0"); - modelBuilder.Entity().ToTable("Table"); - modelBuilder.Entity().HasCheckConstraint("CK_Table_SomeCK", "Id > 0"); - modelBuilder.Entity().ToTable("Table"); + modelBuilder.Entity().ToTable("Table", tb => tb.HasCheckConstraint("CK_Table_SomeCK", "Id > 0")); + modelBuilder.Entity().ToTable("Table", tb => tb.HasCheckConstraint("CK_Table_SomeCK", "Id > 0")); var model = Validate(modelBuilder); diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs index 68916ff69f0..621af188cc7 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalBuilderExtensionsTest.cs @@ -510,7 +510,7 @@ public void Can_create_check_constraint() modelBuilder .Entity() - .HasCheckConstraint("CK_Customer_AlternateId", "AlternateId > Id"); + .ToTable(tb => tb.HasCheckConstraint("CK_Customer_AlternateId", "AlternateId > Id")); var checkConstraint = entityType.FindCheckConstraint("CK_Customer_AlternateId"); @@ -528,11 +528,11 @@ public void Can_create_check_constraint_with_duplicate_name_replaces_existing() modelBuilder .Entity() - .HasCheckConstraint("CK_Customer_AlternateId", "AlternateId > Id"); + .ToTable(tb => tb.HasCheckConstraint("CK_Customer_AlternateId", "AlternateId > Id")); modelBuilder .Entity() - .HasCheckConstraint("CK_Customer_AlternateId", "AlternateId < Id"); + .ToTable(tb => tb.HasCheckConstraint("CK_Customer_AlternateId", "AlternateId < Id")); var checkConstraint = entityType.FindCheckConstraint("CK_Customer_AlternateId"); diff --git a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs index f481a54fdba..b29bef3628a 100644 --- a/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/RelationalModelTest.cs @@ -416,17 +416,22 @@ private static void AssertViews(IRelationalModel model, Mapping mapping) Assert.Null(ordersCustomerIndex.GetDefaultDatabaseName( StoreObjectIdentifier.Table(ordersView.Name, ordersView.Schema))); - var specialtyCK = specialCustomerType.GetCheckConstraints().Single(); - Assert.Equal(mappedToTable - ? "Specialty" - : null, specialtyCK.Name); - Assert.Null(specialtyCK.GetName( - StoreObjectIdentifier.Table(ordersView.Name, ordersView.Schema))); - Assert.Equal(mappedToTable - ? "Specialty" - : null, specialtyCK.GetDefaultName()); - Assert.Equal("Specialty", specialtyCK.GetDefaultName( - StoreObjectIdentifier.Table(ordersView.Name, ordersView.Schema))); + if (mappedToTable) + { + var specialtyCK = specialCustomerType.GetCheckConstraints().Single(); + Assert.Equal("Specialty", specialtyCK.Name); + Assert.Null( + specialtyCK.GetName( + StoreObjectIdentifier.Table(ordersView.Name, ordersView.Schema))); + Assert.Equal("Specialty", specialtyCK.GetDefaultName()); + Assert.Equal( + "Specialty", specialtyCK.GetDefaultName( + StoreObjectIdentifier.Table(ordersView.Name, ordersView.Schema))); + } + else + { + Assert.Empty(specialCustomerType.GetCheckConstraints()); + } Assert.Equal(mappedToTable ? "PK_Order" @@ -2091,10 +2096,13 @@ private IRelationalModel CreateTestModel( } } - cb.HasCheckConstraint("Specialty", "[Specialty] IN ('Specialist', 'Generalist')"); - cb.Property(s => s.Specialty).IsRequired(); + if (cb.Metadata.GetTableName() != null) + { + cb.ToTable(tb => tb.HasCheckConstraint("Specialty", "[Specialty] IN ('Specialist', 'Generalist')")); + } + cb.HasOne(c => c.RelatedCustomer).WithOne() .HasForeignKey(c => c.RelatedCustomerSpecialty) .HasPrincipalKey("SpecialtyAk"); // TODO: Use the derived one, #2611 diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs index 47c449c5160..d7325bdf543 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTest.cs @@ -407,14 +407,13 @@ public void Create_table() "Node", x => { - x.ToTable("Node", "dbo"); + x.ToTable("Node", "dbo", tb => tb.HasCheckConstraint("CK_Node_SomeCheckConstraint", "[Id] > 10")); x.Property("Id"); x.Property("AltId"); x.HasAlternateKey("AltId"); x.Property("ParentAltId"); x.HasOne("Node").WithMany().HasForeignKey("ParentAltId"); x.HasIndex("ParentAltId"); - x.HasCheckConstraint("CK_Node_SomeCheckConstraint", "[Id] > 10"); }), upOps => { @@ -907,17 +906,15 @@ public void Alter_table_comment() "MountainLion", x => { - x.ToTable("MountainLion", "dbo"); + x.ToTable("MountainLion", "dbo", tb => tb.HasComment("Old comment")); x.Property("Id"); - x.HasComment("Old comment"); }), target => target.Entity( "MountainLion", x => { - x.ToTable("MountainLion", "dbo"); + x.ToTable("MountainLion", "dbo", tb => tb.HasComment("New comment")); x.Property("Id"); - x.HasComment("New comment"); }), operations => { @@ -3252,10 +3249,9 @@ public void Add_check_constraint() "Flamingo", x => { - x.ToTable("Flamingo", "dbo"); + x.ToTable("Flamingo", "dbo", tb => tb.HasCheckConstraint("CK_Flamingo_AlternateId", "AlternateId > Id")); x.Property("Id"); x.Property("AlternateId"); - x.HasCheckConstraint("CK_Flamingo_AlternateId", "AlternateId > Id"); }), operations => { @@ -3275,10 +3271,9 @@ public void Drop_check_constraint() "Penguin", x => { - x.ToTable("Penguin", "dbo"); + x.ToTable("Penguin", "dbo", tb => tb.HasCheckConstraint("CK_Penguin_AlternateId", "AlternateId > Id")); x.Property("Id"); x.Property("AlternateId"); - x.HasCheckConstraint("CK_Penguin_AlternateId", "AlternateId > Id"); }), target => target.Entity( "Penguin", @@ -3305,19 +3300,17 @@ public void Rename_check_constraint() "Pelican", x => { - x.ToTable("Pelican", "dbo"); + x.ToTable("Pelican", "dbo", tb => tb.HasCheckConstraint("CK_Pelican_AlternateId", "AlternateId > Id")); x.Property("Id"); x.Property("AlternateId"); - x.HasCheckConstraint("CK_Pelican_AlternateId", "AlternateId > Id"); }), target => target.Entity( "Pelican", x => { - x.ToTable("Pelican", "dbo"); + x.ToTable("Pelican", "dbo", tb => tb.HasCheckConstraint("CK_Pelican_AlternateId", "AlternateId > Id").HasName("CK_Flamingo")); x.Property("Id"); x.Property("AlternateId"); - x.HasCheckConstraint("CK_Pelican_AlternateId", "AlternateId > Id", c => c.HasName("CK_Flamingo")); }), operations => { @@ -3342,19 +3335,17 @@ public void Alter_check_constraint_expression() "Rook", x => { - x.ToTable("Rook", "dbo"); + x.ToTable("Rook", "dbo", tb => tb.HasCheckConstraint("CK_Rook_AlternateId", "AlternateId > Id")); x.Property("Id"); x.Property("AlternateId"); - x.HasCheckConstraint("CK_Rook_AlternateId", "AlternateId > Id"); }), target => target.Entity( "Rook", x => { - x.ToTable("Rook", "dbo"); + x.ToTable("Rook", "dbo", tb => tb.HasCheckConstraint("CK_Rook_AlternateId", "AlternateId < Id")); x.Property("Id"); x.Property("AlternateId"); - x.HasCheckConstraint("CK_Rook_AlternateId", "AlternateId < Id"); }), operations => { diff --git a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs index 7963d2c2521..e8613fc4bb5 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalModelBuilderTest.cs @@ -447,7 +447,7 @@ public virtual void Sproc_overrides_update_when_renamed_in_TPH() Assert.Null(insertSproc.FindResultColumn("Discriminator")); Assert.False(insertSproc.FindResultColumn("Id")!.ForRowsAffected); Assert.True(insertSproc.AreTransactionsSuppressed); - Assert.True(insertSproc.AreRowsAffectedReturned); + Assert.True(insertSproc.IsRowsAffectedReturned); Assert.Equal("bar1", insertSproc["foo"]); Assert.Same(bookLabel, insertSproc.EntityType); @@ -459,7 +459,7 @@ public virtual void Sproc_overrides_update_when_renamed_in_TPH() Assert.True(updateSproc.AreTransactionsSuppressed); Assert.Equal("bar2", updateSproc["foo"]); Assert.Same(bookLabel, updateSproc.EntityType); - Assert.False(updateSproc.AreRowsAffectedReturned); + Assert.False(updateSproc.IsRowsAffectedReturned); var rowsAffectedParameter = updateSproc.Parameters[2]; Assert.Null(rowsAffectedParameter.ForOriginalValue); @@ -487,7 +487,7 @@ public virtual void Sproc_overrides_update_when_renamed_in_TPH() Assert.True(deleteSproc.AreTransactionsSuppressed); Assert.Equal("bar3", deleteSproc["foo"]); Assert.Same(bookLabel, deleteSproc.EntityType); - Assert.False(deleteSproc.AreRowsAffectedReturned); + Assert.False(deleteSproc.IsRowsAffectedReturned); var rowsAffectedResultColumn = deleteSproc.ResultColumns[0]; Assert.True(rowsAffectedResultColumn.ForRowsAffected); @@ -880,6 +880,10 @@ public abstract class TestTableBuilder public abstract TestTableBuilder ExcludeFromMigrations(bool excluded = true); + public abstract TestCheckConstraintBuilder HasCheckConstraint( + string name, + string? sql); + public abstract TestTriggerBuilder HasTrigger(string name); public abstract TestColumnBuilder Property(string propertyName); @@ -912,6 +916,9 @@ protected virtual TestTableBuilder Wrap(TableBuilder tableBuil public override TestTableBuilder ExcludeFromMigrations(bool excluded = true) => Wrap(TableBuilder.ExcludeFromMigrations(excluded)); + public override TestCheckConstraintBuilder HasCheckConstraint(string name, string? sql) + => new(TableBuilder.HasCheckConstraint(name, sql)); + public override TestTriggerBuilder HasTrigger(string name) => new NonGenericTestTriggerBuilder(TableBuilder.HasTrigger(name)); @@ -947,6 +954,9 @@ protected virtual TestTableBuilder Wrap(TableBuilder tableBuilder) public override TestTableBuilder ExcludeFromMigrations(bool excluded = true) => Wrap(TableBuilder.ExcludeFromMigrations(excluded)); + public override TestCheckConstraintBuilder HasCheckConstraint(string name, string? sql) + => new(TableBuilder.HasCheckConstraint(name, sql)); + public override TestTriggerBuilder HasTrigger(string name) => new NonGenericTestTriggerBuilder(TableBuilder.HasTrigger(name)); @@ -966,7 +976,11 @@ public abstract class TestOwnedNavigationTableBuilder ExcludeFromMigrations(bool excluded = true); - + + public abstract TestCheckConstraintBuilder HasCheckConstraint( + string name, + string? sql); + public abstract TestColumnBuilder Property(string propertyName); public abstract TestColumnBuilder Property(Expression> propertyExpression); @@ -1002,6 +1016,9 @@ protected virtual TestOwnedNavigationTableBuilder ExcludeFromMigrations(bool excluded = true) => Wrap(TableBuilder.ExcludeFromMigrations(excluded)); + public override TestCheckConstraintBuilder HasCheckConstraint(string name, string? sql) + => new(TableBuilder.HasCheckConstraint(name, sql)); + public override TestColumnBuilder Property(string propertyName) => new NonGenericTestColumnBuilder(TableBuilder.Property(propertyName)); @@ -1037,6 +1054,9 @@ protected virtual TestOwnedNavigationTableBuilder ExcludeFromMigrations(bool excluded = true) => Wrap(TableBuilder.ExcludeFromMigrations(excluded)); + public override TestCheckConstraintBuilder HasCheckConstraint(string name, string? sql) + => new(TableBuilder.HasCheckConstraint(name, sql)); + public override TestColumnBuilder Property(string propertyName) => new NonGenericTestColumnBuilder(TableBuilder.Property(propertyName)); @@ -2399,16 +2419,9 @@ public override TestTriggerBuilder HasAnnotation(string annotation, object? valu => 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 + public class TestCheckConstraintBuilder : IInfrastructure { - public NonGenericTestCheckConstraintBuilder(CheckConstraintBuilder checkConstraintBuilder) + public TestCheckConstraintBuilder(CheckConstraintBuilder checkConstraintBuilder) { CheckConstraintBuilder = checkConstraintBuilder; } @@ -2419,12 +2432,12 @@ CheckConstraintBuilder IInfrastructure.Instance => CheckConstraintBuilder; protected virtual TestCheckConstraintBuilder Wrap(CheckConstraintBuilder checkConstraintBuilder) - => new NonGenericTestCheckConstraintBuilder(checkConstraintBuilder); + => new TestCheckConstraintBuilder(checkConstraintBuilder); - public override TestCheckConstraintBuilder HasName(string name) + public virtual TestCheckConstraintBuilder HasName(string name) => Wrap(CheckConstraintBuilder.HasName(name)); - public override TestCheckConstraintBuilder HasAnnotation(string annotation, object? value) + public virtual 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 1b7f8610534..47e89f7fc76 100644 --- a/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs +++ b/test/EFCore.Relational.Tests/ModelBuilding/RelationalTestModelBuilderExtensions.cs @@ -1258,96 +1258,6 @@ public static ModelBuilderTest.TestEntityTypeBuilder DeleteUsingStoredP return builder; } - public static ModelBuilderTest.TestEntityTypeBuilder HasCheckConstraint( - this ModelBuilderTest.TestEntityTypeBuilder builder, - string name, - string? sql) - where TEntity : class - { - switch (builder) - { - case IInfrastructure> genericBuilder: - genericBuilder.Instance.HasCheckConstraint(name, sql); - break; - case IInfrastructure nonGenericBuilder: - nonGenericBuilder.Instance.HasCheckConstraint(name, sql); - break; - } - - return builder; - } - - public static ModelBuilderTest.TestEntityTypeBuilder HasCheckConstraint( - this ModelBuilderTest.TestEntityTypeBuilder builder, - string name, - string sql, - Action buildAction) - where TEntity : class - { - switch (builder) - { - case IInfrastructure> genericBuilder: - genericBuilder.Instance.HasCheckConstraint( - name, sql, - b => buildAction(new RelationalModelBuilderTest.NonGenericTestCheckConstraintBuilder(b))); - break; - case IInfrastructure nonGenericBuilder: - nonGenericBuilder.Instance.HasCheckConstraint( - name, sql, - b => buildAction(new RelationalModelBuilderTest.NonGenericTestCheckConstraintBuilder(b))); - break; - } - - return builder; - } - - public static ModelBuilderTest.TestOwnedNavigationBuilder HasCheckConstraint - ( - this ModelBuilderTest.TestOwnedNavigationBuilder builder, - string name, - string? sql) - where TOwnerEntity : class - where TDependentEntity : class - { - switch (builder) - { - case IInfrastructure> genericBuilder: - genericBuilder.Instance.HasCheckConstraint(name, sql); - break; - case IInfrastructure nonGenericBuilder: - nonGenericBuilder.Instance.HasCheckConstraint(name, sql); - break; - } - - return builder; - } - - public static ModelBuilderTest.TestOwnedNavigationBuilder HasCheckConstraint - ( - this ModelBuilderTest.TestOwnedNavigationBuilder builder, - string name, - string sql, - Action buildAction) - where TOwnerEntity : class - where TDependentEntity : class - { - switch (builder) - { - case IInfrastructure> genericBuilder: - genericBuilder.Instance.HasCheckConstraint( - name, sql, - b => buildAction(new RelationalModelBuilderTest.NonGenericTestCheckConstraintBuilder(b))); - break; - case IInfrastructure nonGenericBuilder: - nonGenericBuilder.Instance.HasCheckConstraint( - name, sql, - b => buildAction(new RelationalModelBuilderTest.NonGenericTestCheckConstraintBuilder(b))); - break; - } - - return builder; - } - public static ModelBuilderTest.TestOwnedNavigationBuilder ToJson( this ModelBuilderTest.TestOwnedNavigationBuilder builder) where TOwnerEntity : class diff --git a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs index 29fecbd5d3f..048c0cd81b6 100644 --- a/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs +++ b/test/EFCore.Relational.Tests/RelationalApiConsistencyTest.cs @@ -333,7 +333,7 @@ public override typeof(Action) }), typeof(IConventionStoredProcedure).GetMethod( - nameof(IConventionStoredProcedure.SetAreRowsAffectedReturned), + nameof(IConventionStoredProcedure.SetIsRowsAffectedReturned), new[] { typeof(bool), typeof(bool) }) }; diff --git a/test/EFCore.SqlServer.FunctionalTests/MemoryOptimizedTablesTest.cs b/test/EFCore.SqlServer.FunctionalTests/MemoryOptimizedTablesTest.cs index 4eee030c021..091b0b5c337 100644 --- a/test/EFCore.SqlServer.FunctionalTests/MemoryOptimizedTablesTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/MemoryOptimizedTablesTest.cs @@ -76,12 +76,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .Entity( eb => { - eb.IsMemoryOptimized(); + eb.ToTable(tb => tb.IsMemoryOptimized()); eb.HasIndex(e => e.Name).IsUnique(); eb.HasOne(e => e.BigUn).WithMany(e => e.FastUns).IsRequired().OnDelete(DeleteBehavior.Restrict); }); - modelBuilder.Entity().IsMemoryOptimized(); + modelBuilder.Entity().ToTable(tb => tb.IsMemoryOptimized()); } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs index 293f0032c15..5764028c12e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Migrations/MigrationsSqlServerTest.cs @@ -175,7 +175,7 @@ await Test( _ => { }, builder => builder.UseIdentityColumns().Entity("People", b => { - b.IsMemoryOptimized(); + b.ToTable(tb => tb.IsMemoryOptimized()); b.Property("Id"); }), model => @@ -236,8 +236,7 @@ await Test( _ => { }, builder => builder.UseIdentityColumns().Entity("People", b => { - b.ToTable("Customers", tb => tb.IsTemporal()); - b.IsMemoryOptimized(); + b.ToTable("Customers", tb => tb.IsMemoryOptimized().IsTemporal()); b.Property("Id"); }), model => @@ -1088,7 +1087,7 @@ await Test( builder => builder.Entity( "People", e => { - e.IsMemoryOptimized(); + e.ToTable(tb => tb.IsMemoryOptimized()); e.Property("Id"); e.Property("Name"); e.HasKey("Id").IsClustered(false); @@ -1863,7 +1862,8 @@ await Test( { e.Property("Id"); e.Property("Name"); - e.IsMemoryOptimized().HasKey("Id").IsClustered(false); + e.ToTable(tb => tb.IsMemoryOptimized()); + e.HasKey("Id").IsClustered(false); }), builder => { }, builder => builder.Entity("People").HasIndex("Name").IsUnique(), @@ -1897,7 +1897,8 @@ await Test( { e.Property("Id"); e.Property("Name"); - e.IsMemoryOptimized().HasKey("Id").IsClustered(false); + e.ToTable(tb => tb.IsMemoryOptimized()); + e.HasKey("Id").IsClustered(false); }), builder => { }, builder => builder.Entity("People").HasIndex("Name").IsUnique().HasFilter("[Name] IS NOT NULL AND <> ''"), @@ -1931,7 +1932,9 @@ await Test( "People", e => { e.Property("Name").IsRequired(); - e.IsMemoryOptimized().HasKey("Name").IsClustered(false); + e.ToTable(tb => tb.IsMemoryOptimized()); + e.ToTable(tb => tb.IsMemoryOptimized()); + e.HasKey("Name").IsClustered(false); }), builder => { }, builder => builder.Entity("People").HasIndex("Name").IsUnique().IsClustered(false), @@ -2131,7 +2134,7 @@ await Test( e.Property("DriverLicense"); }), builder => { }, - builder => builder.Entity("People").HasCheckConstraint("CK_People_Foo", "[DriverLicense] > 0"), + builder => builder.Entity("People").ToTable(tb => tb.HasCheckConstraint("CK_People_Foo", "[DriverLicense] > 0")), model => { // TODO: no scaffolding support for check constraints, https://github.com/aspnet/EntityFrameworkCore/issues/15408 @@ -5672,7 +5675,6 @@ await Test( e.Property("SystemTimeStart").ValueGeneratedOnAddOrUpdate(); e.Property("SystemTimeEnd").ValueGeneratedOnAddOrUpdate(); e.HasKey("Id"); - e.HasComment("Table comment"); e.ToTable( tb => tb.IsTemporal( @@ -5680,7 +5682,8 @@ await Test( { ttb.HasPeriodStart("SystemTimeStart"); ttb.HasPeriodEnd("SystemTimeEnd"); - })); + }) + .HasComment("Table comment")); }), model => { @@ -5822,13 +5825,13 @@ await Test( "Customer", e => { e.Property("Name").HasComment("Column comment"); - e.HasComment("Table comment"); + e.ToTable(tb => tb.HasComment("Table comment")); }), builder => builder.Entity( "Customer", e => { e.Property("Name").HasComment("Modified column comment"); - e.HasComment("Modified table comment"); + e.ToTable(tb => tb.HasComment("Modified table comment")); }), model => { diff --git a/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs b/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs index bf2e5913dfe..65f248d701e 100644 --- a/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs +++ b/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs @@ -370,7 +370,7 @@ public void GenerateFluentApi_IEntityType_works_when_IsMemoryOptimized() x => { x.Property("Id"); - x.IsMemoryOptimized(); + x.ToTable(tb => tb.IsMemoryOptimized()); }); var entityType = (IEntityType)modelBuilder.Model.FindEntityType("Post")!; diff --git a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs index 76341ddf7dc..5d3af3b9ad8 100644 --- a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs +++ b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs @@ -470,7 +470,7 @@ public virtual void Detects_incompatible_memory_optimized_shared_table() modelBuilder.Entity().HasOne().WithOne().HasForeignKey(a => a.Id).HasPrincipalKey(b => b.Id).IsRequired(); - modelBuilder.Entity().ToTable("Table").IsMemoryOptimized(); + modelBuilder.Entity().ToTable("Table", tb => tb.IsMemoryOptimized()); modelBuilder.Entity().ToTable("Table"); diff --git a/test/EFCore.SqlServer.Tests/Metadata/Conventions/SqlServerMemoryOptimizedTablesConventionTest.cs b/test/EFCore.SqlServer.Tests/Metadata/Conventions/SqlServerMemoryOptimizedTablesConventionTest.cs index ad0826b7780..e9b152b3fa1 100644 --- a/test/EFCore.SqlServer.Tests/Metadata/Conventions/SqlServerMemoryOptimizedTablesConventionTest.cs +++ b/test/EFCore.SqlServer.Tests/Metadata/Conventions/SqlServerMemoryOptimizedTablesConventionTest.cs @@ -15,7 +15,7 @@ public void Keys_and_indexes_are_nonclustered_for_memory_optimized_tables() Assert.True(modelBuilder.Model.FindEntityType(typeof(Order)).GetKeys().All(k => k.IsClustered() == null)); Assert.True(modelBuilder.Model.FindEntityType(typeof(Order)).GetIndexes().All(k => k.IsClustered() == null)); - modelBuilder.Entity().IsMemoryOptimized(); + modelBuilder.Entity().ToTable(tb => tb.IsMemoryOptimized()); modelBuilder.Entity().HasKey( o => new { o.Id, o.CustomerId }); @@ -24,7 +24,7 @@ public void Keys_and_indexes_are_nonclustered_for_memory_optimized_tables() Assert.True(modelBuilder.Model.FindEntityType(typeof(Order)).GetKeys().All(k => k.IsClustered() == false)); Assert.True(modelBuilder.Model.FindEntityType(typeof(Order)).GetIndexes().All(k => k.IsClustered() == false)); - modelBuilder.Entity().IsMemoryOptimized(false); + modelBuilder.Entity().ToTable(tb => tb.IsMemoryOptimized(false)); Assert.True(modelBuilder.Model.FindEntityType(typeof(Order)).GetKeys().All(k => k.IsClustered() == null)); Assert.True(modelBuilder.Model.FindEntityType(typeof(Order)).GetIndexes().All(k => k.IsClustered() == null)); diff --git a/test/EFCore.SqlServer.Tests/Metadata/SqlServerBuilderExtensionsTest.cs b/test/EFCore.SqlServer.Tests/Metadata/SqlServerBuilderExtensionsTest.cs index 250e98708a9..dbfb6aee0d0 100644 --- a/test/EFCore.SqlServer.Tests/Metadata/SqlServerBuilderExtensionsTest.cs +++ b/test/EFCore.SqlServer.Tests/Metadata/SqlServerBuilderExtensionsTest.cs @@ -65,7 +65,7 @@ public void Can_set_MemoryOptimized() modelBuilder .Entity() - .IsMemoryOptimized(); + .ToTable(tb => tb.IsMemoryOptimized()); var entityType = modelBuilder.Model.FindEntityType(typeof(Customer)); @@ -73,7 +73,7 @@ public void Can_set_MemoryOptimized() modelBuilder .Entity() - .IsMemoryOptimized(false); + .ToTable(tb => tb.IsMemoryOptimized(false)); Assert.False(entityType.IsMemoryOptimized()); } @@ -85,7 +85,7 @@ public void Can_set_MemoryOptimized_non_generic() modelBuilder .Entity(typeof(Customer)) - .IsMemoryOptimized(); + .ToTable(tb => tb.IsMemoryOptimized()); var entityType = modelBuilder.Model.FindEntityType(typeof(Customer)); @@ -93,7 +93,7 @@ public void Can_set_MemoryOptimized_non_generic() modelBuilder .Entity(typeof(Customer)) - .IsMemoryOptimized(false); + .ToTable(tb => tb.IsMemoryOptimized(false)); Assert.False(entityType.IsMemoryOptimized()); } @@ -970,27 +970,6 @@ public void Can_set_identities_with_seed_and_identity_for_property() Assert.Null(model.FindSequence(SqlServerModelExtensions.DefaultHiLoSequenceName)); } - [ConditionalFact] - public void SqlServer_entity_methods_dont_break_out_of_the_generics() - { - var modelBuilder = CreateConventionModelBuilder(); - - AssertIsGeneric( - modelBuilder - .Entity() - .IsMemoryOptimized()); - } - - [ConditionalFact] - public void SqlServer_entity_methods_have_non_generic_overloads() - { - var modelBuilder = CreateConventionModelBuilder(); - - modelBuilder - .Entity(typeof(Customer)) - .IsMemoryOptimized(); - } - [ConditionalFact] public void SqlServer_property_methods_dont_break_out_of_the_generics() { diff --git a/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs b/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs index 9b9ccbe02c3..bb3992c9ab7 100644 --- a/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs +++ b/test/EFCore.SqlServer.Tests/Migrations/SqlServerModelDifferTest.cs @@ -57,7 +57,7 @@ public void Alter_table_MemoryOptimized() "Person", x => { - x.IsMemoryOptimized(); + x.ToTable(tb => tb.IsMemoryOptimized()); }), upOps => { @@ -107,7 +107,7 @@ public void Add_table_MemoryOptimized() "Person", x => { - x.IsMemoryOptimized(); + x.ToTable(tb => tb.IsMemoryOptimized()); }), upOps => { diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs index 2bab0ba44e1..9b4743ef6dd 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerModelBuilderTestBase.cs @@ -424,10 +424,13 @@ public void Can_add_check_constraints() var modelBuilder = CreateModelBuilder(); modelBuilder.Entity() .HasBaseType(null) - .HasCheckConstraint("CK_ChildBase_LargeId", "Id > 1000", c => c.HasName("CK_LargeId")); + .ToTable(tb => tb.HasCheckConstraint("CK_ChildBase_LargeId", "Id > 1000").HasName("CK_LargeId")); modelBuilder.Entity() - .HasCheckConstraint("PositiveId", "Id > 0") - .HasCheckConstraint("CK_ChildBase_LargeId", "Id > 1000"); + .ToTable(tb => + { + tb.HasCheckConstraint("PositiveId", "Id > 0"); + tb.HasCheckConstraint("CK_ChildBase_LargeId", "Id > 1000"); + }); modelBuilder.Entity() .HasBaseType(); modelBuilder.Entity(); @@ -457,12 +460,12 @@ public void Adding_conflicting_check_constraint_to_derived_type_throws() { var modelBuilder = CreateModelBuilder(); modelBuilder.Entity() - .HasCheckConstraint("LargeId", "Id > 100", c => c.HasName("CK_LargeId")); + .ToTable(tb => tb.HasCheckConstraint("LargeId", "Id > 100").HasName("CK_LargeId")); Assert.Equal( RelationalStrings.DuplicateCheckConstraint("LargeId", nameof(Child), nameof(ChildBase)), Assert.Throws( - () => modelBuilder.Entity().HasCheckConstraint("LargeId", "Id > 1000")).Message); + () => modelBuilder.Entity().ToTable(tb => tb.HasCheckConstraint("LargeId", "Id > 1000"))).Message); } [ConditionalFact] @@ -471,9 +474,9 @@ public void Adding_conflicting_check_constraint_to_derived_type_before_base_thro var modelBuilder = CreateModelBuilder(); modelBuilder.Entity() .HasBaseType(null) - .HasCheckConstraint("LargeId", "Id > 1000"); + .ToTable(tb => tb.HasCheckConstraint("LargeId", "Id > 1000")); modelBuilder.Entity() - .HasCheckConstraint("LargeId", "Id > 100", c => c.HasName("CK_LargeId")); + .ToTable(tb => tb.HasCheckConstraint("LargeId", "Id > 100").HasName("CK_LargeId")); Assert.Equal( RelationalStrings.DuplicateCheckConstraint("LargeId", nameof(Child), nameof(ChildBase)), @@ -735,8 +738,7 @@ public virtual void Owned_types_can_be_mapped_to_different_tables() tb.Ignore(l => l.Book); tb.WithOwner() .HasConstraintName("AlternateLabelFK"); - tb.ToTable("TT", "TS"); - tb.IsMemoryOptimized(); + tb.ToTable("TT", "TS", tb => tb.IsMemoryOptimized()); tb.OwnsOne( l => l.AnotherBookLabel, ab => { @@ -899,7 +901,7 @@ public virtual void Owned_type_collections_can_be_mapped_to_different_tables() r => { r.HasKey(o => o.OrderId); - r.IsMemoryOptimized(); + r.ToTable(tb => tb.IsMemoryOptimized()); r.Ignore(o => o.OrderCombination); r.Ignore(o => o.Details); }); @@ -1027,8 +1029,8 @@ public override void Can_configure_owned_type() modelBuilder.Ignore(); var ownedBuilder = modelBuilder.Entity().OwnsOne(c => c.Details) - .ToTable("OtherCustomerDetails") - .HasCheckConstraint("CK_CustomerDetails_T", "AlternateKey <> 0", c => c.HasName("CK_Guid")); + .ToTable("OtherCustomerDetails", tb => + tb.HasCheckConstraint("CK_CustomerDetails_T", "AlternateKey <> 0").HasName("CK_Guid")); ownedBuilder.Property(d => d.CustomerId); ownedBuilder.HasIndex(d => d.CustomerId); ownedBuilder.WithOwner(d => (OtherCustomer?)d.Customer) @@ -1037,8 +1039,8 @@ public override void Can_configure_owned_type() modelBuilder.Entity().OwnsOne( c => c.Details, b => { - b.ToTable("SpecialCustomerDetails"); - b.HasCheckConstraint("CK_CustomerDetails_T", "AlternateKey <> 0", c => c.HasName("CK_Guid")); + b.ToTable("SpecialCustomerDetails", tb => + tb.HasCheckConstraint("CK_CustomerDetails_T", "AlternateKey <> 0").HasName("CK_Guid")); b.Property(d => d.CustomerId); b.HasIndex(d => d.CustomerId); b.WithOwner(d => (SpecialCustomer?)d.Customer) @@ -1921,6 +1923,73 @@ public override TestTemporalPeriodPropertyBuilder HasPeriodStart(string property public override TestTemporalPeriodPropertyBuilder HasPeriodEnd(string propertyName) => new(TemporalTableBuilder.HasPeriodEnd(propertyName)); } + + public abstract class TestOwnedNavigationTemporalTableBuilder + where TOwnerEntity : class + where TDependentEntity : class + { + public abstract TestOwnedNavigationTemporalTableBuilder UseHistoryTable(string name, string? schema); + public abstract TestOwnedNavigationTemporalPeriodPropertyBuilder HasPeriodStart(string propertyName); + public abstract TestOwnedNavigationTemporalPeriodPropertyBuilder HasPeriodEnd(string propertyName); + } + + public class GenericTestOwnedNavigationTemporalTableBuilder : + TestOwnedNavigationTemporalTableBuilder, + IInfrastructure> + where TOwnerEntity : class + where TDependentEntity : class + { + public GenericTestOwnedNavigationTemporalTableBuilder(OwnedNavigationTemporalTableBuilder temporalTableBuilder) + { + TemporalTableBuilder = temporalTableBuilder; + } + + private OwnedNavigationTemporalTableBuilder TemporalTableBuilder { get; } + + OwnedNavigationTemporalTableBuilder IInfrastructure>.Instance + => TemporalTableBuilder; + + protected virtual TestOwnedNavigationTemporalTableBuilder Wrap(OwnedNavigationTemporalTableBuilder tableBuilder) + => new GenericTestOwnedNavigationTemporalTableBuilder(tableBuilder); + + public override TestOwnedNavigationTemporalTableBuilder UseHistoryTable(string name, string? schema) + => Wrap(TemporalTableBuilder.UseHistoryTable(name, schema)); + + public override TestOwnedNavigationTemporalPeriodPropertyBuilder HasPeriodStart(string propertyName) + => new(TemporalTableBuilder.HasPeriodStart(propertyName)); + + public override TestOwnedNavigationTemporalPeriodPropertyBuilder HasPeriodEnd(string propertyName) + => new(TemporalTableBuilder.HasPeriodEnd(propertyName)); + } + + public class NonGenericTestOwnedNavigationTemporalTableBuilder : + TestOwnedNavigationTemporalTableBuilder, + IInfrastructure + where TOwnerEntity : class + where TDependentEntity : class + { + public NonGenericTestOwnedNavigationTemporalTableBuilder(OwnedNavigationTemporalTableBuilder temporalTableBuilder) + { + TemporalTableBuilder = temporalTableBuilder; + } + + private OwnedNavigationTemporalTableBuilder TemporalTableBuilder { get; } + + OwnedNavigationTemporalTableBuilder IInfrastructure.Instance + => TemporalTableBuilder; + + protected virtual TestOwnedNavigationTemporalTableBuilder Wrap(OwnedNavigationTemporalTableBuilder temporalTableBuilder) + => new NonGenericTestOwnedNavigationTemporalTableBuilder(temporalTableBuilder); + + public override TestOwnedNavigationTemporalTableBuilder UseHistoryTable(string name, string? schema) + => Wrap(TemporalTableBuilder.UseHistoryTable(name, schema)); + + public override TestOwnedNavigationTemporalPeriodPropertyBuilder HasPeriodStart(string propertyName) + => new(TemporalTableBuilder.HasPeriodStart(propertyName)); + + public override TestOwnedNavigationTemporalPeriodPropertyBuilder HasPeriodEnd(string propertyName) + => new(TemporalTableBuilder.HasPeriodEnd(propertyName)); + } public class TestTemporalPeriodPropertyBuilder { @@ -1934,4 +2003,17 @@ public TestTemporalPeriodPropertyBuilder(TemporalPeriodPropertyBuilder temporalP public TestTemporalPeriodPropertyBuilder HasColumnName(string name) => new(TemporalPeriodPropertyBuilder.HasColumnName(name)); } + + public class TestOwnedNavigationTemporalPeriodPropertyBuilder + { + public TestOwnedNavigationTemporalPeriodPropertyBuilder(OwnedNavigationTemporalPeriodPropertyBuilder temporalPeriodPropertyBuilder) + { + TemporalPeriodPropertyBuilder = temporalPeriodPropertyBuilder; + } + + protected OwnedNavigationTemporalPeriodPropertyBuilder TemporalPeriodPropertyBuilder { get; } + + public TestOwnedNavigationTemporalPeriodPropertyBuilder HasColumnName(string name) + => new(TemporalPeriodPropertyBuilder.HasColumnName(name)); + } } diff --git a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerTestModelBuilderExtensions.cs b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerTestModelBuilderExtensions.cs index 68826a9ad65..127e52dd2fe 100644 --- a/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerTestModelBuilderExtensions.cs +++ b/test/EFCore.SqlServer.Tests/ModelBuilding/SqlServerTestModelBuilderExtensions.cs @@ -24,19 +24,37 @@ public static ModelBuilderTest.TestIndexBuilder IsClustered( return builder; } - public static ModelBuilderTest.TestOwnedNavigationBuilder IsMemoryOptimized( - this ModelBuilderTest.TestOwnedNavigationBuilder builder, + public static RelationalModelBuilderTest.TestTableBuilder IsMemoryOptimized( + this RelationalModelBuilderTest.TestTableBuilder builder, bool memoryOptimized = true) where TEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.IsMemoryOptimized(memoryOptimized); + break; + case IInfrastructure nongenericBuilder: + nongenericBuilder.Instance.IsMemoryOptimized(memoryOptimized); + break; + } + + return builder; + } + + public static RelationalModelBuilderTest.TestOwnedNavigationTableBuilder IsMemoryOptimized< + TOwnerEntity, TDependentEntity>( + this RelationalModelBuilderTest.TestOwnedNavigationTableBuilder builder, + bool memoryOptimized = true) + where TOwnerEntity : class where TDependentEntity : class { switch (builder) { - case IInfrastructure> genericBuilder: + case IInfrastructure> genericBuilder: genericBuilder.Instance.IsMemoryOptimized(memoryOptimized); break; - case IInfrastructure nongenericBuilder: + case IInfrastructure nongenericBuilder: nongenericBuilder.Instance.IsMemoryOptimized(memoryOptimized); break; } @@ -81,4 +99,46 @@ public static RelationalModelBuilderTest.TestTableBuilder IsTemporal IsTemporal + ( + this RelationalModelBuilderTest.TestOwnedNavigationTableBuilder builder, + bool temporal = true) + where TOwnerEntity : class + where TDependentEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.IsTemporal(temporal); + break; + case IInfrastructure nongenericBuilder: + nongenericBuilder.Instance.IsTemporal(temporal); + break; + } + + return builder; + } + + public static RelationalModelBuilderTest.TestOwnedNavigationTableBuilder IsTemporal< + TOwnerEntity, TDependentEntity>( + this RelationalModelBuilderTest.TestOwnedNavigationTableBuilder builder, + Action> buildAction) + where TOwnerEntity : class + where TDependentEntity : class + { + switch (builder) + { + case IInfrastructure> genericBuilder: + genericBuilder.Instance.IsTemporal( + b => buildAction(new SqlServerModelBuilderTestBase.GenericTestOwnedNavigationTemporalTableBuilder(b))); + break; + case IInfrastructure nongenericBuilder: + nongenericBuilder.Instance.IsTemporal( + b => buildAction(new SqlServerModelBuilderTestBase.NonGenericTestOwnedNavigationTemporalTableBuilder(b))); + break; + } + + return builder; + } }