diff --git a/src/EFCore.Design/Design/Internal/CSharpHelper.cs b/src/EFCore.Design/Design/Internal/CSharpHelper.cs index 2dcda7cbc64..77ab46df070 100644 --- a/src/EFCore.Design/Design/Internal/CSharpHelper.cs +++ b/src/EFCore.Design/Design/Internal/CSharpHelper.cs @@ -1,11 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections; +using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Linq.Expressions; using System.Numerics; using System.Text; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Storage; @@ -773,7 +777,7 @@ internal static IReadOnlyCollection GetFlags(Enum flags) /// public virtual string UnknownLiteral(object? value) { - if (value == null) + if (value is null) { return "null"; } @@ -967,15 +971,54 @@ private bool HandleList(IEnumerable argumentExpressions, StringBuild /// 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 string Fragment(MethodCallCodeFragment fragment) - => Fragment(fragment, indent: 0); + public virtual string Fragment(MethodCallCodeFragment fragment, string? instanceIdentifier = null, bool typeQualified = false) + => Fragment(fragment, typeQualified, instanceIdentifier, indent: 0); - private string Fragment(MethodCallCodeFragment fragment, int indent) + private string Fragment(MethodCallCodeFragment fragment, bool typeQualified, string? instanceIdentifier, int indent) { var builder = new IndentedStringBuilder(); - var current = fragment; - while (current != null) + + if (typeQualified) + { + if (instanceIdentifier is null || fragment.MethodInfo is null || fragment.ChainedCall is not null) + { + throw new ArgumentException(DesignStrings.CannotGenerateTypeQualifiedMethodCall); + } + + builder + .Append(fragment.DeclaringType!) + .Append('.') + .Append(fragment.Method) + .Append('(') + .Append(instanceIdentifier); + + for (var i = 0; i < fragment.Arguments.Count; i++) + { + builder.Append(", "); + Argument(fragment.Arguments[i]); + } + + builder.Append(')'); + + return builder.ToString(); + } + + // Non-type-qualified fragment + + if (instanceIdentifier is not null) + { + builder.Append(instanceIdentifier); + + if (current.ChainedCall is not null) + { + builder + .AppendLine() + .IncrementIndent(); + } + } + + while (true) { builder .Append('.') @@ -989,30 +1032,40 @@ private string Fragment(MethodCallCodeFragment fragment, int indent) builder.Append(", "); } - var argument = current.Arguments[i]; - if (argument is NestedClosureCodeFragment nestedFragment) - { - builder.Append(Fragment(nestedFragment, indent)); - } - else - { - builder.Append(UnknownLiteral(argument)); - } + Argument(current.Arguments[i]); } builder.Append(')'); + if (current.ChainedCall is null) + { + break; + } + + builder.AppendLine(); current = current.ChainedCall; } return builder.ToString(); + + void Argument(object? argument) + { + if (argument is NestedClosureCodeFragment nestedFragment) + { + builder.Append(Fragment(nestedFragment, indent)); + } + else + { + builder.Append(UnknownLiteral(argument)); + } + } } private string Fragment(NestedClosureCodeFragment fragment, int indent) { if (fragment.MethodCalls.Count == 1) { - return fragment.Parameter + " => " + fragment.Parameter + Fragment(fragment.MethodCalls[0], indent); + return fragment.Parameter + " => " + Fragment(fragment.MethodCalls[0], typeQualified: false, fragment.Parameter, indent); } var builder = new IndentedStringBuilder(); @@ -1026,7 +1079,7 @@ private string Fragment(NestedClosureCodeFragment fragment, int indent) { foreach (var methodCall in fragment.MethodCalls) { - builder.Append(fragment.Parameter + Fragment(methodCall, indent + 1)); + builder.AppendLines(Fragment(methodCall, typeQualified: false, fragment.Parameter, indent + 1), skipFinalNewline: true); builder.AppendLine(";"); } } diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index 6fa0c7716e1..e71f98a3a10 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Immutable; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -18,6 +21,9 @@ namespace Microsoft.EntityFrameworkCore.Migrations.Design /// public class CSharpSnapshotGenerator : ICSharpSnapshotGenerator { + private static readonly MethodInfo _hasAnnotationMethodInfo + = typeof(ModelBuilder).GetRequiredRuntimeMethod(nameof(ModelBuilder.HasAnnotation), typeof(string), typeof(string)); + /// /// Initializes a new instance of the class. /// @@ -40,12 +46,12 @@ private ICSharpHelper Code /// /// Generates code for creating an . /// - /// The variable name. + /// The variable name. /// The model. /// The builder code is added to. - public virtual void Generate(string builderName, IModel model, IndentedStringBuilder stringBuilder) + public virtual void Generate(string modelBuilderName, IModel model, IndentedStringBuilder stringBuilder) { - Check.NotEmpty(builderName, nameof(builderName)); + Check.NotEmpty(modelBuilderName, nameof(modelBuilderName)); Check.NotNull(model, nameof(model)); Check.NotNull(stringBuilder, nameof(stringBuilder)); @@ -53,65 +59,33 @@ public virtual void Generate(string builderName, IModel model, IndentedStringBui .FilterIgnoredAnnotations(model.GetAnnotations()) .ToDictionary(a => a.Name, a => a); - var productVersion = model.GetProductVersion(); - - if (annotations.Count > 0 || productVersion != null) + if (model.GetProductVersion() is string productVersion) { - stringBuilder.Append(builderName); - - using (stringBuilder.Indent()) - { - // Temporary patch: specifically exclude some annotations which are known to produce identical Fluent API calls across different - // providers, generating them as raw annotations instead. - var ambiguousAnnotations = RemoveAmbiguousFluentApiAnnotations( - annotations, - name => name.EndsWith(":ValueGenerationStrategy", StringComparison.Ordinal) - || name.EndsWith(":IdentityIncrement", StringComparison.Ordinal) - || name.EndsWith(":IdentitySeed", StringComparison.Ordinal) - || name.EndsWith(":HiLoSequenceName", StringComparison.Ordinal) - || name.EndsWith(":HiLoSequenceSchema", StringComparison.Ordinal)); - - foreach (var methodCallCodeFragment in - Dependencies.AnnotationCodeGenerator.GenerateFluentApiCalls(model, annotations)) - { - stringBuilder - .AppendLine() - .Append(Code.Fragment(methodCallCodeFragment)); - } - - IEnumerable remainingAnnotations = annotations.Values; - if (productVersion != null) - { - remainingAnnotations = remainingAnnotations.Append( - new Annotation(CoreAnnotationNames.ProductVersion, productVersion)); - } - - GenerateAnnotations(remainingAnnotations.Concat(ambiguousAnnotations), stringBuilder); - } - - stringBuilder.AppendLine(";"); + annotations[CoreAnnotationNames.ProductVersion] = new Annotation(CoreAnnotationNames.ProductVersion, productVersion); } + GenerateAnnotations(modelBuilderName, model, stringBuilder, annotations, inChainedCall: false, leadingNewline: false); + foreach (var sequence in model.GetSequences()) { - GenerateSequence(builderName, sequence, stringBuilder); + GenerateSequence(modelBuilderName, sequence, stringBuilder); } - GenerateEntityTypes(builderName, model.GetEntityTypesInHierarchicalOrder(), stringBuilder); + GenerateEntityTypes(modelBuilderName, model.GetEntityTypesInHierarchicalOrder(), stringBuilder); } /// /// Generates code for objects. /// - /// The name of the builder variable. + /// The name of the builder variable. /// The entity types. /// The builder code is added to. protected virtual void GenerateEntityTypes( - string builderName, + string modelBuilderName, IReadOnlyList entityTypes, IndentedStringBuilder stringBuilder) { - Check.NotEmpty(builderName, nameof(builderName)); + Check.NotEmpty(modelBuilderName, nameof(modelBuilderName)); Check.NotNull(entityTypes, nameof(entityTypes)); Check.NotNull(stringBuilder, nameof(stringBuilder)); @@ -120,7 +94,7 @@ protected virtual void GenerateEntityTypes( { stringBuilder.AppendLine(); - GenerateEntityType(builderName, entityType, stringBuilder); + GenerateEntityType(modelBuilderName, entityType, stringBuilder); } foreach (var entityType in entityTypes.Where( @@ -130,7 +104,7 @@ protected virtual void GenerateEntityTypes( { stringBuilder.AppendLine(); - GenerateEntityTypeRelationships(builderName, entityType, stringBuilder); + GenerateEntityTypeRelationships(modelBuilderName, entityType, stringBuilder); } foreach (var entityType in entityTypes.Where( @@ -139,22 +113,22 @@ protected virtual void GenerateEntityTypes( { stringBuilder.AppendLine(); - GenerateEntityTypeNavigations(builderName, entityType, stringBuilder); + GenerateEntityTypeNavigations(modelBuilderName, entityType, stringBuilder); } } /// /// Generates code for an . /// - /// The name of the builder variable. + /// The name of the builder variable. /// The entity type. /// The builder code is added to. protected virtual void GenerateEntityType( - string builderName, + string modelBuilderName, IEntityType entityType, IndentedStringBuilder stringBuilder) { - Check.NotEmpty(builderName, nameof(builderName)); + Check.NotEmpty(modelBuilderName, nameof(modelBuilderName)); Check.NotNull(entityType, nameof(entityType)); Check.NotNull(stringBuilder, nameof(stringBuilder)); @@ -169,8 +143,10 @@ protected virtual void GenerateEntityType( entityTypeName = entityType.ClrType.DisplayName(); } + var entityTypeBuilderName = GenerateEntityTypeBuilderName(); + stringBuilder - .Append(builderName) + .Append(modelBuilderName) .Append( ownerNavigation != null ? ownership!.IsUnique ? ".OwnsOne(" : ".OwnsMany(" @@ -184,26 +160,9 @@ protected virtual void GenerateEntityType( .Append(Code.Literal(ownerNavigation)); } - if (builderName.StartsWith("b", StringComparison.Ordinal)) - { - // ReSharper disable once InlineOutVariableDeclaration - var counter = 1; - if (builderName.Length > 1 - && int.TryParse(builderName[1..], out counter)) - { - counter++; - } - - builderName = "b" + (counter == 0 ? "" : counter.ToString()); - } - else - { - builderName = "b"; - } - stringBuilder .Append(", ") - .Append(builderName) + .Append(entityTypeBuilderName) .AppendLine(" =>"); using (stringBuilder.Indent()) @@ -212,51 +171,70 @@ protected virtual void GenerateEntityType( using (stringBuilder.Indent()) { - GenerateBaseType(builderName, entityType.BaseType, stringBuilder); + GenerateBaseType(entityTypeBuilderName, entityType.BaseType, stringBuilder); - GenerateProperties(builderName, entityType.GetDeclaredProperties(), stringBuilder); + GenerateProperties(entityTypeBuilderName, entityType.GetDeclaredProperties(), stringBuilder); GenerateKeys( - builderName, + entityTypeBuilderName, entityType.GetDeclaredKeys(), entityType.BaseType == null ? entityType.FindPrimaryKey() : null, stringBuilder); - GenerateIndexes(builderName, entityType.GetDeclaredIndexes(), stringBuilder); + GenerateIndexes(entityTypeBuilderName, entityType.GetDeclaredIndexes(), stringBuilder); - GenerateEntityTypeAnnotations(builderName, entityType, stringBuilder); + GenerateEntityTypeAnnotations(entityTypeBuilderName, entityType, stringBuilder); - GenerateCheckConstraints(builderName, entityType, stringBuilder); + GenerateCheckConstraints(entityTypeBuilderName, entityType, stringBuilder); if (ownerNavigation != null) { - GenerateRelationships(builderName, entityType, stringBuilder); + GenerateRelationships(entityTypeBuilderName, entityType, stringBuilder); GenerateNavigations( - builderName, entityType.GetDeclaredNavigations() + entityTypeBuilderName, entityType.GetDeclaredNavigations() .Where(n => !n.IsOnDependent && !n.ForeignKey.IsOwnership), stringBuilder); } - GenerateData(builderName, entityType.GetProperties(), entityType.GetSeedData(providerValues: true), stringBuilder); + GenerateData( + entityTypeBuilderName, entityType.GetProperties(), entityType.GetSeedData(providerValues: true), stringBuilder); } stringBuilder .AppendLine("});"); } + + string GenerateEntityTypeBuilderName() + { + if (modelBuilderName.StartsWith("b", StringComparison.Ordinal)) + { + // ReSharper disable once InlineOutVariableDeclaration + var counter = 1; + if (modelBuilderName.Length > 1 + && int.TryParse(modelBuilderName[1..], out counter)) + { + counter++; + } + + return "b" + (counter == 0 ? "" : counter.ToString()); + } + + return "b"; + } } /// /// Generates code for owned entity types. /// - /// The name of the builder variable. + /// The name of the builder variable. /// The foreign keys identifying each entity type. /// The builder code is added to. protected virtual void GenerateOwnedTypes( - string builderName, + string entityTypeBuilderName, IEnumerable ownerships, IndentedStringBuilder stringBuilder) { - Check.NotNull(builderName, nameof(builderName)); + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(ownerships, nameof(ownerships)); Check.NotNull(stringBuilder, nameof(stringBuilder)); @@ -264,45 +242,45 @@ protected virtual void GenerateOwnedTypes( { stringBuilder.AppendLine(); - GenerateOwnedType(builderName, ownership, stringBuilder); + GenerateOwnedType(entityTypeBuilderName, ownership, stringBuilder); } } /// /// Generates code for an owned entity types. /// - /// The name of the builder variable. + /// The name of the builder variable. /// The foreign key identifying the entity type. /// The builder code is added to. protected virtual void GenerateOwnedType( - string builderName, + string entityTypeBuilderName, IForeignKey ownership, IndentedStringBuilder stringBuilder) { - Check.NotNull(builderName, nameof(builderName)); + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(ownership, nameof(ownership)); Check.NotNull(stringBuilder, nameof(stringBuilder)); - GenerateEntityType(builderName, ownership.DeclaringEntityType, stringBuilder); + GenerateEntityType(entityTypeBuilderName, ownership.DeclaringEntityType, stringBuilder); } /// /// Generates code for the relationships of an . /// - /// The name of the builder variable. + /// The name of the builder variable. /// The entity type. /// The builder code is added to. protected virtual void GenerateEntityTypeRelationships( - string builderName, + string modelBuilderName, IEntityType entityType, IndentedStringBuilder stringBuilder) { - Check.NotEmpty(builderName, nameof(builderName)); + Check.NotEmpty(modelBuilderName, nameof(modelBuilderName)); Check.NotNull(entityType, nameof(entityType)); Check.NotNull(stringBuilder, nameof(stringBuilder)); stringBuilder - .Append(builderName) + .Append(modelBuilderName) .Append(".Entity(") .Append(Code.Literal(entityType.Name)) .AppendLine(", b =>"); @@ -323,46 +301,46 @@ protected virtual void GenerateEntityTypeRelationships( /// /// Generates code for the relationships of an . /// - /// The name of the builder variable. + /// The name of the builder variable. /// The entity type. /// The builder code is added to. protected virtual void GenerateRelationships( - string builderName, + string entityTypeBuilderName, IEntityType entityType, IndentedStringBuilder stringBuilder) { - Check.NotEmpty(builderName, nameof(builderName)); + Check.NotEmpty(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(entityType, nameof(entityType)); Check.NotNull(stringBuilder, nameof(stringBuilder)); - GenerateForeignKeys(builderName, entityType.GetDeclaredForeignKeys(), stringBuilder); + GenerateForeignKeys(entityTypeBuilderName, entityType.GetDeclaredForeignKeys(), stringBuilder); - GenerateOwnedTypes(builderName, entityType.GetDeclaredReferencingForeignKeys().Where(fk => fk.IsOwnership), stringBuilder); + GenerateOwnedTypes(entityTypeBuilderName, entityType.GetDeclaredReferencingForeignKeys().Where(fk => fk.IsOwnership), stringBuilder); GenerateNavigations( - builderName, entityType.GetDeclaredNavigations() + entityTypeBuilderName, entityType.GetDeclaredNavigations() .Where(n => n.IsOnDependent || (!n.IsOnDependent && n.ForeignKey.IsOwnership)), stringBuilder); } /// /// Generates code for the base type of an . /// - /// The name of the builder variable. + /// The name of the builder variable. /// The base entity type. /// The builder code is added to. protected virtual void GenerateBaseType( - string builderName, + string entityTypeBuilderName, IEntityType? baseType, IndentedStringBuilder stringBuilder) { - Check.NotNull(builderName, nameof(builderName)); + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(stringBuilder, nameof(stringBuilder)); if (baseType != null) { stringBuilder .AppendLine() - .Append(builderName) + .Append(entityTypeBuilderName) .Append(".HasBaseType(") .Append(Code.Literal(baseType.Name)) .AppendLine(");"); @@ -372,17 +350,17 @@ protected virtual void GenerateBaseType( /// /// Generates code for an . /// - /// The name of the builder variable. + /// The name of the builder variable. /// The sequence. /// The builder code is added to. protected virtual void GenerateSequence( - string builderName, + string modelBuilderName, ISequence sequence, IndentedStringBuilder stringBuilder) { stringBuilder .AppendLine() - .Append(builderName) + .Append(modelBuilderName) .Append(".HasSequence"); if (sequence.Type != Sequence.DefaultClrType) @@ -459,94 +437,94 @@ protected virtual void GenerateSequence( /// /// Generates code for objects. /// - /// The name of the builder variable. + /// The name of the builder variable. /// The properties. /// The builder code is added to. protected virtual void GenerateProperties( - string builderName, + string entityTypeBuilderName, IEnumerable properties, IndentedStringBuilder stringBuilder) { - Check.NotNull(builderName, nameof(builderName)); + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(properties, nameof(properties)); Check.NotNull(stringBuilder, nameof(stringBuilder)); foreach (var property in properties) { - GenerateProperty(builderName, property, stringBuilder); + GenerateProperty(entityTypeBuilderName, property, stringBuilder); } } /// /// Generates code for an . /// - /// The name of the builder variable. + /// The name of the builder variable. /// The property. /// The builder code is added to. protected virtual void GenerateProperty( - string builderName, + string entityTypeBuilderName, IProperty property, IndentedStringBuilder stringBuilder) { - Check.NotNull(builderName, nameof(builderName)); + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(property, nameof(property)); Check.NotNull(stringBuilder, nameof(stringBuilder)); var clrType = FindValueConverter(property)?.ProviderClrType.MakeNullable(property.IsNullable) ?? property.ClrType; + var propertyBuilderName = $"{entityTypeBuilderName}.Property<{Code.Reference(clrType)}>({Code.Literal(property.Name)})"; + stringBuilder .AppendLine() - .Append(builderName) - .Append(".Property<") - .Append(Code.Reference(clrType)) - .Append(">(") - .Append(Code.Literal(property.Name)) - .Append(")"); + .Append(propertyBuilderName); - using (stringBuilder.Indent()) - { - if (property.IsConcurrencyToken) - { - stringBuilder - .AppendLine() - .Append(".IsConcurrencyToken()"); - } + // Note that GenerateAnnotations below does the corresponding decrement + stringBuilder.IncrementIndent(); - if (property.IsNullable != (clrType.IsNullableType() && !property.IsPrimaryKey())) - { - stringBuilder - .AppendLine() - .Append(".IsRequired()"); - } + if (property.IsConcurrencyToken) + { + stringBuilder + .AppendLine() + .Append(".IsConcurrencyToken()"); + } - if (property.ValueGenerated != ValueGenerated.Never) - { - stringBuilder - .AppendLine() - .Append( - property.ValueGenerated == ValueGenerated.OnAdd - ? ".ValueGeneratedOnAdd()" - : property.ValueGenerated == ValueGenerated.OnUpdate - ? ".ValueGeneratedOnUpdate()" - : property.ValueGenerated == ValueGenerated.OnUpdateSometimes - ? ".ValueGeneratedOnUpdateSometimes()" - : ".ValueGeneratedOnAddOrUpdate()"); - } + if (property.IsNullable != (clrType.IsNullableType() && !property.IsPrimaryKey())) + { + stringBuilder + .AppendLine() + .Append(".IsRequired()"); + } - GeneratePropertyAnnotations(property, stringBuilder); + if (property.ValueGenerated != ValueGenerated.Never) + { + stringBuilder + .AppendLine() + .Append( + property.ValueGenerated == ValueGenerated.OnAdd + ? ".ValueGeneratedOnAdd()" + : property.ValueGenerated == ValueGenerated.OnUpdate + ? ".ValueGeneratedOnUpdate()" + : property.ValueGenerated == ValueGenerated.OnUpdateSometimes + ? ".ValueGeneratedOnUpdateSometimes()" + : ".ValueGeneratedOnAddOrUpdate()"); } - stringBuilder.AppendLine(";"); + GeneratePropertyAnnotations(propertyBuilderName, property, stringBuilder); } /// /// Generates code for the annotations on an . /// + /// The name of the builder variable. /// The property. /// The builder code is added to. - protected virtual void GeneratePropertyAnnotations(IProperty property, IndentedStringBuilder stringBuilder) + protected virtual void GeneratePropertyAnnotations( + string propertyBuilderName, + IProperty property, + IndentedStringBuilder stringBuilder) { + Check.NotNull(propertyBuilderName, nameof(propertyBuilderName)); Check.NotNull(property, nameof(property)); Check.NotNull(stringBuilder, nameof(stringBuilder)); @@ -573,25 +551,7 @@ protected virtual void GeneratePropertyAnnotations(IProperty property, IndentedS GenerateFluentApiForDefaultValue(property, stringBuilder); annotations.Remove(RelationalAnnotationNames.DefaultValue); - // Temporary patch: specifically exclude some annotations which are known to produce identical Fluent API calls across different - // providers, generating them as raw annotations instead. - var ambiguousAnnotations = RemoveAmbiguousFluentApiAnnotations( - annotations, - name => name.EndsWith(":ValueGenerationStrategy", StringComparison.Ordinal) - || name.EndsWith(":IdentityIncrement", StringComparison.Ordinal) - || name.EndsWith(":IdentitySeed", StringComparison.Ordinal) - || name.EndsWith(":HiLoSequenceName", StringComparison.Ordinal) - || name.EndsWith(":HiLoSequenceSchema", StringComparison.Ordinal)); - - foreach (var methodCallCodeFragment in - Dependencies.AnnotationCodeGenerator.GenerateFluentApiCalls(property, annotations)) - { - stringBuilder - .AppendLine() - .Append(Code.Fragment(methodCallCodeFragment)); - } - - GenerateAnnotations(annotations.Values.Concat(ambiguousAnnotations), stringBuilder); + GenerateAnnotations(propertyBuilderName, property, stringBuilder, annotations, inChainedCall: true); } private ValueConverter? FindValueConverter(IProperty property) @@ -602,23 +562,23 @@ protected virtual void GeneratePropertyAnnotations(IProperty property, IndentedS /// /// Generates code for objects. /// - /// The name of the builder variable. + /// The name of the builder variable. /// The keys. /// The primary key. /// The builder code is added to. protected virtual void GenerateKeys( - string builderName, + string entityTypeBuilderName, IEnumerable keys, IKey? primaryKey, IndentedStringBuilder stringBuilder) { - Check.NotNull(builderName, nameof(builderName)); + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(keys, nameof(keys)); Check.NotNull(stringBuilder, nameof(stringBuilder)); if (primaryKey != null) { - GenerateKey(builderName, primaryKey, stringBuilder, primary: true); + GenerateKey(entityTypeBuilderName, primaryKey, stringBuilder, primary: true); } if (primaryKey?.DeclaringEntityType.IsOwned() != true) @@ -628,7 +588,7 @@ protected virtual void GenerateKeys( && (!key.GetReferencingForeignKeys().Any() || key.GetAnnotations().Any(a => a.Name != RelationalAnnotationNames.UniqueConstraintMappings)))) { - GenerateKey(builderName, key, stringBuilder); + GenerateKey(entityTypeBuilderName, key, stringBuilder); } } } @@ -636,173 +596,149 @@ protected virtual void GenerateKeys( /// /// Generates code for an . /// - /// The name of the builder variable. + /// The name of the builder variable. /// The key. /// The builder code is added to. /// A value indicating whether the key is primary. protected virtual void GenerateKey( - string builderName, + string entityTypeBuilderName, IKey key, IndentedStringBuilder stringBuilder, bool primary = false) { - Check.NotNull(builderName, nameof(builderName)); + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(key, nameof(key)); Check.NotNull(stringBuilder, nameof(stringBuilder)); stringBuilder .AppendLine() - .Append(builderName) + .Append(entityTypeBuilderName) .Append(primary ? ".HasKey(" : ".HasAlternateKey(") .Append(string.Join(", ", key.Properties.Select(p => Code.Literal(p.Name)))) .Append(")"); - using (stringBuilder.Indent()) - { - GenerateKeyAnnotations(key, stringBuilder); - } + // Note that GenerateAnnotations below does the corresponding decrement + stringBuilder.IncrementIndent(); - stringBuilder.AppendLine(";"); + GenerateKeyAnnotations(entityTypeBuilderName, key, stringBuilder); } /// /// Generates code for the annotations on a key. /// + /// The name of the builder variable. /// The key. /// The builder code is added to. - protected virtual void GenerateKeyAnnotations(IKey key, IndentedStringBuilder stringBuilder) + protected virtual void GenerateKeyAnnotations(string entityTypeBuilderName, IKey key, IndentedStringBuilder stringBuilder) { + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); + Check.NotNull(key, nameof(key)); + Check.NotNull(stringBuilder, nameof(stringBuilder)); + var annotations = Dependencies.AnnotationCodeGenerator .FilterIgnoredAnnotations(key.GetAnnotations()) .ToDictionary(a => a.Name, a => a); - foreach (var methodCallCodeFragment in - Dependencies.AnnotationCodeGenerator.GenerateFluentApiCalls(key, annotations)) - { - stringBuilder - .AppendLine() - .Append(Code.Fragment(methodCallCodeFragment)); - } - - GenerateAnnotations(annotations.Values, stringBuilder); + GenerateAnnotations(entityTypeBuilderName, key, stringBuilder, annotations, inChainedCall: true); } /// /// Generates code for objects. /// - /// The name of the builder variable. + /// The name of the builder variable. /// The indexes. /// The builder code is added to. protected virtual void GenerateIndexes( - string builderName, + string entityTypeBuilderName, IEnumerable indexes, IndentedStringBuilder stringBuilder) { - Check.NotNull(builderName, nameof(builderName)); + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(indexes, nameof(indexes)); Check.NotNull(stringBuilder, nameof(stringBuilder)); foreach (var index in indexes) { - GenerateIndex(builderName, index, stringBuilder); + GenerateIndex(entityTypeBuilderName, index, stringBuilder); } } /// /// Generates code an . /// - /// The name of the builder variable. + /// The name of the builder variable. /// The index. /// The builder code is added to. protected virtual void GenerateIndex( - string builderName, + string entityTypeBuilderName, IIndex index, IndentedStringBuilder stringBuilder) { - Check.NotNull(builderName, nameof(builderName)); + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(index, nameof(index)); Check.NotNull(stringBuilder, nameof(stringBuilder)); // Note - method names below are meant to be hard-coded // because old snapshot files will fail if they are changed + + var indexProperties = string.Join(", ", index.Properties.Select(p => Code.Literal(p.Name))); + var indexBuilderName = $"{entityTypeBuilderName}.HasIndex(" + + (index.Name is null + ? indexProperties + : $"new[] {{ {indexProperties} }}, {Code.Literal(index.Name)}") + + ")"; + stringBuilder .AppendLine() - .Append(builderName) - .Append(".HasIndex("); - - if (index.Name == null) - { - stringBuilder - .Append(string.Join(", ", index.Properties.Select(p => Code.Literal(p.Name)))); - } - else - { - stringBuilder - .Append("new[] { ") - .Append(string.Join(", ", index.Properties.Select(p => Code.Literal(p.Name)))) - .Append(" }, ") - .Append(Code.Literal(index.Name)); - } + .Append(indexBuilderName); - stringBuilder.Append(")"); + // Note that GenerateAnnotations below does the corresponding decrement + stringBuilder.IncrementIndent(); - using (stringBuilder.Indent()) + if (index.IsUnique) { - if (index.IsUnique) - { - stringBuilder - .AppendLine() - .Append(".IsUnique()"); - } - - GenerateIndexAnnotations(index, stringBuilder); + stringBuilder + .AppendLine() + .Append(".IsUnique()"); } - stringBuilder.AppendLine(";"); + GenerateIndexAnnotations(indexBuilderName, index, stringBuilder); } /// /// Generates code for the annotations on an index. /// + /// The name of the builder variable. /// The index. /// The builder code is added to. protected virtual void GenerateIndexAnnotations( + string indexBuilderName, IIndex index, IndentedStringBuilder stringBuilder) { + Check.NotNull(indexBuilderName, nameof(indexBuilderName)); + Check.NotNull(index, nameof(index)); + Check.NotNull(stringBuilder, nameof(stringBuilder)); + var annotations = Dependencies.AnnotationCodeGenerator .FilterIgnoredAnnotations(index.GetAnnotations()) .ToDictionary(a => a.Name, a => a); - // Temporary patch: specifically exclude some annotations which are known to produce identical Fluent API calls across different - // providers, generating them as raw annotations instead. - var ambiguousAnnotations = RemoveAmbiguousFluentApiAnnotations( - annotations, - name => name.EndsWith(":Include", StringComparison.Ordinal)); - - foreach (var methodCallCodeFragment in - Dependencies.AnnotationCodeGenerator.GenerateFluentApiCalls(index, annotations)) - { - stringBuilder - .AppendLine() - .Append(Code.Fragment(methodCallCodeFragment)); - } - - GenerateAnnotations(annotations.Values.Concat(ambiguousAnnotations), stringBuilder); + GenerateAnnotations(indexBuilderName, index, stringBuilder, annotations, inChainedCall: true); } /// /// Generates code for the annotations on an entity type. /// - /// The name of the builder variable. + /// The name of the builder variable. /// The entity type. /// The builder code is added to. protected virtual void GenerateEntityTypeAnnotations( - string builderName, + string entityTypeBuilderName, IEntityType entityType, IndentedStringBuilder stringBuilder) { - Check.NotNull(builderName, nameof(builderName)); + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(entityType, nameof(entityType)); Check.NotNull(stringBuilder, nameof(stringBuilder)); @@ -828,7 +764,7 @@ protected virtual void GenerateEntityTypeAnnotations( var schemaAnnotation = annotations.Find(RelationalAnnotationNames.Schema); stringBuilder .AppendLine() - .Append(builderName) + .Append(entityTypeBuilderName) .Append(".ToTable("); if (tableName == null @@ -886,7 +822,7 @@ protected virtual void GenerateEntityTypeAnnotations( { stringBuilder .AppendLine() - .Append(builderName) + .Append(entityTypeBuilderName) .Append(".ToView(") .Append(Code.UnknownLiteral(viewName)); if (viewNameAnnotation != null) @@ -919,7 +855,7 @@ protected virtual void GenerateEntityTypeAnnotations( { stringBuilder .AppendLine() - .Append(builderName) + .Append(entityTypeBuilderName) .Append(".ToFunction(") .Append(Code.UnknownLiteral(functionName)) .AppendLine(");"); @@ -940,7 +876,7 @@ protected virtual void GenerateEntityTypeAnnotations( { stringBuilder .AppendLine() - .Append(builderName) + .Append(entityTypeBuilderName) .Append(".ToSqlQuery(") .Append(Code.UnknownLiteral(sqlQuery)) .AppendLine(");"); @@ -958,7 +894,7 @@ protected virtual void GenerateEntityTypeAnnotations( { stringBuilder .AppendLine() - .Append(builderName) + .Append(entityTypeBuilderName) .Append(".") .Append("HasDiscriminator"); @@ -1017,42 +953,21 @@ protected virtual void GenerateEntityTypeAnnotations( stringBuilder.AppendLine(";"); } - var fluentApiCalls = Dependencies.AnnotationCodeGenerator.GenerateFluentApiCalls(entityType, annotations); - if (fluentApiCalls.Count > 0 || annotations.Count > 0) - { - stringBuilder - .AppendLine() - .Append(builderName); - - using (stringBuilder.Indent()) - { - foreach (var methodCallCodeFragment in fluentApiCalls) - { - stringBuilder - .AppendLine() - .AppendLines(Code.Fragment(methodCallCodeFragment), skipFinalNewline: true); - } - - GenerateAnnotations(annotations.Values, stringBuilder); - - stringBuilder - .AppendLine(";"); - } - } + GenerateAnnotations(entityTypeBuilderName, entityType, stringBuilder, annotations, inChainedCall: false); } /// /// Generates code for objects. /// - /// The name of the builder variable. + /// The name of the builder variable. /// The entity type. /// The builder code is added to. protected virtual void GenerateCheckConstraints( - string builderName, + string entityTypeBuilderName, IEntityType entityType, IndentedStringBuilder stringBuilder) { - Check.NotNull(builderName, nameof(builderName)); + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(entityType, nameof(entityType)); Check.NotNull(stringBuilder, nameof(stringBuilder)); @@ -1062,27 +977,27 @@ protected virtual void GenerateCheckConstraints( { stringBuilder.AppendLine(); - GenerateCheckConstraint(builderName, checkConstraint, stringBuilder); + GenerateCheckConstraint(entityTypeBuilderName, checkConstraint, stringBuilder); } } /// /// Generates code for an . /// - /// The name of the builder variable. + /// The name of the builder variable. /// The check constraint. /// The builder code is added to. protected virtual void GenerateCheckConstraint( - string builderName, + string entityTypeBuilderName, ICheckConstraint checkConstraint, IndentedStringBuilder stringBuilder) { - Check.NotNull(builderName, nameof(builderName)); + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(checkConstraint, nameof(checkConstraint)); Check.NotNull(stringBuilder, nameof(stringBuilder)); stringBuilder - .Append(builderName) + .Append(entityTypeBuilderName) .Append(".HasCheckConstraint(") .Append(Code.Literal(checkConstraint.ModelName)) .Append(", ") @@ -1102,15 +1017,15 @@ protected virtual void GenerateCheckConstraint( /// /// Generates code for objects. /// - /// The name of the builder variable. + /// The name of the builder variable. /// The foreign keys. /// The builder code is added to. protected virtual void GenerateForeignKeys( - string builderName, + string entityTypeBuilderName, IEnumerable foreignKeys, IndentedStringBuilder stringBuilder) { - Check.NotNull(builderName, nameof(builderName)); + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(foreignKeys, nameof(foreignKeys)); Check.NotNull(stringBuilder, nameof(stringBuilder)); @@ -1118,29 +1033,29 @@ protected virtual void GenerateForeignKeys( { stringBuilder.AppendLine(); - GenerateForeignKey(builderName, foreignKey, stringBuilder); + GenerateForeignKey(entityTypeBuilderName, foreignKey, stringBuilder); } } /// /// Generates code for an . /// - /// The name of the builder variable. + /// The name of the builder variable. /// The foreign key. /// The builder code is added to. protected virtual void GenerateForeignKey( - string builderName, + string entityTypeBuilderName, IForeignKey foreignKey, IndentedStringBuilder stringBuilder) { - Check.NotNull(builderName, nameof(builderName)); + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(foreignKey, nameof(foreignKey)); Check.NotNull(stringBuilder, nameof(stringBuilder)); if (!foreignKey.IsOwnership) { stringBuilder - .Append(builderName) + .Append(entityTypeBuilderName) .Append(".HasOne(") .Append(Code.Literal(foreignKey.PrincipalEntityType.Name)) .Append(", ") @@ -1152,7 +1067,7 @@ protected virtual void GenerateForeignKey( else { stringBuilder - .Append(builderName) + .Append(entityTypeBuilderName) .Append(".WithOwner("); if (foreignKey.DependentToPrincipal != null) @@ -1166,107 +1081,106 @@ protected virtual void GenerateForeignKey( .Append(")") .AppendLine(); - using (stringBuilder.Indent()) + // Note that GenerateAnnotations below does the corresponding decrement + stringBuilder.IncrementIndent(); + + if (foreignKey.IsUnique + && !foreignKey.IsOwnership) { - if (foreignKey.IsUnique - && !foreignKey.IsOwnership) + stringBuilder + .Append(".WithOne("); + + if (foreignKey.PrincipalToDependent != null) { stringBuilder - .Append(".WithOne("); + .Append(Code.Literal(foreignKey.PrincipalToDependent.Name)); + } - if (foreignKey.PrincipalToDependent != null) - { - stringBuilder - .Append(Code.Literal(foreignKey.PrincipalToDependent.Name)); - } + stringBuilder + .AppendLine(")") + .Append(".HasForeignKey(") + .Append(Code.Literal(foreignKey.DeclaringEntityType.Name)) + .Append(", ") + .Append(string.Join(", ", foreignKey.Properties.Select(p => Code.Literal(p.Name)))) + .Append(")"); + if (foreignKey.PrincipalKey != foreignKey.PrincipalEntityType.FindPrimaryKey()) + { stringBuilder - .AppendLine(")") - .Append(".HasForeignKey(") - .Append(Code.Literal(foreignKey.DeclaringEntityType.Name)) + .AppendLine() + .Append(".HasPrincipalKey(") + .Append(Code.Literal(foreignKey.PrincipalEntityType.Name)) .Append(", ") - .Append(string.Join(", ", foreignKey.Properties.Select(p => Code.Literal(p.Name)))) + .Append(string.Join(", ", foreignKey.PrincipalKey.Properties.Select(p => Code.Literal(p.Name)))) .Append(")"); - - GenerateForeignKeyAnnotations(foreignKey, stringBuilder); - - if (foreignKey.PrincipalKey != foreignKey.PrincipalEntityType.FindPrimaryKey()) - { - stringBuilder - .AppendLine() - .Append(".HasPrincipalKey(") - .Append(Code.Literal(foreignKey.PrincipalEntityType.Name)) - .Append(", ") - .Append(string.Join(", ", foreignKey.PrincipalKey.Properties.Select(p => Code.Literal(p.Name)))) - .Append(")"); - } } - else + } + else + { + if (!foreignKey.IsOwnership) { - if (!foreignKey.IsOwnership) - { - stringBuilder - .Append(".WithMany("); - - if (foreignKey.PrincipalToDependent != null) - { - stringBuilder - .Append(Code.Literal(foreignKey.PrincipalToDependent.Name)); - } + stringBuilder + .Append(".WithMany("); + if (foreignKey.PrincipalToDependent != null) + { stringBuilder - .AppendLine(")"); + .Append(Code.Literal(foreignKey.PrincipalToDependent.Name)); } stringBuilder - .Append(".HasForeignKey(") - .Append(string.Join(", ", foreignKey.Properties.Select(p => Code.Literal(p.Name)))) - .Append(")"); + .AppendLine(")"); + } - GenerateForeignKeyAnnotations(foreignKey, stringBuilder); + stringBuilder + .Append(".HasForeignKey(") + .Append(string.Join(", ", foreignKey.Properties.Select(p => Code.Literal(p.Name)))) + .Append(")"); - if (foreignKey.PrincipalKey != foreignKey.PrincipalEntityType.FindPrimaryKey()) - { - stringBuilder - .AppendLine() - .Append(".HasPrincipalKey(") - .Append(string.Join(", ", foreignKey.PrincipalKey.Properties.Select(p => Code.Literal(p.Name)))) - .Append(")"); - } + if (foreignKey.PrincipalKey != foreignKey.PrincipalEntityType.FindPrimaryKey()) + { + stringBuilder + .AppendLine() + .Append(".HasPrincipalKey(") + .Append(string.Join(", ", foreignKey.PrincipalKey.Properties.Select(p => Code.Literal(p.Name)))) + .Append(")"); } + } - if (!foreignKey.IsOwnership) + if (!foreignKey.IsOwnership) + { + if (foreignKey.DeleteBehavior != DeleteBehavior.ClientSetNull) { - if (foreignKey.DeleteBehavior != DeleteBehavior.ClientSetNull) - { - stringBuilder - .AppendLine() - .Append(".OnDelete(") - .Append(Code.Literal(foreignKey.DeleteBehavior)) - .Append(")"); - } + stringBuilder + .AppendLine() + .Append(".OnDelete(") + .Append(Code.Literal(foreignKey.DeleteBehavior)) + .Append(")"); + } - if (foreignKey.IsRequired) - { - stringBuilder - .AppendLine() - .Append(".IsRequired()"); - } + if (foreignKey.IsRequired) + { + stringBuilder + .AppendLine() + .Append(".IsRequired()"); } } - stringBuilder.AppendLine(";"); + GenerateForeignKeyAnnotations(entityTypeBuilderName, foreignKey, stringBuilder); } /// /// Generates code for the annotations on a foreign key. /// + /// The name of the builder variable. /// The foreign key. /// The builder code is added to. protected virtual void GenerateForeignKeyAnnotations( + string entityTypeBuilderName, IForeignKey foreignKey, IndentedStringBuilder stringBuilder) { + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(foreignKey, nameof(foreignKey)); Check.NotNull(stringBuilder, nameof(stringBuilder)); @@ -1274,34 +1188,26 @@ protected virtual void GenerateForeignKeyAnnotations( .FilterIgnoredAnnotations(foreignKey.GetAnnotations()) .ToDictionary(a => a.Name, a => a); - foreach (var methodCallCodeFragment in - Dependencies.AnnotationCodeGenerator.GenerateFluentApiCalls(foreignKey, annotations)) - { - stringBuilder - .AppendLine() - .Append(Code.Fragment(methodCallCodeFragment)); - } - - GenerateAnnotations(annotations.Values, stringBuilder); + GenerateAnnotations(entityTypeBuilderName, foreignKey, stringBuilder, annotations, inChainedCall: true); } /// /// Generates code for the navigations of an . /// - /// The name of the builder variable. + /// The name of the builder variable. /// The entity type. /// The builder code is added to. protected virtual void GenerateEntityTypeNavigations( - string builderName, + string modelBuilderName, IEntityType entityType, IndentedStringBuilder stringBuilder) { - Check.NotEmpty(builderName, nameof(builderName)); + Check.NotEmpty(modelBuilderName, nameof(modelBuilderName)); Check.NotNull(entityType, nameof(entityType)); Check.NotNull(stringBuilder, nameof(stringBuilder)); stringBuilder - .Append(builderName) + .Append(modelBuilderName) .Append(".Entity(") .Append(Code.Literal(entityType.Name)) .AppendLine(", b =>"); @@ -1324,15 +1230,15 @@ protected virtual void GenerateEntityTypeNavigations( /// /// Generates code for objects. /// - /// The name of the builder variable. + /// The name of the builder variable. /// The navigations. /// The builder code is added to. protected virtual void GenerateNavigations( - string builderName, + string entityTypeBuilderName, IEnumerable navigations, IndentedStringBuilder stringBuilder) { - Check.NotNull(builderName, nameof(builderName)); + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(navigations, nameof(navigations)); Check.NotNull(stringBuilder, nameof(stringBuilder)); @@ -1340,57 +1246,58 @@ protected virtual void GenerateNavigations( { stringBuilder.AppendLine(); - GenerateNavigation(builderName, navigation, stringBuilder); + GenerateNavigation(entityTypeBuilderName, navigation, stringBuilder); } } /// /// Generates code for an . /// - /// The name of the builder variable. + /// The name of the builder variable. /// The navigation. /// The builder code is added to. protected virtual void GenerateNavigation( - string builderName, + string entityTypeBuilderName, INavigation navigation, IndentedStringBuilder stringBuilder) { - Check.NotNull(builderName, nameof(builderName)); + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(navigation, nameof(navigation)); Check.NotNull(stringBuilder, nameof(stringBuilder)); stringBuilder - .Append(builderName) + .Append(entityTypeBuilderName) .Append(".Navigation(") .Append(Code.Literal(navigation.Name)) .Append(")"); - using (stringBuilder.Indent()) - { - if (!navigation.IsOnDependent - && !navigation.IsCollection - && navigation.ForeignKey.IsRequiredDependent) - { - stringBuilder - .AppendLine() - .Append(".IsRequired()"); - } + // Note that GenerateAnnotations below does the corresponding decrement + stringBuilder.IncrementIndent(); - GenerateNavigationAnnotations(navigation, stringBuilder); + if (!navigation.IsOnDependent + && !navigation.IsCollection + && navigation.ForeignKey.IsRequiredDependent) + { + stringBuilder + .AppendLine() + .Append(".IsRequired()"); } - stringBuilder.AppendLine(";"); + GenerateNavigationAnnotations(entityTypeBuilderName, navigation, stringBuilder); } /// /// Generates code for the annotations on a navigation. /// + /// The name of the builder variable. /// The navigation. /// The builder code is added to. protected virtual void GenerateNavigationAnnotations( + string entityTypeBuilderName, INavigation navigation, IndentedStringBuilder stringBuilder) { + Check.NotNull(entityTypeBuilderName, nameof(entityTypeBuilderName)); Check.NotNull(navigation, nameof(navigation)); Check.NotNull(stringBuilder, nameof(stringBuilder)); @@ -1398,65 +1305,18 @@ protected virtual void GenerateNavigationAnnotations( .FilterIgnoredAnnotations(navigation.GetAnnotations()) .ToDictionary(a => a.Name, a => a); - foreach (var methodCallCodeFragment in - Dependencies.AnnotationCodeGenerator.GenerateFluentApiCalls(navigation, annotations)) - { - stringBuilder - .AppendLine() - .Append(Code.Fragment(methodCallCodeFragment)); - } - - GenerateAnnotations(annotations.Values, stringBuilder); - } - - /// - /// Generates code for annotations. - /// - /// The annotations. - /// The builder code is added to. - protected virtual void GenerateAnnotations( - IEnumerable annotations, - IndentedStringBuilder stringBuilder) - { - Check.NotNull(annotations, nameof(annotations)); - Check.NotNull(stringBuilder, nameof(stringBuilder)); - - foreach (var annotation in annotations) - { - stringBuilder.AppendLine(); - GenerateAnnotation(annotation, stringBuilder); - } - } - - /// - /// Generates code for an annotation which does not have a fluent API call. - /// - /// The annotation. - /// The builder code is added to. - protected virtual void GenerateAnnotation( - IAnnotation annotation, - IndentedStringBuilder stringBuilder) - { - Check.NotNull(annotation, nameof(annotation)); - Check.NotNull(stringBuilder, nameof(stringBuilder)); - - stringBuilder - .Append(".HasAnnotation(") - .Append(Code.Literal(annotation.Name)) - .Append(", ") - .Append(Code.UnknownLiteral(annotation.Value)) - .Append(")"); + GenerateAnnotations(entityTypeBuilderName, navigation, stringBuilder, annotations, inChainedCall: true); } /// /// Generates code for data seeding. /// - /// The name of the builder variable. + /// The name of the builder variable. /// The properties to generate. /// The data to be seeded. /// The builder code is added to. protected virtual void GenerateData( - string builderName, + string entityTypeBuilderName, IEnumerable properties, IEnumerable> data, IndentedStringBuilder stringBuilder) @@ -1475,7 +1335,7 @@ protected virtual void GenerateData( stringBuilder .AppendLine() - .Append(builderName) + .Append(entityTypeBuilderName) .Append(".") .Append(nameof(EntityTypeBuilder.HasData)) .AppendLine("("); @@ -1621,23 +1481,85 @@ private void GenerateFluentApiForDefaultValue( .Append(")"); } - private static IReadOnlyList RemoveAmbiguousFluentApiAnnotations( + private void GenerateAnnotations( + string builderName, + IAnnotatable annotatable, + IndentedStringBuilder stringBuilder, Dictionary annotations, - Func annotationNameMatcher) + bool inChainedCall, + bool leadingNewline = true) { - List? ambiguousAnnotations = null; + var fluentApiCalls = Dependencies.AnnotationCodeGenerator.GenerateFluentApiCalls(annotatable, annotations); + + MethodCallCodeFragment? chainedCall = null; + var typeQualifiedCalls = new List(); - foreach (var (name, annotation) in annotations) + // Chain together all Fluent API calls which we can, and leave the others to be generated as type-qualified + foreach (var call in fluentApiCalls) { - if (annotationNameMatcher(name)) + if (call.MethodInfo is not null + && call.MethodInfo.IsStatic + && (call.MethodInfo.DeclaringType is null + || call.MethodInfo.DeclaringType.Assembly != typeof(RelationalModelBuilderExtensions).Assembly)) { - annotations.Remove(name); - ambiguousAnnotations ??= new List(); - ambiguousAnnotations.Add(annotation); + typeQualifiedCalls.Add(call); + } + else + { + chainedCall = chainedCall is null ? call : chainedCall.Chain(call); } } - return (IReadOnlyList?)ambiguousAnnotations ?? ImmutableList.Empty; + // Append remaining raw annotations which did not get generated as Fluent API calls + foreach (var annotation in annotations.Values.OrderBy(a => a.Name)) + { + var call = new MethodCallCodeFragment(_hasAnnotationMethodInfo, annotation.Name, annotation.Value); + chainedCall = chainedCall is null ? call : chainedCall.Chain(call); + } + + // First generate single Fluent API call chain + if (chainedCall is not null) + { + if (inChainedCall) + { + stringBuilder + .AppendLine() + .AppendLines(Code.Fragment(chainedCall), skipFinalNewline: true); + } + else + { + if (leadingNewline) + { + stringBuilder.AppendLine(); + } + + stringBuilder.AppendLines(Code.Fragment(chainedCall, builderName), skipFinalNewline: true); + stringBuilder.AppendLine(";"); + } + + leadingNewline = true; + } + + if (inChainedCall) + { + stringBuilder.AppendLine(";"); + stringBuilder.DecrementIndent(); + } + + // Then generate separate fully-qualified calls + if (typeQualifiedCalls.Count > 0) + { + if (leadingNewline) + { + stringBuilder.AppendLine(); + } + + foreach (var call in typeQualifiedCalls) + { + stringBuilder.Append(Code.Fragment(call, builderName, typeQualified: true)); + stringBuilder.AppendLine(";"); + } + } } } } diff --git a/src/EFCore.Design/Properties/DesignStrings.Designer.cs b/src/EFCore.Design/Properties/DesignStrings.Designer.cs index fbbb59da74a..0f2591e3df5 100644 --- a/src/EFCore.Design/Properties/DesignStrings.Designer.cs +++ b/src/EFCore.Design/Properties/DesignStrings.Designer.cs @@ -63,6 +63,12 @@ public static string CannotFindTypeMappingForColumn(object? columnName, object? GetString("CannotFindTypeMappingForColumn", nameof(columnName), nameof(dateType)), columnName, dateType); + /// + /// A type-qualified method call requires an instance identifier, a MethodInfo and no chained calls. + /// + public static string CannotGenerateTypeQualifiedMethodCall + => GetString("CannotGenerateTypeQualifiedMethodCall"); + /// /// The entity type '{entityType}' has a custom constructor binding. This is usually caused by using proxies. Compiled model can't be generated, because dynamic proxy types are not supported. If you are not using proxies configure the custom constructor binding in '{customize}' in a partial '{className}' class instead. /// @@ -771,4 +777,3 @@ private static string GetString(string name, params string[] formatterNames) } } } - diff --git a/src/EFCore.Design/Properties/DesignStrings.resx b/src/EFCore.Design/Properties/DesignStrings.resx index a1598791c4d..df15729acfc 100644 --- a/src/EFCore.Design/Properties/DesignStrings.resx +++ b/src/EFCore.Design/Properties/DesignStrings.resx @@ -135,6 +135,9 @@ Could not find type mapping for column '{columnName}' with data type '{dateType}'. Skipping column. + + A type-qualified method call requires an instance identifier, a MethodInfo and no chained calls. + The entity type '{entityType}' has a custom constructor binding. This is usually caused by using proxies. Compiled model can't be generated, because dynamic proxy types are not supported. If you are not using proxies configure the custom constructor binding in '{customize}' in a partial '{className}' class instead. diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs index 71304918d42..1cc5515f206 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Data; using System.Linq; +using System.Text; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; @@ -28,7 +30,8 @@ public class CSharpDbContextGenerator : ICSharpDbContextGenerator private readonly ICSharpHelper _code; private readonly IProviderConfigurationCodeGenerator _providerConfigurationCodeGenerator; private readonly IAnnotationCodeGenerator _annotationCodeGenerator; - private IndentedStringBuilder _sb = null!; + private readonly IndentedStringBuilder _builder = new(); + private readonly HashSet _namespaces = new(); private bool _entityTypeBuilderInitialized; private bool _useDataAnnotations; private bool _useNullableReferenceTypes; @@ -75,26 +78,22 @@ public virtual string WriteCode( _useDataAnnotations = useDataAnnotations; _useNullableReferenceTypes = useNullableReferenceTypes; - _sb = new IndentedStringBuilder(); + _builder.Clear(); + _namespaces.Clear(); - _sb.AppendLine("using System;"); // Guid default values require new Guid() which requires this using - _sb.AppendLine("using Microsoft.EntityFrameworkCore;"); - _sb.AppendLine("using Microsoft.EntityFrameworkCore.Metadata;"); + _namespaces.Add("System"); // Guid default values require new Guid() which requires this using + _namespaces.Add("Microsoft.EntityFrameworkCore"); + _namespaces.Add("Microsoft.EntityFrameworkCore.Metadata"); - var finalContextNamespace = contextNamespace ?? modelNamespace; - - if (finalContextNamespace != modelNamespace && !string.IsNullOrEmpty(modelNamespace)) - { - _sb.AppendLine(string.Concat("using ", modelNamespace, ";")); - } + // The final namespaces list is calculated after code generation, since namespaces may be added during code generation - _sb.AppendLine(); + var finalContextNamespace = contextNamespace ?? modelNamespace; if (!string.IsNullOrEmpty(finalContextNamespace)) { - _sb.AppendLine($"namespace {finalContextNamespace}"); - _sb.AppendLine("{"); - _sb.IncrementIndent(); + _builder.AppendLine($"namespace {finalContextNamespace}"); + _builder.AppendLine("{"); + _builder.IncrementIndent(); } GenerateClass( @@ -106,11 +105,35 @@ public virtual string WriteCode( if (!string.IsNullOrEmpty(finalContextNamespace)) { - _sb.DecrementIndent(); - _sb.AppendLine("}"); + _builder.DecrementIndent(); + _builder.AppendLine("}"); + } + + var namespaceStringBuilder = new StringBuilder(); + + IEnumerable namespaces = _namespaces.OrderBy( + ns => ns switch + { + "System" => 1, + var s when s.StartsWith("System", StringComparison.Ordinal) => 2, + var s when s.StartsWith("Microsoft", StringComparison.Ordinal) => 3, + _ => 4 + }) + .ThenBy(ns => ns); + + if (finalContextNamespace != modelNamespace && !string.IsNullOrEmpty(modelNamespace)) + { + namespaces = namespaces.Append(modelNamespace); + } + + foreach (var @namespace in namespaces) + { + namespaceStringBuilder.Append("using ").Append(@namespace).AppendLine(";"); } - return _sb.ToString(); + namespaceStringBuilder.AppendLine(); + + return namespaceStringBuilder.ToString() + _builder; } /// @@ -130,10 +153,10 @@ protected virtual void GenerateClass( Check.NotNull(contextName, nameof(contextName)); Check.NotNull(connectionString, nameof(connectionString)); - _sb.AppendLine($"public partial class {contextName} : DbContext"); - _sb.AppendLine("{"); + _builder.AppendLine($"public partial class {contextName} : DbContext"); + _builder.AppendLine("{"); - using (_sb.Indent()) + using (_builder.Indent()) { GenerateConstructors(contextName); GenerateDbSets(model); @@ -146,24 +169,24 @@ protected virtual void GenerateClass( GenerateOnModelCreating(model); } - _sb.AppendLine(); + _builder.AppendLine(); - using (_sb.Indent()) + using (_builder.Indent()) { - _sb.AppendLine("partial void OnModelCreatingPartial(ModelBuilder modelBuilder);"); + _builder.AppendLine("partial void OnModelCreatingPartial(ModelBuilder modelBuilder);"); } - _sb.AppendLine("}"); + _builder.AppendLine("}"); } private void GenerateConstructors(string contextName) { - _sb.AppendLine($"public {contextName}()") + _builder.AppendLine($"public {contextName}()") .AppendLine("{") .AppendLine("}") .AppendLine(); - _sb.AppendLine($"public {contextName}(DbContextOptions<{contextName}> options)") + _builder.AppendLine($"public {contextName}(DbContextOptions<{contextName}> options)") .IncrementIndent() .AppendLine(": base(options)") .DecrementIndent() @@ -176,19 +199,19 @@ private void GenerateDbSets(IModel model) { foreach (var entityType in model.GetEntityTypes()) { - _sb.Append($"public virtual DbSet<{entityType.Name}> {entityType.GetDbSetName()} {{ get; set; }}"); + _builder.Append($"public virtual DbSet<{entityType.Name}> {entityType.GetDbSetName()} {{ get; set; }}"); if (_useNullableReferenceTypes) { - _sb.Append(" = null!;"); + _builder.Append(" = null!;"); } - _sb.AppendLine(); + _builder.AppendLine(); } if (model.GetEntityTypes().Any()) { - _sb.AppendLine(); + _builder.AppendLine(); } } @@ -197,12 +220,12 @@ private void GenerateEntityTypeErrors(IModel model) var errors = model.GetEntityTypeErrors(); foreach (var entityTypeError in errors) { - _sb.AppendLine($"// {entityTypeError.Value} Please see the warning messages."); + _builder.AppendLine($"// {entityTypeError.Value} Please see the warning messages."); } if (errors.Count > 0) { - _sb.AppendLine(); + _builder.AppendLine(); } } @@ -218,19 +241,19 @@ protected virtual void GenerateOnConfiguring( { Check.NotNull(connectionString, nameof(connectionString)); - _sb.AppendLine("protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)"); - _sb.AppendLine("{"); + _builder.AppendLine("protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)"); + _builder.AppendLine("{"); - using (_sb.Indent()) + using (_builder.Indent()) { - _sb.AppendLine("if (!optionsBuilder.IsConfigured)"); - _sb.AppendLine("{"); + _builder.AppendLine("if (!optionsBuilder.IsConfigured)"); + _builder.AppendLine("{"); - using (_sb.Indent()) + using (_builder.Indent()) { if (!suppressConnectionStringWarning) { - _sb.DecrementIndent() + _builder.DecrementIndent() .DecrementIndent() .DecrementIndent() .DecrementIndent() @@ -241,22 +264,20 @@ protected virtual void GenerateOnConfiguring( .IncrementIndent(); } - _sb.Append("optionsBuilder"); - var useProviderCall = _providerConfigurationCodeGenerator.GenerateUseProvider( connectionString); - _sb - .Append(_code.Fragment(useProviderCall)) + _builder + .AppendLines(_code.Fragment(useProviderCall, "optionsBuilder"), skipFinalNewline: true) .AppendLine(";"); } - _sb.AppendLine("}"); + _builder.AppendLine("}"); } - _sb.AppendLine("}"); + _builder.AppendLine("}"); - _sb.AppendLine(); + _builder.AppendLine(); } /// @@ -269,8 +290,8 @@ protected virtual void GenerateOnModelCreating(IModel model) { Check.NotNull(model, nameof(model)); - _sb.AppendLine("protected override void OnModelCreating(ModelBuilder modelBuilder)"); - _sb.Append("{"); + _builder.AppendLine("protected override void OnModelCreating(ModelBuilder modelBuilder)"); + _builder.Append("{"); var annotations = _annotationCodeGenerator .FilterIgnoredAnnotations(model.GetAnnotations()) @@ -285,31 +306,29 @@ protected virtual void GenerateOnModelCreating(IModel model) var lines = new List(); - lines.AddRange( - _annotationCodeGenerator.GenerateFluentApiCalls(model, annotations).Select(m => _code.Fragment(m)) - .Concat(GenerateAnnotations(annotations.Values))); + GenerateAnnotations(model, annotations, lines); if (lines.Count > 0) { - using (_sb.Indent()) + using (_builder.Indent()) { - _sb.AppendLine(); - _sb.Append("modelBuilder" + lines[0]); + _builder.AppendLine(); + _builder.Append("modelBuilder" + lines[0]); - using (_sb.Indent()) + using (_builder.Indent()) { foreach (var line in lines.Skip(1)) { - _sb.AppendLine(); - _sb.Append(line); + _builder.AppendLine(); + _builder.Append(line); } } - _sb.AppendLine(";"); + _builder.AppendLine(";"); } } - using (_sb.Indent()) + using (_builder.Indent()) { foreach (var entityType in model.GetEntityTypes()) { @@ -319,7 +338,7 @@ protected virtual void GenerateOnModelCreating(IModel model) if (_entityTypeBuilderInitialized) { - _sb.AppendLine("});"); + _builder.AppendLine("});"); } } @@ -329,23 +348,23 @@ protected virtual void GenerateOnModelCreating(IModel model) } } - _sb.AppendLine(); + _builder.AppendLine(); - using (_sb.Indent()) + using (_builder.Indent()) { - _sb.AppendLine("OnModelCreatingPartial(modelBuilder);"); + _builder.AppendLine("OnModelCreatingPartial(modelBuilder);"); } - _sb.AppendLine("}"); + _builder.AppendLine("}"); } private void InitializeEntityTypeBuilder(IEntityType entityType) { if (!_entityTypeBuilderInitialized) { - _sb.AppendLine(); - _sb.AppendLine($"modelBuilder.Entity<{entityType.Name}>({EntityLambdaIdentifier} =>"); - _sb.Append("{"); + _builder.AppendLine(); + _builder.AppendLine($"modelBuilder.Entity<{entityType.Name}>({EntityLambdaIdentifier} =>"); + _builder.Append("{"); } _entityTypeBuilderInitialized = true; @@ -379,9 +398,9 @@ private void GenerateEntityType(IEntityType entityType) GenerateTableName(entityType); } - var lines = new List( - _annotationCodeGenerator.GenerateFluentApiCalls(entityType, annotations).Select(m => _code.Fragment(m)) - .Concat(GenerateAnnotations(annotations.Values))); + var lines = new List(); + + GenerateAnnotations(entityType, annotations, lines); AppendMultiLineFluentApi(entityType, lines); @@ -420,22 +439,22 @@ private void AppendMultiLineFluentApi(IEntityType entityType, IList line InitializeEntityTypeBuilder(entityType); - using (_sb.Indent()) + using (_builder.Indent()) { - _sb.AppendLine(); + _builder.AppendLine(); - _sb.Append(EntityLambdaIdentifier + lines[0]); + _builder.Append(EntityLambdaIdentifier + lines[0]); - using (_sb.Indent()) + using (_builder.Indent()) { foreach (var line in lines.Skip(1)) { - _sb.AppendLine(); - _sb.Append(line); + _builder.AppendLine(); + _builder.Append(line); } } - _sb.AppendLine(";"); + _builder.AppendLine(";"); } } @@ -488,9 +507,7 @@ private void GenerateKey(IKey? key, IEntityType entityType) $".{nameof(RelationalKeyBuilderExtensions.HasName)}({_code.Literal(key.GetName()!)})"); } - lines.AddRange( - _annotationCodeGenerator.GenerateFluentApiCalls(key, annotations).Select(m => _code.Fragment(m)) - .Concat(GenerateAnnotations(annotations.Values))); + GenerateAnnotations(key, annotations, lines); AppendMultiLineFluentApi(key.DeclaringEntityType, lines); } @@ -555,9 +572,7 @@ private void GenerateIndex(IIndex index) lines.Add($".{nameof(IndexBuilder.IsUnique)}()"); } - lines.AddRange( - _annotationCodeGenerator.GenerateFluentApiCalls(index, annotations).Select(m => _code.Fragment(m)) - .Concat(GenerateAnnotations(annotations.Values))); + GenerateAnnotations(index, annotations, lines); AppendMultiLineFluentApi(index.DeclaringEntityType, lines); } @@ -669,9 +684,7 @@ private void GenerateProperty(IProperty property) lines.Add($".{nameof(PropertyBuilder.IsConcurrencyToken)}()"); } - lines.AddRange( - _annotationCodeGenerator.GenerateFluentApiCalls(property, annotations).Select(m => _code.Fragment(m)) - .Concat(GenerateAnnotations(annotations.Values))); + GenerateAnnotations(property, annotations, lines); switch (lines.Count) { @@ -734,9 +747,7 @@ private void GenerateRelationship(IForeignKey foreignKey) canUseDataAnnotations = false; } - lines.AddRange( - _annotationCodeGenerator.GenerateFluentApiCalls(foreignKey, annotations).Select(m => _code.Fragment(m)) - .Concat(GenerateAnnotations(annotations.Values))); + GenerateAnnotations(foreignKey, annotations, lines); if (!_useDataAnnotations || !canUseDataAnnotations) @@ -794,23 +805,65 @@ private void GenerateSequence(ISequence sequence) lines = new List { lines[0] + lines[1] }; } - _sb.AppendLine(); - _sb.Append(lines[0]); + _builder.AppendLine(); + _builder.Append(lines[0]); - using (_sb.Indent()) + using (_builder.Indent()) { foreach (var line in lines.Skip(1)) { - _sb.AppendLine(); - _sb.Append(line); + _builder.AppendLine(); + _builder.Append(line); } } - _sb.AppendLine(";"); + _builder.AppendLine(";"); } - private IList GenerateAnnotations(IEnumerable annotations) - => annotations.Select( - a => $".HasAnnotation({_code.Literal(a.Name)}, {_code.UnknownLiteral(a.Value)})").ToList(); + private void GenerateAnnotations(IAnnotatable annotatable, Dictionary annotations, List lines) + { + foreach (var call in _annotationCodeGenerator.GenerateFluentApiCalls(annotatable, annotations)) + { + var fluentApiCall = call; + + // Remove optional arguments + if (fluentApiCall.MethodInfo is { } methodInfo) + { + var methodParameters = methodInfo.GetParameters(); + + var paramOffset = methodInfo.IsStatic ? 1 : 0; + + for (int i = fluentApiCall.Arguments.Count - 1; i >= 0; i--) + { + if (!methodParameters[i + paramOffset].HasDefaultValue) + { + break; + } + + var defaultValue = methodParameters[i + paramOffset].DefaultValue; + var argument = fluentApiCall.Arguments[i]; + + if (argument is null && defaultValue is null || argument is not null && argument.Equals(defaultValue)) + { + fluentApiCall = new MethodCallCodeFragment(methodInfo, fluentApiCall.Arguments.Take(i).ToArray()); + } + else + { + break; + } + } + } + + lines.Add(_code.Fragment(fluentApiCall)); + + if (fluentApiCall.Namespace is not null) + { + _namespaces.Add(fluentApiCall.Namespace); + } + } + + lines.AddRange(annotations.Values.Select( + a => $".HasAnnotation({_code.Literal(a.Name)}, {_code.UnknownLiteral(a.Value)})")); + } } } diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index 100688d09b4..8d518d42aab 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -5,8 +5,10 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Reflection; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; using NotNullWhenAttribute = System.Diagnostics.CodeAnalysis.NotNullWhenAttribute; @@ -35,6 +37,78 @@ public class AnnotationCodeGenerator : IAnnotationCodeGenerator RelationalAnnotationNames.RelationalOverrides }; + #region MethodInfos + + private static readonly MethodInfo _modelHasDefaultSchemaMethodInfo + = typeof(RelationalModelBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalModelBuilderExtensions.HasDefaultSchema), typeof(ModelBuilder), typeof(string)); + + private static readonly MethodInfo _modelUseCollationMethodInfo + = typeof(RelationalModelBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalModelBuilderExtensions.UseCollation), typeof(ModelBuilder), typeof(string)); + + private static readonly MethodInfo _entityTypeHasCommentMethodInfo + = typeof(RelationalEntityTypeBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalEntityTypeBuilderExtensions.HasComment), typeof(EntityTypeBuilder), typeof(string)); + + private static readonly MethodInfo _propertyHasColumnNameMethodInfo + = typeof(RelationalPropertyBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalPropertyBuilderExtensions.HasColumnName), typeof(PropertyBuilder), typeof(string)); + + private static readonly MethodInfo _propertyHasDefaultValueSqlMethodInfo1 + = typeof(RelationalPropertyBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalPropertyBuilderExtensions.HasDefaultValueSql), typeof(PropertyBuilder)); + + private static readonly MethodInfo _propertyHasDefaultValueSqlMethodInfo2 + = typeof(RelationalPropertyBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalPropertyBuilderExtensions.HasDefaultValueSql), typeof(PropertyBuilder), typeof(string)); + + private static readonly MethodInfo _propertyHasComputedColumnSqlMethodInfo1 + = typeof(RelationalPropertyBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalPropertyBuilderExtensions.HasComputedColumnSql), typeof(PropertyBuilder)); + + private static readonly MethodInfo _propertyHasComputedColumnSqlMethodInfo2 + = typeof(RelationalPropertyBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalPropertyBuilderExtensions.HasComputedColumnSql), typeof(PropertyBuilder), typeof(string)); + + private static readonly MethodInfo _hasComputedColumnSqlMethodInfo3 + = typeof(RelationalPropertyBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalPropertyBuilderExtensions.HasComputedColumnSql), typeof(PropertyBuilder), typeof(string), typeof(bool)); + + private static readonly MethodInfo _propertyIsFixedLengthMethodInfo + = typeof(RelationalPropertyBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalPropertyBuilderExtensions.IsFixedLength), typeof(PropertyBuilder), typeof(bool)); + + private static readonly MethodInfo _propertyHasCommentMethodInfo + = typeof(RelationalPropertyBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalPropertyBuilderExtensions.HasComment), typeof(PropertyBuilder), typeof(string)); + + private static readonly MethodInfo _propertyUseCollationMethodInfo + = typeof(RelationalPropertyBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalPropertyBuilderExtensions.UseCollation), typeof(PropertyBuilder), typeof(string)); + + private static readonly MethodInfo _keyHasNameMethodInfo + = typeof(RelationalKeyBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalKeyBuilderExtensions.HasName), typeof(KeyBuilder), typeof(string)); + + private static readonly MethodInfo _referenceReferenceHasConstraintNameMethodInfo + = typeof(RelationalForeignKeyBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalForeignKeyBuilderExtensions.HasConstraintName), typeof(ReferenceReferenceBuilder), typeof(string)); + + private static readonly MethodInfo _referenceCollectionHasConstraintNameMethodInfo + = typeof(RelationalForeignKeyBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalForeignKeyBuilderExtensions.HasConstraintName), typeof(ReferenceCollectionBuilder), typeof(string)); + + private static readonly MethodInfo _indexHasDatabaseNameMethodInfo + = typeof(RelationalIndexBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalIndexBuilderExtensions.HasDatabaseName), typeof(IndexBuilder), typeof(string)); + + private static readonly MethodInfo _indexHasFilterNameMethodInfo + = typeof(RelationalIndexBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalIndexBuilderExtensions.HasFilter), typeof(IndexBuilder), typeof(string)); + + #endregion MethodInfos + /// /// Initializes a new instance of this class. /// @@ -108,12 +182,12 @@ public virtual IReadOnlyList GenerateFluentApiCalls( GenerateSimpleFluentApiCall( annotations, - RelationalAnnotationNames.DefaultSchema, nameof(RelationalModelBuilderExtensions.HasDefaultSchema), + RelationalAnnotationNames.DefaultSchema, _modelHasDefaultSchemaMethodInfo, methodCallCodeFragments); GenerateSimpleFluentApiCall( annotations, - RelationalAnnotationNames.Collation, nameof(RelationalModelBuilderExtensions.UseCollation), + RelationalAnnotationNames.Collation, _modelUseCollationMethodInfo, methodCallCodeFragments); methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(model, annotations, GenerateFluentApi)); @@ -129,7 +203,7 @@ public virtual IReadOnlyList GenerateFluentApiCalls( GenerateSimpleFluentApiCall( annotations, - RelationalAnnotationNames.Comment, nameof(RelationalEntityTypeBuilderExtensions.HasComment), methodCallCodeFragments); + RelationalAnnotationNames.Comment, _entityTypeHasCommentMethodInfo, methodCallCodeFragments); methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(entityType, annotations, GenerateFluentApi)); @@ -145,50 +219,41 @@ public virtual IReadOnlyList GenerateFluentApiCalls( GenerateSimpleFluentApiCall( annotations, - RelationalAnnotationNames.ColumnName, nameof(RelationalPropertyBuilderExtensions.HasColumnName), methodCallCodeFragments); + RelationalAnnotationNames.ColumnName, _propertyHasColumnNameMethodInfo, methodCallCodeFragments); if (TryGetAndRemove(annotations, RelationalAnnotationNames.DefaultValueSql, out string? defaultValueSql)) { methodCallCodeFragments.Add( defaultValueSql.Length == 0 - ? new MethodCallCodeFragment( - nameof(RelationalPropertyBuilderExtensions.HasDefaultValueSql)) - : new MethodCallCodeFragment( - nameof(RelationalPropertyBuilderExtensions.HasDefaultValueSql), - defaultValueSql)); + ? new MethodCallCodeFragment(_propertyHasDefaultValueSqlMethodInfo1) + : new MethodCallCodeFragment(_propertyHasDefaultValueSqlMethodInfo2, defaultValueSql)); } if (TryGetAndRemove(annotations, RelationalAnnotationNames.ComputedColumnSql, out string? computedColumnSql)) { methodCallCodeFragments.Add( computedColumnSql.Length == 0 - ? new MethodCallCodeFragment( - nameof(RelationalPropertyBuilderExtensions.HasComputedColumnSql)) + ? new MethodCallCodeFragment(_propertyHasComputedColumnSqlMethodInfo1) : TryGetAndRemove(annotations, RelationalAnnotationNames.IsStored, out bool isStored) - ? new MethodCallCodeFragment( - nameof(RelationalPropertyBuilderExtensions.HasComputedColumnSql), - computedColumnSql, - isStored) - : new MethodCallCodeFragment( - nameof(RelationalPropertyBuilderExtensions.HasComputedColumnSql), - computedColumnSql)); + ? new MethodCallCodeFragment(_hasComputedColumnSqlMethodInfo3, computedColumnSql, isStored) + : new MethodCallCodeFragment(_propertyHasComputedColumnSqlMethodInfo2, computedColumnSql)); } if (TryGetAndRemove(annotations, RelationalAnnotationNames.IsFixedLength, out bool isFixedLength)) { methodCallCodeFragments.Add( isFixedLength - ? new MethodCallCodeFragment(nameof(RelationalAnnotationNames.IsFixedLength)) - : new MethodCallCodeFragment(nameof(RelationalAnnotationNames.IsFixedLength), isFixedLength)); + ? new MethodCallCodeFragment(_propertyIsFixedLengthMethodInfo) + : new MethodCallCodeFragment(_propertyIsFixedLengthMethodInfo, isFixedLength)); } GenerateSimpleFluentApiCall( annotations, - RelationalAnnotationNames.Comment, nameof(RelationalPropertyBuilderExtensions.HasComment), methodCallCodeFragments); + RelationalAnnotationNames.Comment, _propertyHasCommentMethodInfo, methodCallCodeFragments); GenerateSimpleFluentApiCall( annotations, - RelationalAnnotationNames.Collation, nameof(RelationalPropertyBuilderExtensions.UseCollation), methodCallCodeFragments); + RelationalAnnotationNames.Collation, _propertyUseCollationMethodInfo, methodCallCodeFragments); methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(property, annotations, GenerateFluentApi)); @@ -202,9 +267,7 @@ public virtual IReadOnlyList GenerateFluentApiCalls( { var methodCallCodeFragments = new List(); - GenerateSimpleFluentApiCall( - annotations, - RelationalAnnotationNames.Name, nameof(RelationalKeyBuilderExtensions.HasName), methodCallCodeFragments); + GenerateSimpleFluentApiCall(annotations, RelationalAnnotationNames.Name, _keyHasNameMethodInfo, methodCallCodeFragments); methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(key, annotations, GenerateFluentApi)); @@ -213,16 +276,18 @@ public virtual IReadOnlyList GenerateFluentApiCalls( /// public virtual IReadOnlyList GenerateFluentApiCalls( - IForeignKey navigation, + IForeignKey foreignKey, IDictionary annotations) { var methodCallCodeFragments = new List(); GenerateSimpleFluentApiCall( annotations, - RelationalAnnotationNames.Name, nameof(RelationalForeignKeyBuilderExtensions.HasConstraintName), methodCallCodeFragments); + RelationalAnnotationNames.Name, + foreignKey.IsUnique ? _referenceReferenceHasConstraintNameMethodInfo : _referenceCollectionHasConstraintNameMethodInfo, + methodCallCodeFragments); - methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(navigation, annotations, GenerateFluentApi)); + methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(foreignKey, annotations, GenerateFluentApi)); return methodCallCodeFragments; } @@ -259,12 +324,9 @@ public virtual IReadOnlyList GenerateFluentApiCalls( var methodCallCodeFragments = new List(); GenerateSimpleFluentApiCall( - annotations, - RelationalAnnotationNames.Name, nameof(RelationalIndexBuilderExtensions.HasDatabaseName), methodCallCodeFragments); - + annotations, RelationalAnnotationNames.Name, _indexHasDatabaseNameMethodInfo, methodCallCodeFragments); GenerateSimpleFluentApiCall( - annotations, - RelationalAnnotationNames.Filter, nameof(RelationalIndexBuilderExtensions.HasFilter), methodCallCodeFragments); + annotations, RelationalAnnotationNames.Filter, _indexHasFilterNameMethodInfo, methodCallCodeFragments); methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(index, annotations, GenerateFluentApi)); @@ -659,7 +721,8 @@ private void RemoveConventionalAnnotationsHelper( } } - private static bool TryGetAndRemove(IDictionary annotations, string annotationName, [NotNullWhen(true)] out T? annotationValue) + private static bool TryGetAndRemove( + IDictionary annotations, string annotationName, [NotNullWhen(true)] out T? annotationValue) { if (annotations.TryGetValue(annotationName, out var annotation) && annotation.Value != null) @@ -676,7 +739,7 @@ private static bool TryGetAndRemove(IDictionary annotati private static void GenerateSimpleFluentApiCall( IDictionary annotations, string annotationName, - string methodName, + MethodInfo methodInfo, List methodCallCodeFragments) { if (annotations.TryGetValue(annotationName, out var annotation)) @@ -685,15 +748,13 @@ private static void GenerateSimpleFluentApiCall( if (annotation.Value is object annotationValue) { methodCallCodeFragments.Add( - new MethodCallCodeFragment(methodName, annotationValue)); + new MethodCallCodeFragment(methodInfo, annotationValue)); } } } // Dictionary is safe for removal during enumeration private static IEnumerable> EnumerateForRemoval(IDictionary annotations) - => annotations is Dictionary - ? (IEnumerable>)annotations - : annotations.ToList(); + => annotations is Dictionary ? annotations : annotations.ToList(); } } diff --git a/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs index 6c8bb344fdf..cb336112ba1 100644 --- a/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -60,9 +61,7 @@ void RemoveAnnotationsHandledByConventions(IKey key, IDictionary /// The foreign key to which the annotations are applied. /// The set of annotations from which to remove the conventional ones. - void RemoveAnnotationsHandledByConventions(IForeignKey foreignKey, IDictionary annotations) - { - } + void RemoveAnnotationsHandledByConventions(IForeignKey foreignKey, IDictionary annotations) { } /// /// Removes annotation whose configuration is already applied by convention, and do not need to be @@ -72,6 +71,53 @@ void RemoveAnnotationsHandledByConventions(IForeignKey foreignKey, IDictionary The set of annotations from which to remove the conventional ones. void RemoveAnnotationsHandledByConventions(IIndex index, IDictionary annotations) { } + /// + /// For the given annotations which have corresponding fluent API calls, returns those fluent API calls + /// and removes the annotations. + /// + /// The annotatable to which the annotations are applied. + /// The set of annotations from which to generate fluent API calls. + void RemoveAnnotationsHandledByConventions(IAnnotatable annotatable, IDictionary annotations) + { + switch (annotatable) + { + case IModel model: + RemoveAnnotationsHandledByConventions(model, annotations); + return; + + case IEntityType entityType: + RemoveAnnotationsHandledByConventions(entityType, annotations); + return; + + case IProperty property: + RemoveAnnotationsHandledByConventions(property, annotations); + return; + + case IKey key: + RemoveAnnotationsHandledByConventions(key, annotations); + return; + + case IForeignKey foreignKey: + RemoveAnnotationsHandledByConventions(foreignKey, annotations); + return; + + case INavigation navigation: + RemoveAnnotationsHandledByConventions(navigation, annotations); + return; + + case ISkipNavigation skipNavigation: + RemoveAnnotationsHandledByConventions(skipNavigation, annotations); + return; + + case IIndex index: + RemoveAnnotationsHandledByConventions(index, annotations); + return; + + default: + throw new ArgumentException(RelationalStrings.UnhandledAnnotatableType(annotatable.GetType())); + } + } + /// /// For the given annotations which have corresponding fluent API calls, returns those fluent API calls /// and removes the annotations. @@ -160,6 +206,26 @@ IReadOnlyList GenerateFluentApiCalls( IDictionary annotations) => Array.Empty(); + /// + /// For the given annotations which have corresponding fluent API calls, returns those fluent API calls + /// and removes the annotations. + /// + /// The annotatable to which the annotations are applied. + /// The set of annotations from which to generate fluent API calls. + IReadOnlyList GenerateFluentApiCalls(IAnnotatable annotatable, IDictionary annotations) + => annotatable switch + { + IModel model => GenerateFluentApiCalls(model, annotations), + IEntityType entityType => GenerateFluentApiCalls(entityType, annotations), + IProperty property => GenerateFluentApiCalls(property, annotations), + IKey key => GenerateFluentApiCalls(key, annotations), + IForeignKey foreignKey => GenerateFluentApiCalls(foreignKey, annotations), + INavigation navigation => GenerateFluentApiCalls(navigation, annotations), + ISkipNavigation skipNavigation => GenerateFluentApiCalls(skipNavigation, annotations), + IIndex index => GenerateFluentApiCalls(index, annotations), + _ => throw new ArgumentException(RelationalStrings.UnhandledAnnotatableType(annotatable.GetType())) + }; + /// /// For the given annotations which have corresponding data annotation attributes, returns those attribute code fragments /// and removes the annotations. @@ -181,5 +247,21 @@ IReadOnlyList GenerateDataAnnotationAttributes( IProperty property, IDictionary annotations) => Array.Empty(); + + /// + /// For the given annotations which have corresponding data annotation attributes, returns those attribute code fragments + /// and removes the annotations. + /// + /// The annotatable to which the annotations are applied. + /// The set of annotations from which to generate fluent API calls. + IReadOnlyList GenerateDataAnnotationAttributes( + IAnnotatable annotatable, + IDictionary annotations) + => annotatable switch + { + IEntityType entityType => GenerateDataAnnotationAttributes(entityType, annotations), + IProperty property => GenerateDataAnnotationAttributes(property, annotations), + _ => throw new ArgumentException(RelationalStrings.UnhandledAnnotatableType(annotatable.GetType())) + }; } } diff --git a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs index e3ebc2a1faf..61d1ee3dc1d 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs +++ b/src/EFCore.Relational/Properties/RelationalStrings.Designer.cs @@ -1096,6 +1096,14 @@ public static string UnableToBindMemberToEntityProjection(object? memberType, ob GetString("UnableToBindMemberToEntityProjection", nameof(memberType), nameof(member), nameof(entityType)), memberType, member, entityType); + /// + /// Unhandled annotatable type '{annotatableType}'. + /// + public static string UnhandledAnnotatableType(object? expressionType) + => string.Format( + GetString("UnhandledAnnotatableType", nameof(expressionType)), + expressionType); + /// /// Unhandled expression '{expression}' of type '{expressionType}' encountered in '{visitor}'. /// diff --git a/src/EFCore.Relational/Properties/RelationalStrings.resx b/src/EFCore.Relational/Properties/RelationalStrings.resx index d465a7c314f..763417d25c9 100644 --- a/src/EFCore.Relational/Properties/RelationalStrings.resx +++ b/src/EFCore.Relational/Properties/RelationalStrings.resx @@ -771,6 +771,9 @@ Unable to bind '{memberType}.{member}' to an entity projection of '{entityType}'. + + Unhandled annotatable type '{annotatableType}'. + Unhandled expression '{expression}' of type '{expressionType}' encountered in '{visitor}'. diff --git a/src/EFCore.SqlServer.NTS/Scaffolding/Internal/SqlServerNetTopologySuiteCodeGeneratorPlugin.cs b/src/EFCore.SqlServer.NTS/Scaffolding/Internal/SqlServerNetTopologySuiteCodeGeneratorPlugin.cs index d1554d69a31..3eff03534db 100644 --- a/src/EFCore.SqlServer.NTS/Scaffolding/Internal/SqlServerNetTopologySuiteCodeGeneratorPlugin.cs +++ b/src/EFCore.SqlServer.NTS/Scaffolding/Internal/SqlServerNetTopologySuiteCodeGeneratorPlugin.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Reflection; using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Scaffolding; namespace Microsoft.EntityFrameworkCore.SqlServer.Scaffolding.Internal @@ -14,6 +17,11 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Scaffolding.Internal /// public class SqlServerNetTopologySuiteCodeGeneratorPlugin : ProviderCodeGeneratorPlugin { + private static readonly MethodInfo _useNetTopologySuiteMethodInfo + = typeof(SqlServerNetTopologySuiteDbContextOptionsBuilderExtensions).GetRequiredRuntimeMethod( + nameof(SqlServerNetTopologySuiteDbContextOptionsBuilderExtensions.UseNetTopologySuite), + typeof(Action)); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -21,6 +29,6 @@ public class SqlServerNetTopologySuiteCodeGeneratorPlugin : ProviderCodeGenerato /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override MethodCallCodeFragment GenerateProviderOptions() - => new(nameof(SqlServerNetTopologySuiteDbContextOptionsBuilderExtensions.UseNetTopologySuite)); + => new(_useNetTopologySuiteMethodInfo); } } diff --git a/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs b/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs index 2819ba5f228..6a38b95f1db 100644 --- a/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs +++ b/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -21,6 +22,86 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Design.Internal /// public class SqlServerAnnotationCodeGenerator : AnnotationCodeGenerator { + #region MethodInfos + + private static readonly MethodInfo _modelUseIdentityColumnsMethodInfo + = typeof(SqlServerModelBuilderExtensions).GetRequiredRuntimeMethod( + nameof(SqlServerModelBuilderExtensions.UseIdentityColumns), typeof(ModelBuilder), typeof(long), typeof(int)); + + private static readonly MethodInfo _modelUseHiLoMethodInfo + = typeof(SqlServerModelBuilderExtensions).GetRequiredRuntimeMethod( + nameof(SqlServerModelBuilderExtensions.UseHiLo), typeof(ModelBuilder), typeof(string), typeof(string)); + + private static readonly MethodInfo _modelHasDatabaseMaxSizeMethodInfo + = typeof(SqlServerModelBuilderExtensions).GetRequiredRuntimeMethod( + nameof(SqlServerModelBuilderExtensions.HasDatabaseMaxSize), typeof(ModelBuilder), typeof(string)); + + private static readonly MethodInfo _modelHasServiceTierSqlMethodInfo + = typeof(SqlServerModelBuilderExtensions).GetRequiredRuntimeMethod( + nameof(SqlServerModelBuilderExtensions.HasServiceTierSql), typeof(ModelBuilder), typeof(string)); + + private static readonly MethodInfo _modelHasPerformanceLevelSqlMethodInfo + = typeof(SqlServerModelBuilderExtensions).GetRequiredRuntimeMethod( + nameof(SqlServerModelBuilderExtensions.HasPerformanceLevelSql), typeof(ModelBuilder), typeof(string)); + + private static readonly MethodInfo _modelHasAnnotationMethodInfo + = typeof(ModelBuilder).GetRequiredRuntimeMethod( + nameof(ModelBuilder.HasAnnotation), typeof(string), typeof(object)); + + private static readonly MethodInfo _entityTypeToTableMethodInfo + = typeof(RelationalEntityTypeBuilderExtensions).GetRequiredRuntimeMethod( + nameof(RelationalEntityTypeBuilderExtensions.ToTable), typeof(EntityTypeBuilder), typeof(string)); + + private static readonly MethodInfo _propertyIsSparseMethodInfo + = typeof(SqlServerPropertyBuilderExtensions).GetRequiredRuntimeMethod( + nameof(SqlServerPropertyBuilderExtensions.IsSparse), typeof(PropertyBuilder), typeof(bool)); + + private static readonly MethodInfo _propertyUseIdentityColumnsMethodInfo + = typeof(SqlServerPropertyBuilderExtensions).GetRequiredRuntimeMethod( + nameof(SqlServerPropertyBuilderExtensions.UseIdentityColumn), typeof(PropertyBuilder), typeof(long), typeof(int)); + + private static readonly MethodInfo _indexIsClusteredMethodInfo + = typeof(SqlServerIndexBuilderExtensions).GetRequiredRuntimeMethod( + nameof(SqlServerIndexBuilderExtensions.IsClustered), typeof(IndexBuilder), typeof(bool)); + + private static readonly MethodInfo _indexIncludePropertiesMethodInfo + = typeof(SqlServerIndexBuilderExtensions).GetRequiredRuntimeMethod( + nameof(SqlServerIndexBuilderExtensions.IncludeProperties), typeof(IndexBuilder), typeof(string[])); + + private static readonly MethodInfo _indexHasFillFactorMethodInfo + = typeof(SqlServerIndexBuilderExtensions).GetRequiredRuntimeMethod( + nameof(SqlServerIndexBuilderExtensions.HasFillFactor), typeof(IndexBuilder), typeof(int)); + + private static readonly MethodInfo _keyIsClusteredMethodInfo + = typeof(SqlServerKeyBuilderExtensions).GetRequiredRuntimeMethod( + nameof(SqlServerKeyBuilderExtensions.IsClustered), typeof(KeyBuilder), typeof(bool)); + + private static readonly MethodInfo _tableIsTemporalMethodInfo + = typeof(SqlServerTableBuilderExtensions).GetRequiredRuntimeMethod( + nameof(SqlServerTableBuilderExtensions.IsTemporal), typeof(TableBuilder), typeof(bool)); + + private static readonly MethodInfo _temporalTableUseHistoryTableMethodInfo1 + = typeof(TemporalTableBuilder).GetRequiredRuntimeMethod( + nameof(TemporalTableBuilder.UseHistoryTable), typeof(string), typeof(string)); + + private static readonly MethodInfo _temporalTableUseHistoryTableMethodInfo2 + = typeof(TemporalTableBuilder).GetRequiredRuntimeMethod( + nameof(TemporalTableBuilder.UseHistoryTable), typeof(string)); + + private static readonly MethodInfo _temporalTableHasPeriodStartMethodInfo + = typeof(TemporalTableBuilder).GetRequiredRuntimeMethod( + nameof(TemporalTableBuilder.HasPeriodStart), typeof(string)); + + private static readonly MethodInfo _temporalTableHasPeriodEndMethodInfo + = typeof(TemporalTableBuilder).GetRequiredRuntimeMethod( + nameof(TemporalTableBuilder.HasPeriodEnd), typeof(string)); + + private static readonly MethodInfo _temporalPropertyHasColumnNameMethodInfo + = typeof(TemporalPeriodPropertyBuilder).GetRequiredRuntimeMethod( + nameof(TemporalPeriodPropertyBuilder.HasColumnName), typeof(string)); + + #endregion MethodInfos + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -44,11 +125,26 @@ public override IReadOnlyList GenerateFluentApiCalls( { var fragments = new List(base.GenerateFluentApiCalls(model, annotations)); - if (GenerateValueGenerationStrategy(annotations, onModel: true) is MethodCallCodeFragment valueGenerationStrategy) + if (GenerateValueGenerationStrategy(annotations, model, onModel: true) is MethodCallCodeFragment valueGenerationStrategy) { fragments.Add(valueGenerationStrategy); } + GenerateSimpleFluentApiCall( + annotations, + SqlServerAnnotationNames.MaxDatabaseSize, _modelHasDatabaseMaxSizeMethodInfo, + fragments); + + GenerateSimpleFluentApiCall( + annotations, + SqlServerAnnotationNames.ServiceTierSql, _modelHasServiceTierSqlMethodInfo, + fragments); + + GenerateSimpleFluentApiCall( + annotations, + SqlServerAnnotationNames.PerformanceLevelSql, _modelHasPerformanceLevelSqlMethodInfo, + fragments); + return fragments; } @@ -64,17 +160,15 @@ public override IReadOnlyList GenerateFluentApiCalls( { var fragments = new List(base.GenerateFluentApiCalls(property, annotations)); - if (GenerateValueGenerationStrategy(annotations, onModel: false) is MethodCallCodeFragment valueGenerationStrategy) + if (GenerateValueGenerationStrategy(annotations, property.DeclaringEntityType.Model, onModel: false) is MethodCallCodeFragment + valueGenerationStrategy) { fragments.Add(valueGenerationStrategy); } if (GetAndRemove(annotations, SqlServerAnnotationNames.Sparse) is bool isSparse) { - fragments.Add( - isSparse - ? new(nameof(SqlServerPropertyBuilderExtensions.IsSparse)) - : new(nameof(SqlServerPropertyBuilderExtensions.IsSparse), false)); + fragments.Add(isSparse ? new(_propertyIsSparseMethodInfo) : new(_propertyIsSparseMethodInfo, false)); } return fragments; @@ -111,44 +205,35 @@ public override IReadOnlyList GenerateFluentApiCalls( { temporalTableBuilderCalls.Add( historyTableSchema != null - ? new MethodCallCodeFragment(nameof(TemporalTableBuilder.UseHistoryTable), historyTableName, historyTableSchema) - : new MethodCallCodeFragment(nameof(TemporalTableBuilder.UseHistoryTable), historyTableName)); + ? new MethodCallCodeFragment(_temporalTableUseHistoryTableMethodInfo1, historyTableName, historyTableSchema) + : new MethodCallCodeFragment(_temporalTableUseHistoryTableMethodInfo2, historyTableName)); } // ttb => ttb.HasPeriodStart("Start").HasColumnName("ColumnStart") temporalTableBuilderCalls.Add( periodStartColumnName != null ? new MethodCallCodeFragment( - nameof(TemporalTableBuilder.HasPeriodStart), + _temporalTableHasPeriodStartMethodInfo, new[] { periodStartProperty.Name }, - new MethodCallCodeFragment( - nameof(TemporalPeriodPropertyBuilder.HasColumnName), - periodStartColumnName)) - : new MethodCallCodeFragment( - nameof(TemporalTableBuilder.HasPeriodStart), - periodStartProperty.Name)); + new MethodCallCodeFragment(_temporalPropertyHasColumnNameMethodInfo, periodStartColumnName)) + : new MethodCallCodeFragment(_temporalTableHasPeriodStartMethodInfo, periodStartProperty.Name)); // ttb => ttb.HasPeriodEnd("End").HasColumnName("ColumnEnd") temporalTableBuilderCalls.Add( periodEndColumnName != null - ? new MethodCallCodeFragment( - nameof(TemporalTableBuilder.HasPeriodEnd), - new[] { periodEndProperty.Name }, - new MethodCallCodeFragment( - nameof(TemporalPeriodPropertyBuilder.HasColumnName), - periodEndColumnName)) - : new MethodCallCodeFragment( - nameof(TemporalTableBuilder.HasPeriodEnd), - periodEndProperty.Name)); - + ? new MethodCallCodeFragment( + _temporalTableHasPeriodEndMethodInfo, + new[] { periodEndProperty.Name }, + new MethodCallCodeFragment(_temporalPropertyHasColumnNameMethodInfo, periodEndColumnName)) + : new MethodCallCodeFragment(_temporalTableHasPeriodEndMethodInfo, periodEndProperty.Name)); // ToTable(tb => tb.IsTemporal(ttb => { ... })) var toTemporalTableCall = new MethodCallCodeFragment( - nameof(RelationalEntityTypeBuilderExtensions.ToTable), + _entityTypeToTableMethodInfo, new NestedClosureCodeFragment( "tb", new MethodCallCodeFragment( - nameof(SqlServerTableBuilderExtensions.IsTemporal), + _tableIsTemporalMethodInfo, new NestedClosureCodeFragment( "ttb", temporalTableBuilderCalls)))); @@ -194,8 +279,8 @@ protected override bool IsHandledByConvention(IModel model, IAnnotation annotati protected override MethodCallCodeFragment? GenerateFluentApi(IKey key, IAnnotation annotation) => annotation.Name == SqlServerAnnotationNames.Clustered ? (bool)annotation.Value! == false - ? new MethodCallCodeFragment(nameof(SqlServerIndexBuilderExtensions.IsClustered), false) - : new MethodCallCodeFragment(nameof(SqlServerIndexBuilderExtensions.IsClustered)) + ? new MethodCallCodeFragment(_keyIsClusteredMethodInfo, false) + : new MethodCallCodeFragment(_keyIsClusteredMethodInfo) : null; /// @@ -208,20 +293,18 @@ protected override bool IsHandledByConvention(IModel model, IAnnotation annotati => annotation.Name switch { SqlServerAnnotationNames.Clustered => (bool)annotation.Value! == false - ? new MethodCallCodeFragment(nameof(SqlServerIndexBuilderExtensions.IsClustered), false) - : new MethodCallCodeFragment(nameof(SqlServerIndexBuilderExtensions.IsClustered)), - - SqlServerAnnotationNames.Include => new MethodCallCodeFragment( - nameof(SqlServerIndexBuilderExtensions.IncludeProperties), annotation.Value), + ? new MethodCallCodeFragment(_indexIsClusteredMethodInfo, false) + : new MethodCallCodeFragment(_indexIsClusteredMethodInfo), - SqlServerAnnotationNames.FillFactor => new MethodCallCodeFragment( - nameof(SqlServerIndexBuilderExtensions.HasFillFactor), annotation.Value), + SqlServerAnnotationNames.Include => new MethodCallCodeFragment(_indexIncludePropertiesMethodInfo, annotation.Value), + SqlServerAnnotationNames.FillFactor => new MethodCallCodeFragment(_indexHasFillFactorMethodInfo, annotation.Value), _ => null }; private MethodCallCodeFragment? GenerateValueGenerationStrategy( IDictionary annotations, + IModel model, bool onModel) { SqlServerValueGenerationStrategy strategy; @@ -239,24 +322,22 @@ protected override bool IsHandledByConvention(IModel model, IAnnotation annotati switch (strategy) { case SqlServerValueGenerationStrategy.IdentityColumn: - var seed = GetAndRemove(annotations, SqlServerAnnotationNames.IdentitySeed) ?? 1; - var increment = GetAndRemove(annotations, SqlServerAnnotationNames.IdentityIncrement) ?? 1; + var seed = GetAndRemove(annotations, SqlServerAnnotationNames.IdentitySeed) + ?? model.FindAnnotation(SqlServerAnnotationNames.IdentitySeed)?.Value as long? + ?? 1; + var increment = GetAndRemove(annotations, SqlServerAnnotationNames.IdentityIncrement) + ?? model.FindAnnotation(SqlServerAnnotationNames.IdentityIncrement)?.Value as int? + ?? 1; return new( - onModel - ? "UseIdentityColumns" - : "UseIdentityColumn", - (seed, increment) switch - { - (1, 1) => Array.Empty(), - (_, 1) => new object[] { seed }, - _ => new object[] { seed, increment } - }); + onModel ? _modelUseIdentityColumnsMethodInfo : _propertyUseIdentityColumnsMethodInfo, + seed, + increment); case SqlServerValueGenerationStrategy.SequenceHiLo: var name = GetAndRemove(annotations, SqlServerAnnotationNames.HiLoSequenceName); var schema = GetAndRemove(annotations, SqlServerAnnotationNames.HiLoSequenceSchema); return new( - nameof(SqlServerModelBuilderExtensions.UseHiLo), + _modelUseHiLoMethodInfo, (name, schema) switch { (null, null) => Array.Empty(), @@ -266,7 +347,7 @@ protected override bool IsHandledByConvention(IModel model, IAnnotation annotati case SqlServerValueGenerationStrategy.None: return new( - nameof(ModelBuilder.HasAnnotation), + _modelHasAnnotationMethodInfo, SqlServerAnnotationNames.ValueGenerationStrategy, SqlServerValueGenerationStrategy.None); @@ -286,5 +367,22 @@ protected override bool IsHandledByConvention(IModel model, IAnnotation annotati return default; } + + private static void GenerateSimpleFluentApiCall( + IDictionary annotations, + string annotationName, + MethodInfo methodInfo, + List methodCallCodeFragments) + { + if (annotations.TryGetValue(annotationName, out var annotation)) + { + annotations.Remove(annotationName); + if (annotation.Value is object annotationValue) + { + methodCallCodeFragments.Add( + new MethodCallCodeFragment(methodInfo, annotationValue)); + } + } + } } } diff --git a/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerCodeGenerator.cs b/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerCodeGenerator.cs index 4cf3dbed30b..438fe998b30 100644 --- a/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerCodeGenerator.cs +++ b/src/EFCore.SqlServer/Scaffolding/Internal/SqlServerCodeGenerator.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Reflection; using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Scaffolding; namespace Microsoft.EntityFrameworkCore.SqlServer.Scaffolding.Internal @@ -14,6 +17,13 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Scaffolding.Internal /// public class SqlServerCodeGenerator : ProviderCodeGenerator { + private static readonly MethodInfo _useSqlServerMethodInfo + = typeof(SqlServerDbContextOptionsExtensions).GetRequiredRuntimeMethod( + nameof(SqlServerDbContextOptionsExtensions.UseSqlServer), + typeof(DbContextOptionsBuilder), + typeof(string), + typeof(Action)); + /// /// Initializes a new instance of the class. /// @@ -33,7 +43,7 @@ public override MethodCallCodeFragment GenerateUseProvider( string connectionString, MethodCallCodeFragment? providerOptions) => new( - nameof(SqlServerDbContextOptionsExtensions.UseSqlServer), + _useSqlServerMethodInfo, providerOptions == null ? new object[] { connectionString } : new object[] { connectionString, new NestedClosureCodeFragment("x", providerOptions) }); diff --git a/src/EFCore.Sqlite.Core/Scaffolding/Internal/SqliteCodeGenerator.cs b/src/EFCore.Sqlite.Core/Scaffolding/Internal/SqliteCodeGenerator.cs index 511202fe8f3..290f3393d53 100644 --- a/src/EFCore.Sqlite.Core/Scaffolding/Internal/SqliteCodeGenerator.cs +++ b/src/EFCore.Sqlite.Core/Scaffolding/Internal/SqliteCodeGenerator.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Reflection; using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Scaffolding; namespace Microsoft.EntityFrameworkCore.Sqlite.Scaffolding.Internal @@ -14,6 +17,13 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Scaffolding.Internal /// public class SqliteCodeGenerator : ProviderCodeGenerator { + private static readonly MethodInfo _useSqliteMethodInfo + = typeof(SqliteDbContextOptionsBuilderExtensions).GetRequiredRuntimeMethod( + nameof(SqliteDbContextOptionsBuilderExtensions.UseSqlite), + typeof(DbContextOptionsBuilder), + typeof(string), + typeof(Action)); + /// /// Initializes a new instance of the class. /// @@ -33,7 +43,7 @@ public override MethodCallCodeFragment GenerateUseProvider( string connectionString, MethodCallCodeFragment? providerOptions) => new( - nameof(SqliteDbContextOptionsBuilderExtensions.UseSqlite), + _useSqliteMethodInfo, providerOptions == null ? new object[] { connectionString } : new object[] { connectionString, new NestedClosureCodeFragment("x", providerOptions) }); diff --git a/src/EFCore.Sqlite.NTS/Scaffolding/Internal/SqliteNetTopologySuiteCodeGeneratorPlugin.cs b/src/EFCore.Sqlite.NTS/Scaffolding/Internal/SqliteNetTopologySuiteCodeGeneratorPlugin.cs index 8b495ef34ad..5032d7382b5 100644 --- a/src/EFCore.Sqlite.NTS/Scaffolding/Internal/SqliteNetTopologySuiteCodeGeneratorPlugin.cs +++ b/src/EFCore.Sqlite.NTS/Scaffolding/Internal/SqliteNetTopologySuiteCodeGeneratorPlugin.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Reflection; using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Scaffolding; namespace Microsoft.EntityFrameworkCore.Sqlite.Scaffolding.Internal @@ -14,6 +17,11 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Scaffolding.Internal /// public class SqliteNetTopologySuiteCodeGeneratorPlugin : ProviderCodeGeneratorPlugin { + private static readonly MethodInfo _useNetTopologySuiteMethodInfo + = typeof(SqliteNetTopologySuiteDbContextOptionsBuilderExtensions).GetRequiredRuntimeMethod( + nameof(SqliteNetTopologySuiteDbContextOptionsBuilderExtensions.UseNetTopologySuite), + typeof(SqliteDbContextOptionsBuilder)); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -21,7 +29,6 @@ public class SqliteNetTopologySuiteCodeGeneratorPlugin : ProviderCodeGeneratorPl /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override MethodCallCodeFragment GenerateProviderOptions() - => new( - nameof(SqliteNetTopologySuiteDbContextOptionsBuilderExtensions.UseNetTopologySuite)); + => new(_useNetTopologySuiteMethodInfo); } } diff --git a/src/EFCore/Design/AttributeCodeFragment.cs b/src/EFCore/Design/AttributeCodeFragment.cs index c362b9ca7a1..b81c2ebfa08 100644 --- a/src/EFCore/Design/AttributeCodeFragment.cs +++ b/src/EFCore/Design/AttributeCodeFragment.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Collections.Generic; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Design diff --git a/src/EFCore/Design/ICSharpHelper.cs b/src/EFCore/Design/ICSharpHelper.cs index a0c8e0f69b1..ae255d40dfd 100644 --- a/src/EFCore/Design/ICSharpHelper.cs +++ b/src/EFCore/Design/ICSharpHelper.cs @@ -17,8 +17,12 @@ public interface ICSharpHelper /// Generates a method call code fragment. /// /// The method call. + /// An identifier on which the method call will be generated. + /// + /// if the method call should be type-qualified, for instance/extension syntax. + /// /// The fragment. - string Fragment(MethodCallCodeFragment fragment); + string Fragment(MethodCallCodeFragment fragment, string? instanceIdentifier = null, bool typeQualified = false); /// /// Generates a valid C# identifier from the specified string unique to the scope. diff --git a/src/EFCore/Design/MethodCallCodeFragment.cs b/src/EFCore/Design/MethodCallCodeFragment.cs index 1c0163b62f0..b7e113c09bc 100644 --- a/src/EFCore/Design/MethodCallCodeFragment.cs +++ b/src/EFCore/Design/MethodCallCodeFragment.cs @@ -1,7 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Design @@ -13,26 +16,77 @@ public class MethodCallCodeFragment { private readonly List _arguments; + /// + /// Only used when is null + /// + private readonly string? _method; + + /// + /// Initializes a new instance of the class. + /// + /// The method's . + /// The method call's arguments. Can be . + public MethodCallCodeFragment(MethodInfo methodInfo, params object?[] arguments) + { + Check.NotNull(methodInfo, nameof(methodInfo)); + Check.NotNull(arguments, nameof(arguments)); + + var parameterLength = methodInfo.GetParameters().Length; + if (methodInfo.IsStatic) + { + parameterLength--; + } + + if (arguments.Length > parameterLength) + { + throw new ArgumentException( + CoreStrings.IncorrectNumberOfArguments(methodInfo.Name, arguments.Length, parameterLength), + nameof(arguments)); + } + + MethodInfo = methodInfo; + _arguments = new List(arguments); + } + /// /// Initializes a new instance of the class. /// /// The method's name. /// The method call's arguments. Can be . + [Obsolete("Use the overload accepting a MethodInfo")] public MethodCallCodeFragment(string method, params object?[] arguments) { Check.NotEmpty(method, nameof(method)); Check.NotNull(arguments, nameof(arguments)); - Method = method; + _method = method; _arguments = new List(arguments); } + /// + /// Initializes a new instance of the class. + /// + /// The method's . + /// The method call's arguments. Can be . + /// The next method call to chain after this. + public MethodCallCodeFragment( + MethodInfo methodInfo, + object?[] arguments, + MethodCallCodeFragment chainedCall) + : this(methodInfo, arguments) + { + Check.NotNull(chainedCall, nameof(chainedCall)); + + ChainedCall = chainedCall; + } + /// /// Initializes a new instance of the class. /// /// The method's name. /// The method call's arguments. Can be . /// The next method call to chain after this. + [Obsolete("Use the overload accepting a MethodInfo")] public MethodCallCodeFragment( string method, object?[] arguments, @@ -45,10 +99,31 @@ public MethodCallCodeFragment( } /// - /// Gets or sets the method's name. + /// Gets the for this method call. + /// + /// The . + public virtual MethodInfo? MethodInfo { get; } + + /// + /// Gets the namespace of the method's declaring type. + /// + /// The declaring type's name. + public virtual string? Namespace + => MethodInfo?.DeclaringType?.Namespace; + + /// + /// Gets the name of the method's declaring type. + /// + /// The declaring type's name. + public virtual string? DeclaringType + => MethodInfo?.DeclaringType?.Name; + + /// + /// Gets the method's name. /// /// The method's name. - public virtual string Method { get; } + public virtual string Method + => MethodInfo is null ? _method! : MethodInfo.Name; /// /// Gets the method call's arguments. @@ -63,12 +138,22 @@ public virtual IReadOnlyList Arguments /// The next method call. public virtual MethodCallCodeFragment? ChainedCall { get; } + /// + /// Creates a method chain from this method to another. + /// + /// The method's . + /// The next method call's arguments. + /// A new fragment representing the method chain. + public virtual MethodCallCodeFragment Chain(MethodInfo methodInfo, params object[] arguments) + => Chain(new MethodCallCodeFragment(methodInfo, arguments)); + /// /// Creates a method chain from this method to another. /// /// The next method's name. /// The next method call's arguments. /// A new fragment representing the method chain. + [Obsolete("Use the overload accepting a MethodInfo")] public virtual MethodCallCodeFragment Chain(string method, params object[] arguments) => Chain(new MethodCallCodeFragment(method, arguments)); @@ -78,6 +163,10 @@ public virtual MethodCallCodeFragment Chain(string method, params object[] argum /// The next method. /// A new fragment representing the method chain. public virtual MethodCallCodeFragment Chain(MethodCallCodeFragment call) - => new(Method, _arguments.ToArray(), ChainedCall?.Chain(call) ?? call); + => MethodInfo is null +#pragma warning disable 618 + ? new(_method!, _arguments.ToArray(), ChainedCall?.Chain(call) ?? call) +#pragma warning restore 618 + : new(MethodInfo, _arguments.ToArray(), ChainedCall?.Chain(call) ?? call); } } diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 55a7f49f547..28767bb510e 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -1166,6 +1166,14 @@ public static string InconsistentOwnership(object? ownedEntityType, object? nonO GetString("InconsistentOwnership", nameof(ownedEntityType), nameof(nonOwnedEntityType)), ownedEntityType, nonOwnedEntityType); + /// + /// '{method}' was invoked with {argumentCount} arguments, but has {parameterCount} parameters. + /// + public static string IncorrectNumberOfArguments(object? method, object? argumentCount, object? parameterCount) + => string.Format( + GetString("IncorrectNumberOfArguments", nameof(method), nameof(argumentCount), nameof(parameterCount)), + method, argumentCount, parameterCount); + /// /// The specified index properties {indexProperties} are not declared on the entity type '{entityType}'. Ensure that index properties are declared on the target entity type. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 283f838b00d..47921f78ff1 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -561,6 +561,9 @@ The entity type '{ownedEntityType}' is configured as owned, but the entity type '{nonOwnedEntityType}' is not. Configure all entity types with defining navigations sharing a CLR type as owned in 'OnModelCreating'. Obsolete + + '{method}' was invoked with {argumentCount} arguments, but has {parameterCount} parameters. + The specified index properties {indexProperties} are not declared on the entity type '{entityType}'. Ensure that index properties are declared on the target entity type. diff --git a/src/Shared/SharedTypeExtensions.cs b/src/Shared/SharedTypeExtensions.cs index ccc73beb4ac..69dc7224f67 100644 --- a/src/Shared/SharedTypeExtensions.cs +++ b/src/Shared/SharedTypeExtensions.cs @@ -163,77 +163,31 @@ public static MethodInfo GetRequiredMethod(this Type type, string name, params T } public static PropertyInfo GetRequiredProperty(this Type type, string name) - { - var property = type.GetTypeInfo().GetProperty(name); - if (property == null) - { - throw new InvalidOperationException(); - } - - return property; - } + => type.GetTypeInfo().GetProperty(name) + ?? throw new InvalidOperationException($"Could not find property '{name}' on type '{type}'"); public static FieldInfo GetRequiredDeclaredField(this Type type, string name) - { - var field = type.GetTypeInfo().GetDeclaredField(name); - if (field == null) - { - throw new InvalidOperationException(); - } - - return field; - } + => type.GetTypeInfo().GetDeclaredField(name) + ?? throw new InvalidOperationException($"Could not find field '{name}' on type '{type}'"); public static MethodInfo GetRequiredDeclaredMethod(this Type type, string name) - { - var method = type.GetTypeInfo().GetDeclaredMethod(name); - if (method == null) - { - throw new InvalidOperationException(); - } - - return method; - } + => type.GetTypeInfo().GetDeclaredMethod(name) + ?? throw new InvalidOperationException($"Could not find method '{name}' on type '{type}'"); public static MethodInfo GetRequiredDeclaredMethod(this Type type, string name, Func methodSelector) - { - var method = type.GetTypeInfo().GetDeclaredMethods(name).Single(methodSelector); - - return method; - } + => type.GetTypeInfo().GetDeclaredMethods(name).Single(methodSelector); public static PropertyInfo GetRequiredDeclaredProperty(this Type type, string name) - { - var property = type.GetTypeInfo().GetDeclaredProperty(name); - if (property == null) - { - throw new InvalidOperationException(); - } - - return property; - } + => type.GetTypeInfo().GetDeclaredProperty(name) + ?? throw new InvalidOperationException($"Could not find property '{name}' on type '{type}'"); public static MethodInfo GetRequiredRuntimeMethod(this Type type, string name, params Type[] parameters) - { - var method = type.GetTypeInfo().GetRuntimeMethod(name, parameters); - if (method == null) - { - throw new InvalidOperationException(); - } - - return method; - } + => type.GetTypeInfo().GetRuntimeMethod(name, parameters) + ?? throw new InvalidOperationException($"Could not find method '{name}' on type '{type}'"); public static PropertyInfo GetRequiredRuntimeProperty(this Type type, string name) - { - var property = type.GetTypeInfo().GetRuntimeProperty(name); - if (property == null) - { - throw new InvalidOperationException(); - } - - return property; - } + => type.GetTypeInfo().GetRuntimeProperty(name) + ?? throw new InvalidOperationException($"Could not find property '{name}' on type '{type}'"); public static bool IsInstantiable(this Type type) => !type.IsAbstract diff --git a/test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs b/test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs index b005024f944..ff9db93949f 100644 --- a/test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs +++ b/test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs @@ -4,6 +4,7 @@ using System; using System.Linq.Expressions; using System.Numerics; +using System.Reflection; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; @@ -292,77 +293,120 @@ public void Namespace_works(string[] input, string excepted) [ConditionalFact] public void Fragment_MethodCallCodeFragment_works() { - var method = new MethodCallCodeFragment("Test", true, 42); + var method = new MethodCallCodeFragment(_testFuncMethodInfo, true, 42); var result = new CSharpHelper(TypeMappingSource).Fragment(method); - Assert.Equal(".Test(true, 42)", result); + Assert.Equal(".TestFunc(true, 42)", result); } [ConditionalFact] public void Fragment_MethodCallCodeFragment_works_with_arrays() { - var method = new MethodCallCodeFragment("Test", new byte[] { 1, 2 }, new[] { 3, 4 }, new[] { "foo", "bar" }); + var method = new MethodCallCodeFragment(_testFuncMethodInfo, new byte[] { 1, 2 }, new[] { 3, 4 }, new[] { "foo", "bar" }); var result = new CSharpHelper(TypeMappingSource).Fragment(method); - Assert.Equal(".Test(new byte[] { 1, 2 }, new[] { 3, 4 }, new[] { \"foo\", \"bar\" })", result); + Assert.Equal(".TestFunc(new byte[] { 1, 2 }, new[] { 3, 4 }, new[] { \"foo\", \"bar\" })", result); } [ConditionalFact] public void Fragment_MethodCallCodeFragment_works_when_niladic() { - var method = new MethodCallCodeFragment("Test"); + var method = new MethodCallCodeFragment(_testFuncMethodInfo); var result = new CSharpHelper(TypeMappingSource).Fragment(method); - Assert.Equal(".Test()", result); + Assert.Equal(".TestFunc()", result); } [ConditionalFact] public void Fragment_MethodCallCodeFragment_works_when_chaining() { - var method = new MethodCallCodeFragment("Test") - .Chain("Test"); + var method = new MethodCallCodeFragment(_testFuncMethodInfo) + .Chain(_testFuncMethodInfo); var result = new CSharpHelper(TypeMappingSource).Fragment(method); - Assert.Equal(".Test().Test()", result); + Assert.Equal($".TestFunc(){EOL}.TestFunc()", result); } [ConditionalFact] public void Fragment_MethodCallCodeFragment_works_when_chaining_on_chain() { - var method = new MethodCallCodeFragment("One", Array.Empty(), new MethodCallCodeFragment("Two")) - .Chain("Three"); + var method = new MethodCallCodeFragment( + _testFuncMethodInfo, new[] { "One" }, new MethodCallCodeFragment(_testFuncMethodInfo, "Two")) + .Chain(_testFuncMethodInfo, "Three"); var result = new CSharpHelper(TypeMappingSource).Fragment(method); - Assert.Equal(".One().Two().Three()", result); + Assert.Equal(@$".TestFunc(""One""){EOL}.TestFunc(""Two""){EOL}.TestFunc(""Three"")", result); } [ConditionalFact] public void Fragment_MethodCallCodeFragment_works_when_chaining_on_chain_with_call() { - var method = new MethodCallCodeFragment("One", Array.Empty(), new MethodCallCodeFragment("Two")) - .Chain(new MethodCallCodeFragment("Three", Array.Empty(), new MethodCallCodeFragment("Four"))); + var method = new MethodCallCodeFragment(_testFuncMethodInfo, new[] { "One" }, new MethodCallCodeFragment(_testFuncMethodInfo, "Two")) + .Chain(new MethodCallCodeFragment(_testFuncMethodInfo, new[] { "Three" }, new MethodCallCodeFragment(_testFuncMethodInfo, "Four"))); var result = new CSharpHelper(TypeMappingSource).Fragment(method); - Assert.Equal(".One().Two().Three().Four()", result); + Assert.Equal(@$".TestFunc(""One""){EOL}.TestFunc(""Two""){EOL}.TestFunc(""Three""){EOL}.TestFunc(""Four"")", result); } [ConditionalFact] public void Fragment_MethodCallCodeFragment_works_when_nested_closure() { var method = new MethodCallCodeFragment( - "Test", - new NestedClosureCodeFragment("x", new MethodCallCodeFragment("Test"))); + _testFuncMethodInfo, + new NestedClosureCodeFragment("x", new MethodCallCodeFragment(_testFuncMethodInfo))); + + var result = new CSharpHelper(TypeMappingSource).Fragment(method); + + Assert.Equal(".TestFunc(x => x.TestFunc())", result); + } + + [ConditionalFact] + public void Fragment_MethodCallCodeFragment_works_with_identifier() + { + var method = new MethodCallCodeFragment(_testFuncMethodInfo, true, 42); + + var result = new CSharpHelper(TypeMappingSource).Fragment(method, instanceIdentifier: "builder"); + + Assert.Equal("builder.TestFunc(true, 42)", result); + } + + [ConditionalFact] + public void Fragment_MethodCallCodeFragment_works_with_identifier_chained() + { + var method = new MethodCallCodeFragment(_testFuncMethodInfo, new[] { "One"}, new MethodCallCodeFragment(_testFuncMethodInfo)); + + var result = new CSharpHelper(TypeMappingSource).Fragment(method, instanceIdentifier: "builder"); + + Assert.Equal($@"builder{EOL} .TestFunc(""One""){EOL} .TestFunc()", result); + } + + [ConditionalFact] + public void Fragment_MethodCallCodeFragment_works_with_type_qualified() + { + var method = new MethodCallCodeFragment(_testFuncMethodInfo, true, 42); + + var result = new CSharpHelper(TypeMappingSource).Fragment(method, instanceIdentifier: "builder"); + + Assert.Equal("builder.TestFunc(true, 42)", result); + } + +#pragma warning disable 618 // Method name constructors on MethodCallCodeFragment have been obsoleted + [ConditionalFact] + public void Fragment_MethodCallCodeFragment_works_without_MethodInfo() + { + var method = new MethodCallCodeFragment("TestFunc", true, 42); var result = new CSharpHelper(TypeMappingSource).Fragment(method); - Assert.Equal(".Test(x => x.Test())", result); + Assert.Equal(".TestFunc(true, 42)", result); } +#pragma warning restore 618 [ConditionalFact] public void Really_unknown_literal_with_no_mapping_support() @@ -655,6 +699,14 @@ public SimpleTestNonImplementedTypeMapping() protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) => throw new NotSupportedException(); } + + private static readonly MethodInfo _testFuncMethodInfo + = typeof(CSharpHelperTest).GetRuntimeMethod( + nameof(TestFunc), + new[] { typeof(object), typeof(object), typeof(object), typeof(object) }); + + public static void TestFunc(object builder, object o1, object o2, object o3) + => throw new NotSupportedException(); } internal class SimpleTestType diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index a7e8bbb0aa9..11e66b16cad 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -35,7 +35,7 @@ namespace Microsoft.EntityFrameworkCore.Migrations.Design public class CSharpMigrationsGeneratorTest { private static readonly string _nl = Environment.NewLine; - private static readonly string _toTable = _nl + @"modelBuilder.ToTable(""WithAnnotations"");" + _nl; + private static readonly string _toTable = _nl + @"entityTypeBuilder.ToTable(""WithAnnotations"")"; [ConditionalFact] public void Test_new_annotations_handled_for_entity_types() @@ -108,42 +108,36 @@ public void Test_new_annotations_handled_for_entity_types() { { RelationalAnnotationNames.TableName, - ("MyTable", _nl + "modelBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToTable) + @"(""MyTable"");" + _nl) + ("MyTable", _nl + "entityTypeBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToTable) + @"(""MyTable"")") }, { RelationalAnnotationNames.Schema, ("MySchema", _nl - + "modelBuilder." + + "entityTypeBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToTable) - + @"(""WithAnnotations"", ""MySchema"");" - + _nl) + + @"(""WithAnnotations"", ""MySchema"")") }, { CoreAnnotationNames.DiscriminatorProperty, ("Id", - _toTable + _toTable + ";" + _nl + _nl - + "modelBuilder.HasDiscriminator" - + @"(""Id"");" - + _nl) + + "entityTypeBuilder.HasDiscriminator" + + @"(""Id"")") }, { CoreAnnotationNames.DiscriminatorValue, ("MyDiscriminatorValue", - _toTable + _toTable + ";" + _nl + _nl - + "modelBuilder.HasDiscriminator" + + "entityTypeBuilder.HasDiscriminator" + "()." + nameof(DiscriminatorBuilder.HasValue) - + @"(""MyDiscriminatorValue"");" - + _nl) + + @"(""MyDiscriminatorValue"")") }, { RelationalAnnotationNames.Comment, ("My Comment", - _toTable + _toTable + ";" + _nl + _nl - + "modelBuilder" - + _nl - + @" .HasComment(""My Comment"");" - + _nl) + + @"entityTypeBuilder.HasComment(""My Comment"")") }, { #pragma warning disable CS0612 // Type or member is obsolete @@ -153,15 +147,15 @@ public void Test_new_annotations_handled_for_entity_types() }, { RelationalAnnotationNames.ViewName, - ("MyView", _nl + "modelBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToView) + @"(""MyView"");" + _nl) + ("MyView", _nl + "entityTypeBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToView) + @"(""MyView"")") }, { RelationalAnnotationNames.FunctionName, - (null, _nl + "modelBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToFunction) + @"(null);" + _nl) + (null, _nl + "entityTypeBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToFunction) + @"(null)") }, { RelationalAnnotationNames.SqlQuery, - (null, _nl + "modelBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToSqlQuery) + @"(null);" + _nl) + (null, _nl + "entityTypeBuilder." + nameof(RelationalEntityTypeBuilderExtensions.ToSqlQuery) + @"(null)") } }; @@ -169,7 +163,7 @@ public void Test_new_annotations_handled_for_entity_types() b => b.Entity().Metadata, notForEntityType, forEntityType, a => _toTable, - (g, m, b) => g.TestGenerateEntityTypeAnnotations("modelBuilder", (IEntityType)m, b)); + (g, m, b) => g.TestGenerateEntityTypeAnnotations("entityTypeBuilder", (IEntityType)m, b)); } [ConditionalFact] @@ -229,8 +223,7 @@ public void Test_new_annotations_handled_for_properties() RelationalAnnotationNames.ModelDependencies }; - var columnMapping = - $@"{_nl}.{nameof(RelationalPropertyBuilderExtensions.HasColumnType)}(""default_int_mapping"")"; + var columnMapping = $@"{_nl}.{nameof(RelationalPropertyBuilderExtensions.HasColumnType)}(""default_int_mapping"")"; // Add a line here if the code generator is supposed to handle this annotation // Note that other tests should be added to check code is generated correctly @@ -291,7 +284,7 @@ public void Test_new_annotations_handled_for_properties() b => b.Entity().Property(e => e.Id).Metadata, notForProperty, forProperty, a => $"{columnMapping}", - (g, m, b) => g.TestGeneratePropertyAnnotations((IProperty)m, b)); + (g, m, b) => g.TestGeneratePropertyAnnotations("propertyBuilder", (IProperty)m, b)); } private static void MissingAnnotationCheck( @@ -353,11 +346,13 @@ private static void MissingAnnotationCheck( try { - Assert.Equal( - validAnnotations.ContainsKey(annotationName) + var expected = validAnnotations.ContainsKey(annotationName) ? validAnnotations[annotationName].Expected - : generationDefault(annotationName), - sb.ToString()); + : generationDefault(annotationName); + + Assert.Equal( + string.IsNullOrEmpty(expected) ? expected : $"{expected};{_nl}", + sb.ToString()); } catch (Exception e) { @@ -381,8 +376,11 @@ public virtual void TestGenerateEntityTypeAnnotations( IndentedStringBuilder stringBuilder) => GenerateEntityTypeAnnotations(builderName, entityType, stringBuilder); - public virtual void TestGeneratePropertyAnnotations(IProperty property, IndentedStringBuilder stringBuilder) - => GeneratePropertyAnnotations(property, stringBuilder); + public virtual void TestGeneratePropertyAnnotations( + string builderName, + IProperty property, + IndentedStringBuilder stringBuilder) + => GeneratePropertyAnnotations(builderName, property, stringBuilder); } // ReSharper disable once ClassNeverInstantiated.Local @@ -470,7 +468,7 @@ private static void AssertConverter(ValueConverter valueConverter, string expect var sb = new IndentedStringBuilder(); - generator.TestGeneratePropertyAnnotations((IProperty)property, sb); + generator.TestGeneratePropertyAnnotations("propertyBuilder", (IProperty)property, sb); Assert.Equal(expected + _nl + ".HasMaxLength(1000)", sb.ToString()); } @@ -589,8 +587,7 @@ partial class MyMigration protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder - .HasAnnotation(""Some:EnumValue"", RegexOptions.Multiline); + modelBuilder.HasAnnotation(""Some:EnumValue"", RegexOptions.Multiline); modelBuilder.Entity(""T1"", b => { @@ -719,8 +716,7 @@ partial class MySnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder - .HasAnnotation(""Some:EnumValue"", RegexOptions.Multiline); + modelBuilder.HasAnnotation(""Some:EnumValue"", RegexOptions.Multiline); modelBuilder.Entity(""Cheese"", b => { diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 9b1ca3dddc8..ad23167c10a 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore.ChangeTracking; -using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -26,6 +25,7 @@ using NetTopologySuite; using NetTopologySuite.Geometries; using Xunit; +using Xunit.Sdk; // ReSharper disable InconsistentNaming // ReSharper disable UnusedAutoPropertyAccessor.Local @@ -296,14 +296,15 @@ public virtual void Model_annotations_are_stored_in_snapshot() @" modelBuilder .HasAnnotation(""AnnotationName"", ""AnnotationValue"") - .HasAnnotation(""Relational:MaxIdentifierLength"", 128) - .HasAnnotation(""SqlServer:DatabaseMaxSize"", ""100 MB"") - .HasAnnotation(""SqlServer:PerformanceLevelSql"", ""'S0'"") - .HasAnnotation(""SqlServer:ServiceTierSql"", ""'basic'"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);"), + .HasAnnotation(""Relational:MaxIdentifierLength"", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + SqlServerModelBuilderExtensions.HasDatabaseMaxSize(modelBuilder, ""100 MB""); + SqlServerModelBuilderExtensions.HasServiceTierSql(modelBuilder, ""'basic'""); + SqlServerModelBuilderExtensions.HasPerformanceLevelSql(modelBuilder, ""'S0'"");"), o => { - Assert.Equal(6, o.GetAnnotations().Count()); + Assert.Equal(8, o.GetAnnotations().Count()); Assert.Equal("AnnotationValue", o["AnnotationName"]); }); } @@ -322,11 +323,12 @@ public virtual void Model_default_schema_annotation_is_stored_in_snapshot_as_flu modelBuilder .HasDefaultSchema(""DefaultSchema"") .HasAnnotation(""AnnotationName"", ""AnnotationValue"") - .HasAnnotation(""Relational:MaxIdentifierLength"", 128) - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);"), + .HasAnnotation(""Relational:MaxIdentifierLength"", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);"), o => { - Assert.Equal(4, o.GetAnnotations().Count()); + Assert.Equal(6, o.GetAnnotations().Count()); Assert.Equal("AnnotationValue", o["AnnotationName"]); Assert.Equal("DefaultSchema", o[RelationalAnnotationNames.DefaultSchema]); }); @@ -348,8 +350,9 @@ public virtual void Entities_are_stored_in_model_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id""); @@ -360,8 +363,9 @@ public virtual void Entities_are_stored_in_model_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -399,8 +403,9 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Discriminator"") .HasColumnType(""nvarchar(max)""); @@ -430,7 +435,7 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT() });"), o => { - Assert.Equal(2, o.GetAnnotations().Count()); + Assert.Equal(4, o.GetAnnotations().Count()); Assert.Equal( "DerivedEntity", @@ -456,8 +461,9 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT_with_one_exclu { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Discriminator"") .HasColumnType(""nvarchar(max)""); @@ -487,7 +493,7 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT_with_one_exclu });"), o => { - Assert.Equal(2, o.GetAnnotations().Count()); + Assert.Equal(4, o.GetAnnotations().Count()); Assert.Equal( "DerivedEntity", @@ -507,8 +513,9 @@ public void Views_are_stored_in_the_model_snapshot() modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty"", b => { b.Property(""Id"") - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id""); @@ -622,7 +629,7 @@ public virtual void Sequence_is_stored_in_snapshot_as_fluent_api() .IsCyclic();"), o => { - Assert.Equal(3, o.GetAnnotations().Count()); + Assert.Equal(5, o.GetAnnotations().Count()); }); } @@ -643,8 +650,9 @@ public virtual void CheckConstraint_is_stored_in_snapshot_as_fluent_api() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -679,8 +687,9 @@ public virtual void CheckConstraint_is_only_stored_in_snapshot_once_for_TPH() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Discriminator"") .IsRequired() @@ -730,11 +739,9 @@ public virtual void Model_use_identity_columns() builder => builder.UseIdentityColumns(), AddBoilerPlate( @" - modelBuilder - .HasAnnotation(""Relational:MaxIdentifierLength"", 128) - .HasAnnotation(""SqlServer:IdentityIncrement"", 1) - .HasAnnotation(""SqlServer:IdentitySeed"", 1L) - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);"), + modelBuilder.HasAnnotation(""Relational:MaxIdentifierLength"", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);"), o => { Assert.Equal(4, o.GetAnnotations().Count()); @@ -751,11 +758,9 @@ public virtual void Model_use_identity_columns_custom_seed() builder => builder.UseIdentityColumns(5), AddBoilerPlate( @" - modelBuilder - .HasAnnotation(""Relational:MaxIdentifierLength"", 128) - .HasAnnotation(""SqlServer:IdentityIncrement"", 1) - .HasAnnotation(""SqlServer:IdentitySeed"", 5L) - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);"), + modelBuilder.HasAnnotation(""Relational:MaxIdentifierLength"", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 5L, 1);"), o => { Assert.Equal(4, o.GetAnnotations().Count()); @@ -772,11 +777,9 @@ public virtual void Model_use_identity_columns_custom_increment() builder => builder.UseIdentityColumns(increment: 5), AddBoilerPlate( @" - modelBuilder - .HasAnnotation(""Relational:MaxIdentifierLength"", 128) - .HasAnnotation(""SqlServer:IdentityIncrement"", 5) - .HasAnnotation(""SqlServer:IdentitySeed"", 1L) - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);"), + modelBuilder.HasAnnotation(""Relational:MaxIdentifierLength"", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 5);"), o => { Assert.Equal(4, o.GetAnnotations().Count()); @@ -804,18 +807,17 @@ public virtual void Model_use_identity_columns_custom_seed_increment() }, AddBoilerPlate( @" - modelBuilder - .HasAnnotation(""Relational:MaxIdentifierLength"", 128) - .HasAnnotation(""SqlServer:IdentityIncrement"", 5) - .HasAnnotation(""SqlServer:IdentitySeed"", 9223372036854775807L) - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.HasAnnotation(""Relational:MaxIdentifierLength"", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 9223372036854775807L, 5); modelBuilder.Entity(""Building"", b => { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 9223372036854775807L, 5); b.HasKey(""Id""); @@ -855,15 +857,15 @@ public virtual void EntityType_annotations_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id""); b.ToTable(""EntityWithOneProperty""); - b - .HasAnnotation(""AnnotationName"", ""AnnotationValue""); + b.HasAnnotation(""AnnotationName"", ""AnnotationValue""); });"), o => { @@ -888,8 +890,9 @@ public virtual void BaseType_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Discriminator"") .IsRequired() @@ -956,8 +959,9 @@ public virtual void Discriminator_annotations_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Discriminator"") .IsRequired() @@ -1016,8 +1020,9 @@ public virtual void Properties_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -1114,8 +1119,9 @@ public virtual void Alternate_keys_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -1152,8 +1158,9 @@ public virtual void Indexes_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -1188,8 +1195,9 @@ public virtual void Indexes_are_stored_in_snapshot_including_composite_index() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -1229,8 +1237,9 @@ public virtual void Foreign_keys_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id""); @@ -1241,8 +1250,9 @@ public virtual void Foreign_keys_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -1312,8 +1322,9 @@ public virtual void Many_to_many_join_table_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Name"") .HasColumnType(""nvarchar(max)""); @@ -1327,8 +1338,9 @@ public virtual void Many_to_many_join_table_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Description"") .HasColumnType(""nvarchar(max)""); @@ -1453,8 +1465,9 @@ public virtual void Can_override_table_name_for_many_to_many_join_table_stored_i { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Name"") .HasColumnType(""nvarchar(max)""); @@ -1468,8 +1481,9 @@ public virtual void Can_override_table_name_for_many_to_many_join_table_stored_i { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Description"") .HasColumnType(""nvarchar(max)""); @@ -1622,8 +1636,9 @@ public virtual void Shared_columns_are_stored_in_the_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .ValueGeneratedOnUpdateSometimes() @@ -1730,8 +1745,9 @@ public virtual void AlternateKey_name_preserved_when_generic() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Property"") .HasColumnType(""uniqueidentifier""); @@ -1767,8 +1783,9 @@ public virtual void Discriminator_of_enum() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Day"") .HasColumnType(""bigint""); @@ -1799,8 +1816,9 @@ public virtual void Discriminator_of_enum_to_string() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Day"") .IsRequired() @@ -1837,8 +1855,9 @@ public virtual void Temporal_table_information_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""End"") .ValueGeneratedOnAddOrUpdate() @@ -1857,14 +1876,17 @@ public virtual void Temporal_table_information_is_stored_in_snapshot() b.ToTable(""EntityWithStringProperty""); - b - .ToTable(tb => tb.IsTemporal(ttb => - { - ttb.UseHistoryTable(""HistoryTable""); - ttb.HasPeriodStart(""Start"").HasColumnName(""PeriodStart""); - ttb.HasPeriodEnd(""End"").HasColumnName(""PeriodEnd""); - } - )); + b.ToTable(tb => tb.IsTemporal(ttb => + { + ttb.UseHistoryTable(""HistoryTable""); + ttb + .HasPeriodStart(""Start"") + .HasColumnName(""PeriodStart""); + ttb + .HasPeriodEnd(""End"") + .HasColumnName(""PeriodEnd""); + } + )); });", usingSystem: true), o => { @@ -1929,8 +1951,9 @@ public virtual void Owned_types_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id"") .HasName(""PK_Custom""); @@ -1974,8 +1997,9 @@ public virtual void Owned_types_are_stored_in_snapshot() .IsUnique() .HasFilter(""[EntityWithTwoProperties_EntityWithStringKeyId] IS NOT NULL""); - b1.HasIndex(""Id"") - .HasAnnotation(""SqlServer:Include"", new[] { ""AlternateId"" }); + b1.HasIndex(""Id""); + + SqlServerIndexBuilderExtensions.IncludeProperties(b1.HasIndex(""Id""), new[] { ""AlternateId"" }); b1.ToTable(""EntityWithOneProperty""); @@ -2008,8 +2032,9 @@ public virtual void Owned_types_are_stored_in_snapshot() { b1.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property(""Id""), 1L, 1); b1.Property(""EntityWithOnePropertyId"") .HasColumnType(""int""); @@ -2155,8 +2180,9 @@ public virtual void Owned_types_are_stored_in_snapshot_when_excluded() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id"") .HasName(""PK_Custom""); @@ -2233,8 +2259,9 @@ public virtual void Owned_types_are_stored_in_snapshot_when_excluded() { b1.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property(""Id""), 1L, 1); b1.Property(""EntityWithOnePropertyId"") .HasColumnType(""int""); @@ -2343,8 +2370,9 @@ public virtual void Shared_owned_types_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id""); @@ -2506,16 +2534,17 @@ partial class Snapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder - .HasAnnotation(""Relational:MaxIdentifierLength"", 128) - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.HasAnnotation(""Relational:MaxIdentifierLength"", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+TestOwner"", b => { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id""); @@ -2530,8 +2559,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType(""int""); b1.Property(""Id"") - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property(""Id""), 1L, 1); b1.Property(""TestEnum"") .HasColumnType(""int""); @@ -2587,16 +2617,17 @@ partial class Snapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder - .HasAnnotation(""Relational:MaxIdentifierLength"", 128) - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.HasAnnotation(""Relational:MaxIdentifierLength"", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+TestOwner"", b => { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id""); @@ -2612,8 +2643,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property(""Id""), 1L, 1); b1.Property(""TestEnum"") .HasColumnType(""int""); @@ -2693,8 +2725,9 @@ public virtual void Property_annotations_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""AnnotationName"", ""AnnotationValue"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasAnnotation(""AnnotationName"", ""AnnotationValue""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id""); @@ -2720,8 +2753,9 @@ public virtual void Custom_value_generator_is_ignored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id""); @@ -2743,8 +2777,9 @@ public virtual void Property_isNullable_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Name"") .IsRequired() @@ -2773,8 +2808,9 @@ public virtual void Property_ValueGenerated_value_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .ValueGeneratedOnAdd() @@ -2841,8 +2877,9 @@ public virtual void Property_maxLength_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Name"") .HasMaxLength(100) @@ -2867,8 +2904,9 @@ public virtual void Property_unicodeness_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Name"") .IsUnicode(false) @@ -2893,8 +2931,9 @@ public virtual void Property_fixedlengthness_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Name"") .HasMaxLength(100) @@ -2927,8 +2966,9 @@ public virtual void Many_facets_chained_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Name"") .HasMaxLength(100) @@ -2965,8 +3005,9 @@ public virtual void Property_concurrencyToken_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .IsConcurrencyToken() @@ -2995,8 +3036,9 @@ public virtual void Property_column_name_annotation_is_stored_in_snapshot_as_flu { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int"") @@ -3025,8 +3067,9 @@ public virtual void Property_column_type_annotation_is_stored_in_snapshot_as_flu { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""CType""); @@ -3054,8 +3097,9 @@ public virtual void Property_default_value_annotation_is_stored_in_snapshot_as_f { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .ValueGeneratedOnAdd() @@ -3085,8 +3129,9 @@ public virtual void Property_default_value_annotation_is_stored_in_snapshot_as_f { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .ValueGeneratedOnAdd() @@ -3117,8 +3162,9 @@ public virtual void Property_default_value_sql_annotation_is_stored_in_snapshot_ { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .ValueGeneratedOnAdd() @@ -3148,8 +3194,9 @@ public virtual void Property_default_value_sql_annotation_is_stored_in_snapshot_ { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .ValueGeneratedOnAdd() @@ -3179,8 +3226,9 @@ public virtual void Property_computed_column_sql_annotation_is_stored_in_snapsho { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .ValueGeneratedOnAddOrUpdate() @@ -3210,8 +3258,9 @@ public virtual void Property_computed_column_sql_stored_annotation_is_stored_in_ { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .ValueGeneratedOnAddOrUpdate() @@ -3245,8 +3294,9 @@ public virtual void Property_computed_column_sql_annotation_is_stored_in_snapsho { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .ValueGeneratedOnAddOrUpdate() @@ -3272,8 +3322,9 @@ public virtual void Property_default_value_of_enum_type_is_stored_in_snapshot_wi { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Day"") .ValueGeneratedOnAdd() @@ -3306,8 +3357,9 @@ public virtual void Property_enum_type_is_stored_in_snapshot_with_custom_convers { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Day"") .IsRequired() @@ -3347,8 +3399,9 @@ public virtual void Property_of_nullable_enum() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Day"") .HasColumnType(""bigint""); @@ -3373,8 +3426,9 @@ public virtual void Property_of_enum_to_nullable() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Day"") .HasColumnType(""bigint""); @@ -3398,8 +3452,9 @@ public virtual void Property_of_nullable_enum_to_string() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Day"") .HasColumnType(""nvarchar(max)""); @@ -3428,8 +3483,9 @@ public virtual void Property_multiple_annotations_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int"") @@ -3476,8 +3532,9 @@ public virtual void Property_without_column_type() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id""); @@ -3511,10 +3568,9 @@ public virtual void Property_with_identity_column() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:IdentityIncrement"", 1) - .HasAnnotation(""SqlServer:IdentitySeed"", 1L) - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id""); @@ -3551,10 +3607,9 @@ public virtual void Property_with_identity_column_custom_seed() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:IdentityIncrement"", 1) - .HasAnnotation(""SqlServer:IdentitySeed"", 5L) - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 5L, 1); b.HasKey(""Id""); @@ -3591,10 +3646,9 @@ public virtual void Property_with_identity_column_custom_increment() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:IdentityIncrement"", 5) - .HasAnnotation(""SqlServer:IdentitySeed"", 1L) - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 5); b.HasKey(""Id""); @@ -3630,10 +3684,9 @@ public virtual void Property_with_identity_column_custom_seed_increment() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:IdentityIncrement"", 5) - .HasAnnotation(""SqlServer:IdentitySeed"", 5L) - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 5L, 5); b.HasKey(""Id""); @@ -3669,8 +3722,9 @@ public virtual void Key_annotations_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -3702,8 +3756,9 @@ public virtual void Key_name_annotation_is_stored_in_snapshot_as_fluent_api() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -3736,8 +3791,9 @@ public virtual void Key_multiple_annotations_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -3780,8 +3836,9 @@ public virtual void Index_annotations_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -3812,8 +3869,9 @@ public virtual void Index_isUnique_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -3846,8 +3904,9 @@ public virtual void Index_database_name_annotation_is_stored_in_snapshot_as_flue { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -3884,8 +3943,9 @@ public virtual void Index_filter_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -3919,8 +3979,9 @@ public virtual void Index_multiple_annotations_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -3961,8 +4022,9 @@ public virtual void Index_with_default_constraint_name_exceeding_max() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Name"") .HasColumnType(""nvarchar(max)""); @@ -3991,8 +4053,9 @@ public virtual void IndexAttribute_causes_column_to_have_key_or_index_column_len { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""FirstName"") .HasColumnType(""nvarchar(450)""); @@ -4034,8 +4097,9 @@ public virtual void IndexAttribute_name_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""FirstName"") .HasColumnType(""nvarchar(450)""); @@ -4081,8 +4145,9 @@ public virtual void IndexAttribute_IsUnique_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""FirstName"") .HasColumnType(""nvarchar(450)""); @@ -4134,16 +4199,18 @@ public virtual void IndexAttribute_IncludeProperties_generated_without_fluent_ap { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Name"") .HasColumnType(""nvarchar(max)""); b.HasKey(""Id""); - b.HasIndex(""Id"") - .HasAnnotation(""SqlServer:Include"", new[] { ""Name"" }); + b.HasIndex(""Id""); + + SqlServerIndexBuilderExtensions.IncludeProperties(b.HasIndex(""Id""), new[] { ""Name"" }); b.ToTable(""EntityWithStringProperty""); });", usingSystem: true), @@ -4176,8 +4243,9 @@ public virtual void ForeignKey_annotations_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id""); @@ -4188,8 +4256,9 @@ public virtual void ForeignKey_annotations_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -4207,9 +4276,9 @@ public virtual void ForeignKey_annotations_are_stored_in_snapshot() b.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty"", ""EntityWithOneProperty"") .WithOne(""EntityWithTwoProperties"") .HasForeignKey(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithTwoProperties"", ""AlternateId"") - .HasAnnotation(""AnnotationName"", ""AnnotationValue"") .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .IsRequired() + .HasAnnotation(""AnnotationName"", ""AnnotationValue""); b.Navigation(""EntityWithOneProperty""); }); @@ -4252,8 +4321,9 @@ public virtual void ForeignKey_isRequired_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Name"") .IsRequired() @@ -4306,8 +4376,9 @@ public virtual void ForeignKey_isUnique_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Name"") .HasColumnType(""nvarchar(450)""); @@ -4362,8 +4433,9 @@ public virtual void ForeignKey_deleteBehavior_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -4415,8 +4487,9 @@ public virtual void ForeignKey_deleteBehavior_is_stored_in_snapshot_for_one_to_o { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -4476,8 +4549,9 @@ public virtual void ForeignKey_name_preserved_when_generic() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Property"") .HasColumnType(""uniqueidentifier""); @@ -4544,8 +4618,9 @@ public virtual void ForeignKey_constraint_name_is_stored_in_snapshot_as_fluent_a { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id""); @@ -4556,8 +4631,9 @@ public virtual void ForeignKey_constraint_name_is_stored_in_snapshot_as_fluent_a { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -4575,9 +4651,9 @@ public virtual void ForeignKey_constraint_name_is_stored_in_snapshot_as_fluent_a b.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty"", ""EntityWithOneProperty"") .WithOne(""EntityWithTwoProperties"") .HasForeignKey(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithTwoProperties"", ""AlternateId"") - .HasConstraintName(""Constraint"") .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .IsRequired() + .HasConstraintName(""Constraint""); b.Navigation(""EntityWithOneProperty""); }); @@ -4610,8 +4686,9 @@ public virtual void ForeignKey_multiple_annotations_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id""); @@ -4622,8 +4699,9 @@ public virtual void ForeignKey_multiple_annotations_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -4641,10 +4719,10 @@ public virtual void ForeignKey_multiple_annotations_are_stored_in_snapshot() b.HasOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty"", ""EntityWithOneProperty"") .WithOne(""EntityWithTwoProperties"") .HasForeignKey(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithTwoProperties"", ""AlternateId"") - .HasConstraintName(""Constraint"") - .HasAnnotation(""AnnotationName"", ""AnnotationValue"") .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); + .IsRequired() + .HasConstraintName(""Constraint"") + .HasAnnotation(""AnnotationName"", ""AnnotationValue""); b.Navigation(""EntityWithOneProperty""); }); @@ -4679,8 +4757,9 @@ public virtual void Do_not_generate_entity_type_builder_again_if_no_foreign_key_ { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Discriminator"") .IsRequired() @@ -4702,8 +4781,9 @@ public virtual void Do_not_generate_entity_type_builder_again_if_no_foreign_key_ { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id""); @@ -4757,8 +4837,9 @@ public virtual void ForeignKey_principal_key_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -4822,8 +4903,9 @@ public virtual void ForeignKey_principal_key_with_non_default_name_is_stored_in_ { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -4886,8 +4968,9 @@ public virtual void Navigation_annotations_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id""); @@ -4898,8 +4981,9 @@ public virtual void Navigation_annotations_are_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -4953,8 +5037,9 @@ public virtual void Navigation_isRequired_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.HasKey(""Id""); @@ -4965,8 +5050,9 @@ public virtual void Navigation_isRequired_is_stored_in_snapshot() { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -5158,16 +5244,17 @@ partial class Snapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder - .HasAnnotation(""Relational:MaxIdentifierLength"", 128) - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.HasAnnotation(""Relational:MaxIdentifierLength"", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithManyProperties"", b => { b.Property(""Id"") .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .HasColumnType(""int""); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); b.Property(""Boolean"") .HasColumnType(""bit""); @@ -5504,9 +5591,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) protected virtual string GetHeading(bool empty = false) => @" - modelBuilder - .HasAnnotation(""Relational:MaxIdentifierLength"", 128) - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);" + modelBuilder.HasAnnotation(""Relational:MaxIdentifierLength"", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);" + (empty ? null : @" @@ -5564,7 +5651,15 @@ protected void Test(IModel model, string expectedCode, Action as { var generator = CreateMigrationsGenerator(); var code = generator.GenerateSnapshot("RootNamespace", typeof(DbContext), "Snapshot", model); - Assert.Equal(expectedCode, code, ignoreLineEndingDifferences: true); + + try + { + Assert.Equal(expectedCode, code, ignoreLineEndingDifferences: true); + } + catch (EqualException e) + { + throw new Exception(e.Message + Environment.NewLine + Environment.NewLine + "-- Actual code:" + Environment.NewLine + code); + } var modelFromSnapshot = BuildModelFromSnapshotSource(code); assert(modelFromSnapshot, model); diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs index 671fe66146e..b08445d6fb5 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs @@ -2,19 +2,29 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Linq; +using System.Reflection; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Xunit; namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal { public class CSharpDbContextGeneratorTest : ModelCodeGeneratorTestBase { + private static readonly string _nl = Environment.NewLine; + [ConditionalFact] public void Empty_model() { @@ -173,7 +183,7 @@ public void DbSets_without_nrt() }, code => { - Assert.Contains("DbSet Entity { get; set; }" + Environment.NewLine, code.ContextFile.Code); + Assert.Contains("DbSet Entity { get; set; }" + _nl, code.ContextFile.Code); }, null); } @@ -248,7 +258,9 @@ public void Plugins_work() }); Assert.Contains( - @"optionsBuilder.UseSqlServer(""Initial Catalog=TestDatabase"", x => x.SetProviderOption()).SetContextOption();", + @"optionsBuilder" + _nl + + @" .UseSqlServer(""Initial Catalog=TestDatabase"", x => x.SetProviderOption())" + _nl + + @" .SetContextOption();", scaffoldedModel.ContextFile.Code); } @@ -425,7 +437,7 @@ public void ValueGenerated_works() code => { Assert.Contains( - @$"Property(e => e.ValueGeneratedOnAdd){Environment.NewLine} .ValueGeneratedOnAdd()", + @$"Property(e => e.ValueGeneratedOnAdd){_nl} .ValueGeneratedOnAdd()", code.ContextFile.Code); Assert.Contains("Property(e => e.ValueGeneratedOnAddOrUpdate).ValueGeneratedOnAddOrUpdate()", code.ContextFile.Code); Assert.Contains("Property(e => e.ConcurrencyToken).IsConcurrencyToken()", code.ContextFile.Code); @@ -980,13 +992,136 @@ public void Global_namespace_works_just_model() }); } + [ConditionalFact] + public void Fluent_calls_in_custom_namespaces_work() + { + Test( + modelBuilder => CustomTestNamespace.TestModelBuilderExtensions.TestFluentApiCall(modelBuilder), + new ModelCodeGenerationOptions { SuppressOnConfiguring = true }, + code => + { + AssertFileContents( + @"using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using CustomTestNamespace; + +namespace TestNamespace +{ + public partial class TestDbContext : DbContext + { + public TestDbContext() + { + } + + public TestDbContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.TestFluentApiCall(); + + OnModelCreatingPartial(modelBuilder); + } + + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); + } +} +", + code.ContextFile); + + Assert.Empty(code.AdditionalFiles); + }, + model => Assert.Empty(model.GetEntityTypes()), + skipBuild: true); + } + + protected override void AddModelServices(IServiceCollection services) + { + services.Replace(ServiceDescriptor.Singleton()); + } + + protected override void AddScaffoldingServices(IServiceCollection services) + { + services.Replace(ServiceDescriptor.Singleton()); + } + + private class TestModelAnnotationProvider : SqlServerAnnotationProvider + { + public TestModelAnnotationProvider(RelationalAnnotationProviderDependencies dependencies) + : base(dependencies) + { + } + + public override IEnumerable For(IRelationalModel database, bool designTime) + { + foreach (var annotation in base.For(database, designTime)) + { + yield return annotation; + } + + if (database["Test:TestModelAnnotation"] is string annotationValue) + { + yield return new Annotation("Test:TestModelAnnotation", annotationValue); + } + } + } + + private class TestModelAnnotationCodeGenerator : SqlServerAnnotationCodeGenerator + { + private static readonly MethodInfo _testFluentApiCallMethodInfo + = typeof(CustomTestNamespace.TestModelBuilderExtensions).GetRuntimeMethod( + nameof(CustomTestNamespace.TestModelBuilderExtensions.TestFluentApiCall), new[] { typeof(ModelBuilder) })!; + + public TestModelAnnotationCodeGenerator(AnnotationCodeGeneratorDependencies dependencies) + : base(dependencies) + { + } + + protected override MethodCallCodeFragment GenerateFluentApi(IModel model, IAnnotation annotation) + => annotation.Name switch + { + "Test:TestModelAnnotation" => new(_testFluentApiCallMethodInfo), + _ => base.GenerateFluentApi(model, annotation) + }; + } + private class TestCodeGeneratorPlugin : ProviderCodeGeneratorPlugin { + private static readonly MethodInfo _setProviderOptionMethodInfo + = typeof(TestCodeGeneratorPlugin).GetRuntimeMethod( + nameof(SetProviderOption), new[] { typeof(SqlServerDbContextOptionsBuilder) }); + + private static readonly MethodInfo _setContextOptionMethodInfo + = typeof(TestCodeGeneratorPlugin).GetRuntimeMethod( + nameof(SetContextOption), new[] { typeof(DbContextOptionsBuilder) }); + public override MethodCallCodeFragment GenerateProviderOptions() - => new("SetProviderOption"); + => new(_setProviderOptionMethodInfo); public override MethodCallCodeFragment GenerateContextOptions() - => new("SetContextOption"); + => new(_setContextOptionMethodInfo); + + public static SqlServerDbContextOptionsBuilder SetProviderOption(SqlServerDbContextOptionsBuilder optionsBuilder) + => throw new NotSupportedException(); + + public static SqlServerDbContextOptionsBuilder SetContextOption(DbContextOptionsBuilder optionsBuilder) + => throw new NotSupportedException(); + } + } +} + +namespace CustomTestNamespace +{ + internal static class TestModelBuilderExtensions + { + public static ModelBuilder TestFluentApiCall(ModelBuilder modelBuilder) + { + modelBuilder.Model.SetAnnotation("Test:TestModelAnnotation", "foo"); + + return modelBuilder; } } } diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs index 3873e96470e..71480749480 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpEntityTypeGeneratorTest.cs @@ -2023,22 +2023,21 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void AddModelServices(IServiceCollection services) { - services.Replace(ServiceDescriptor.Singleton()); + services.Replace(ServiceDescriptor.Singleton()); } protected override void AddScaffoldingServices(IServiceCollection services) { - services.Replace(ServiceDescriptor.Singleton()); + services.Replace(ServiceDescriptor.Singleton()); } - public class ModelAnnotationProvider : SqlServerAnnotationProvider + private class TestModelAnnotationProvider : SqlServerAnnotationProvider { - public ModelAnnotationProvider(RelationalAnnotationProviderDependencies dependencies) + public TestModelAnnotationProvider(RelationalAnnotationProviderDependencies dependencies) : base(dependencies) { } - /// public override IEnumerable For(ITable table, bool designTime) { foreach (var annotation in base.For(table, designTime)) @@ -2054,7 +2053,6 @@ public override IEnumerable For(ITable table, bool designTime) } } - /// public override IEnumerable For(IColumn column, bool designTime) { foreach (var annotation in base.For(column, designTime)) @@ -2072,9 +2070,9 @@ public override IEnumerable For(IColumn column, bool designTime) } } - public class ModelAnnotationCodeGenerator : SqlServerAnnotationCodeGenerator + private class TestModelAnnotationCodeGenerator : SqlServerAnnotationCodeGenerator { - public ModelAnnotationCodeGenerator(AnnotationCodeGeneratorDependencies dependencies) + public TestModelAnnotationCodeGenerator(AnnotationCodeGeneratorDependencies dependencies) : base(dependencies) { } diff --git a/test/EFCore.Relational.Tests/TestUtilities/TestProviderCodeGenerator.cs b/test/EFCore.Relational.Tests/TestUtilities/TestProviderCodeGenerator.cs index 562601c01af..16c90747fd6 100644 --- a/test/EFCore.Relational.Tests/TestUtilities/TestProviderCodeGenerator.cs +++ b/test/EFCore.Relational.Tests/TestUtilities/TestProviderCodeGenerator.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Reflection; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Scaffolding; @@ -17,9 +19,19 @@ public override MethodCallCodeFragment GenerateUseProvider( string connectionString, MethodCallCodeFragment providerOptions) => new( - "UseTestProvider", + _useTestProviderMethodInfo, providerOptions == null ? new object[] { connectionString } : new object[] { connectionString, new NestedClosureCodeFragment("x", providerOptions) }); + + private static readonly MethodInfo _useTestProviderMethodInfo + = typeof(TestProviderCodeGenerator).GetRequiredRuntimeMethod( + nameof(UseTestProvider), typeof(DbContextOptionsBuilder), typeof(string), typeof(Action)); + + public static void UseTestProvider( + DbContextOptionsBuilder optionsBuilder, + string connectionString, + Action optionsAction = null) + => throw new NotSupportedException(); } } diff --git a/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs b/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs index 121ab0501e9..2e048bee9dd 100644 --- a/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs +++ b/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs @@ -195,6 +195,25 @@ public void GenerateFluentApi_IProperty_works_with_identity() increment => Assert.Equal(10, increment)); } + [ConditionalFact] + public void GenerateFluentApi_IProperty_works_with_identity_default_seed_increment() + { + var generator = CreateGenerator(); + var modelBuilder = SqlServerConventionSetBuilder.CreateModelBuilder(); + modelBuilder.Entity("Post", x => x.Property("Id").UseIdentityColumn()); + var property = modelBuilder.Model.FindEntityType("Post").FindProperty("Id"); + + var annotations = property.GetAnnotations().ToDictionary(a => a.Name, a => a); + var result = generator.GenerateFluentApiCalls((IProperty)property, annotations).Single(); + + Assert.Equal("UseIdentityColumn", result.Method); + + Assert.Collection( + result.Arguments, + seed => Assert.Equal(1L, seed), + increment => Assert.Equal(1, increment)); + } + [ConditionalFact] public void GenerateFluentApi_IModel_works_with_HiLo() { @@ -262,6 +281,48 @@ MethodCallCodeFragment GenerateFluentApiCall(string entityTypeName, string prope } } + [ConditionalFact] + public void GenerateFluentApi_IModel_works_with_DatabaseMaxSize() + { + var generator = CreateGenerator(); + var modelBuilder = SqlServerConventionSetBuilder.CreateModelBuilder(); + modelBuilder.HasDatabaseMaxSize("100"); + + var annotations = modelBuilder.Model.GetAnnotations().ToDictionary(a => a.Name, a => a); + var result = generator.GenerateFluentApiCalls((IModel)modelBuilder.Model, annotations) + .Single(c => c.Method == nameof(SqlServerModelBuilderExtensions.HasDatabaseMaxSize)); + + Assert.Equal("100", Assert.Single(result.Arguments)); + } + + [ConditionalFact] + public void GenerateFluentApi_IModel_works_with_ServiceTier() + { + var generator = CreateGenerator(); + var modelBuilder = SqlServerConventionSetBuilder.CreateModelBuilder(); + modelBuilder.HasServiceTier("foo"); + + var annotations = modelBuilder.Model.GetAnnotations().ToDictionary(a => a.Name, a => a); + var result = generator.GenerateFluentApiCalls((IModel)modelBuilder.Model, annotations) + .Single(c => c.Method == nameof(SqlServerModelBuilderExtensions.HasServiceTierSql)); + + Assert.Equal("'foo'", Assert.Single(result.Arguments)); + } + + [ConditionalFact] + public void GenerateFluentApi_IModel_works_with_PerformanceLevel() + { + var generator = CreateGenerator(); + var modelBuilder = SqlServerConventionSetBuilder.CreateModelBuilder(); + modelBuilder.HasPerformanceLevel("foo"); + + var annotations = modelBuilder.Model.GetAnnotations().ToDictionary(a => a.Name, a => a); + var result = generator.GenerateFluentApiCalls((IModel)modelBuilder.Model, annotations) + .Single(c => c.Method == nameof(SqlServerModelBuilderExtensions.HasPerformanceLevelSql)); + + Assert.Equal("'foo'", Assert.Single(result.Arguments)); + } + private SqlServerAnnotationCodeGenerator CreateGenerator() => new( new AnnotationCodeGeneratorDependencies( diff --git a/test/EFCore.SqlServer.Tests/Scaffolding/SqlServerCodeGeneratorTest.cs b/test/EFCore.SqlServer.Tests/Scaffolding/SqlServerCodeGeneratorTest.cs index 6d157069189..d0f21d8c079 100644 --- a/test/EFCore.SqlServer.Tests/Scaffolding/SqlServerCodeGeneratorTest.cs +++ b/test/EFCore.SqlServer.Tests/Scaffolding/SqlServerCodeGeneratorTest.cs @@ -1,8 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Linq; +using System.Reflection; using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.SqlServer.Scaffolding.Internal; using Xunit; @@ -34,7 +37,7 @@ public virtual void Use_provider_method_is_generated_correctly_with_options() new ProviderCodeGeneratorDependencies( Enumerable.Empty())); - var providerOptions = new MethodCallCodeFragment("SetProviderOption"); + var providerOptions = new MethodCallCodeFragment(_setProviderOptionMethodInfo); var result = codeGenerator.GenerateUseProvider("Data Source=Test", providerOptions); @@ -51,5 +54,11 @@ public virtual void Use_provider_method_is_generated_correctly_with_options() }); Assert.Null(result.ChainedCall); } + + private static readonly MethodInfo _setProviderOptionMethodInfo + = typeof(SqlServerCodeGeneratorTest).GetRuntimeMethod(nameof(SetProviderOption), new[] { typeof(DbContextOptionsBuilder) }); + + public static SqlServerDbContextOptionsBuilder SetProviderOption(DbContextOptionsBuilder optionsBuilder) + => throw new NotSupportedException(); } } diff --git a/test/EFCore.Sqlite.Tests/Scaffolding/SqliteCodeGeneratorTest.cs b/test/EFCore.Sqlite.Tests/Scaffolding/SqliteCodeGeneratorTest.cs index f5571cb2603..1e8f8a3bc91 100644 --- a/test/EFCore.Sqlite.Tests/Scaffolding/SqliteCodeGeneratorTest.cs +++ b/test/EFCore.Sqlite.Tests/Scaffolding/SqliteCodeGeneratorTest.cs @@ -1,8 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Linq; +using System.Reflection; using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Sqlite.Scaffolding.Internal; using Xunit; @@ -33,7 +36,7 @@ public virtual void Use_provider_method_is_generated_correctly_with_options() new ProviderCodeGeneratorDependencies( Enumerable.Empty())); - var providerOptions = new MethodCallCodeFragment("SetProviderOption"); + var providerOptions = new MethodCallCodeFragment(_setProviderOptionMethodInfo); var result = codeGenerator.GenerateUseProvider("Data Source=Test", providerOptions); @@ -50,5 +53,11 @@ public virtual void Use_provider_method_is_generated_correctly_with_options() }); Assert.Null(result.ChainedCall); } + + private static readonly MethodInfo _setProviderOptionMethodInfo + = typeof(SqliteCodeGeneratorTest).GetRuntimeMethod(nameof(SetProviderOption), new[] { typeof(DbContextOptionsBuilder) }); + + public static SqliteDbContextOptionsBuilder SetProviderOption(DbContextOptionsBuilder optionsBuilder) + => throw new NotSupportedException(); } } diff --git a/test/EFCore.Tests/Design/MethodCallCodeFragmentTest.cs b/test/EFCore.Tests/Design/MethodCallCodeFragmentTest.cs new file mode 100644 index 00000000000..0c75e45a9e6 --- /dev/null +++ b/test/EFCore.Tests/Design/MethodCallCodeFragmentTest.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Reflection; +using Xunit; + +namespace Microsoft.EntityFrameworkCore.Design +{ + public class MethodCallCodeFragmentTest + { + [ConditionalFact] + public virtual void Ctor_throw_when_too_many_parameters_extension() + { + _ = new MethodCallCodeFragment(_extensionFuncMethodInfo, 1); + Assert.Throws(() => new MethodCallCodeFragment(_extensionFuncMethodInfo, 1, 2)); + } + + [ConditionalFact] + public virtual void Ctor_throw_when_too_many_parameters_instance() + { + _ = new MethodCallCodeFragment(_instanceFuncMethodInfo, 1); + Assert.Throws(() => new MethodCallCodeFragment(_instanceFuncMethodInfo, 1, 2)); + } + + private static readonly MethodInfo _extensionFuncMethodInfo + = typeof(MethodCallCodeFragmentTest).GetRequiredRuntimeMethod(nameof(ExtensionFunc), typeof(object), typeof(int)); + + private static readonly MethodInfo _instanceFuncMethodInfo + = typeof(MethodCallCodeFragmentTest).GetRequiredRuntimeMethod(nameof(InstanceFunc), typeof(int)); + + public static void ExtensionFunc(object thisParameter, int p) + => throw new NotSupportedException(); + + public void InstanceFunc(int p) + => throw new NotSupportedException(); + } +}