diff --git a/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs b/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs index cc677a97990..2bb6c074b1d 100644 --- a/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs +++ b/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs @@ -74,6 +74,7 @@ public static IServiceCollection AddEntityFrameworkDesignTimeServices( .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton( new DesignTimeConnectionStringResolver(applicationServiceProviderAccessor)) .AddSingleton(reporter) diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index 11fb0a3e56e..e890e108e1d 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -5,13 +5,11 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Scaffolding.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Utilities; @@ -53,17 +51,9 @@ public virtual void Generate(string builderName, IModel model, IndentedStringBui Check.NotNull(model, nameof(model)); Check.NotNull(stringBuilder, nameof(stringBuilder)); - var annotations = model.GetAnnotations().ToList(); + var annotations = model.GetAnnotations().ToDictionary(a => a.Name, a => a); - IgnoreAnnotations( - annotations, - ChangeDetector.SkipDetectChangesAnnotation, - CoreAnnotationNames.ChangeTrackingStrategy, - CoreAnnotationNames.OwnedTypes, - RelationalAnnotationNames.RelationalModel, - RelationalAnnotationNames.CheckConstraints, - RelationalAnnotationNames.Sequences, - RelationalAnnotationNames.DbFunctions); + Dependencies.AnnotationCodeGenerator.RemoveIgnoredAnnotations(annotations); if (annotations.Count > 0) { @@ -71,11 +61,15 @@ public virtual void Generate(string builderName, IModel model, IndentedStringBui using (stringBuilder.Indent()) { - GenerateFluentApiForAnnotation( - ref annotations, RelationalAnnotationNames.DefaultSchema, nameof(RelationalModelBuilderExtensions.HasDefaultSchema), - stringBuilder); + foreach (var methodCallCodeFragment in + Dependencies.AnnotationCodeGenerator.GenerateFluentApiCalls(model, annotations)) + { + stringBuilder + .AppendLine() + .Append(Code.Fragment(methodCallCodeFragment)); + } - GenerateAnnotations(annotations, stringBuilder); + GenerateRawAnnotations(annotations.Values, stringBuilder); } stringBuilder.AppendLine(";"); @@ -522,19 +516,9 @@ protected virtual void GeneratePropertyAnnotations([NotNull] IProperty property, Check.NotNull(property, nameof(property)); Check.NotNull(stringBuilder, nameof(stringBuilder)); - var annotations = property.GetAnnotations().ToList(); - - GenerateFluentApiForAnnotation( - ref annotations, - RelationalAnnotationNames.ColumnName, - nameof(RelationalPropertyBuilderExtensions.HasColumnName), - stringBuilder); + var annotations = property.GetAnnotations().ToDictionary(a => a.Name, a => a); - GenerateFluentApiForAnnotation( - ref annotations, - RelationalAnnotationNames.ViewColumnName, - nameof(RelationalPropertyBuilderExtensions.HasViewColumnName), - stringBuilder); + Dependencies.AnnotationCodeGenerator.RemoveIgnoredAnnotations(annotations); stringBuilder .AppendLine() @@ -544,79 +528,19 @@ protected virtual void GeneratePropertyAnnotations([NotNull] IProperty property, .Append( Code.Literal( property.GetColumnType() - ?? Dependencies.RelationalTypeMappingSource.GetMapping(property).StoreType)) + ?? Dependencies.RelationalTypeMappingSource.GetMapping(property).StoreType)) .Append(")"); + annotations.Remove(RelationalAnnotationNames.ColumnType); + + foreach (var methodCallCodeFragment in + Dependencies.AnnotationCodeGenerator.GenerateFluentApiCalls(property, annotations)) + { + stringBuilder + .AppendLine() + .Append(Code.Fragment(methodCallCodeFragment)); + } - GenerateFluentApiForAnnotation( - ref annotations, - RelationalAnnotationNames.DefaultValueSql, - nameof(RelationalPropertyBuilderExtensions.HasDefaultValueSql), - stringBuilder); - - GenerateFluentApiForComputedColumn(ref annotations, stringBuilder); - - GenerateFluentApiForAnnotation( - ref annotations, - RelationalAnnotationNames.IsFixedLength, - nameof(RelationalPropertyBuilderExtensions.IsFixedLength), - stringBuilder); - - GenerateFluentApiForAnnotation( - ref annotations, - RelationalAnnotationNames.Comment, - nameof(RelationalPropertyBuilderExtensions.HasComment), - stringBuilder); - - GenerateFluentApiForAnnotation( - ref annotations, - RelationalAnnotationNames.Collation, - nameof(RelationalPropertyBuilderExtensions.UseCollation), - stringBuilder); - - GenerateFluentApiForAnnotation( - ref annotations, - CoreAnnotationNames.MaxLength, - nameof(PropertyBuilder.HasMaxLength), - stringBuilder); - - GenerateFluentApiForPrecisionAndScale(ref annotations, stringBuilder); - - GenerateFluentApiForAnnotation( - ref annotations, - CoreAnnotationNames.Unicode, - nameof(PropertyBuilder.IsUnicode), - stringBuilder); - - var valueConverter = FindValueConverter(property); - - GenerateFluentApiForAnnotation( - ref annotations, - RelationalAnnotationNames.DefaultValue, - a => valueConverter == null ? a?.Value : valueConverter.ConvertToProvider(a?.Value), - nameof(RelationalPropertyBuilderExtensions.HasDefaultValue), - stringBuilder); - - IgnoreAnnotations( - annotations, - RelationalAnnotationNames.ColumnType, - RelationalAnnotationNames.TableColumnMappings, - RelationalAnnotationNames.ViewColumnMappings, - RelationalAnnotationNames.RelationalOverrides, - CoreAnnotationNames.ValueGeneratorFactory, - CoreAnnotationNames.PropertyAccessMode, - CoreAnnotationNames.ChangeTrackingStrategy, - CoreAnnotationNames.BeforeSaveBehavior, - CoreAnnotationNames.AfterSaveBehavior, - CoreAnnotationNames.TypeMapping, - CoreAnnotationNames.ValueComparer, -#pragma warning disable 618 - CoreAnnotationNames.KeyValueComparer, - CoreAnnotationNames.StructuralValueComparer, -#pragma warning restore 618 - CoreAnnotationNames.ValueConverter, - CoreAnnotationNames.ProviderClrType); - - GenerateAnnotations(annotations, stringBuilder); + GenerateRawAnnotations(annotations.Values, stringBuilder); } private ValueConverter FindValueConverter(IProperty property) @@ -697,16 +621,19 @@ protected virtual void GenerateKey( /// The builder code is added to. protected virtual void GenerateKeyAnnotations([NotNull] IKey key, [NotNull] IndentedStringBuilder stringBuilder) { - var annotations = key.GetAnnotations().ToList(); + var annotations = key.GetAnnotations().ToDictionary(a => a.Name, a => a); - IgnoreAnnotations( - annotations, - RelationalAnnotationNames.UniqueConstraintMappings); + Dependencies.AnnotationCodeGenerator.RemoveIgnoredAnnotations(annotations); - GenerateFluentApiForAnnotation( - ref annotations, RelationalAnnotationNames.Name, nameof(RelationalKeyBuilderExtensions.HasName), stringBuilder); + foreach (var methodCallCodeFragment in + Dependencies.AnnotationCodeGenerator.GenerateFluentApiCalls(key, annotations)) + { + stringBuilder + .AppendLine() + .Append(Code.Fragment(methodCallCodeFragment)); + } - GenerateAnnotations(annotations, stringBuilder); + GenerateRawAnnotations(annotations.Values, stringBuilder); } /// @@ -791,18 +718,19 @@ protected virtual void GenerateIndex( protected virtual void GenerateIndexAnnotations( [NotNull] IIndex index, [NotNull] IndentedStringBuilder stringBuilder) { - var annotations = index.GetAnnotations().ToList(); + var annotations = index.GetAnnotations().ToDictionary(a => a.Name, a => a); - IgnoreAnnotations( - annotations, - CSharpModelGenerator.IgnoredIndexAnnotations); + Dependencies.AnnotationCodeGenerator.RemoveIgnoredAnnotations(annotations); - GenerateFluentApiForAnnotation( - ref annotations, RelationalAnnotationNames.Name, nameof(RelationalIndexBuilderExtensions.HasDatabaseName), stringBuilder); - GenerateFluentApiForAnnotation( - ref annotations, RelationalAnnotationNames.Filter, nameof(RelationalIndexBuilderExtensions.HasFilter), stringBuilder); + foreach (var methodCallCodeFragment in + Dependencies.AnnotationCodeGenerator.GenerateFluentApiCalls(index, annotations)) + { + stringBuilder + .AppendLine() + .Append(Code.Fragment(methodCallCodeFragment)); + } - GenerateAnnotations(annotations, stringBuilder); + GenerateRawAnnotations(annotations.Values, stringBuilder); } /// @@ -820,9 +748,12 @@ protected virtual void GenerateEntityTypeAnnotations( Check.NotNull(entityType, nameof(entityType)); Check.NotNull(stringBuilder, nameof(stringBuilder)); - var annotations = entityType.GetAnnotations().ToList(); - var tableNameAnnotation = annotations.FirstOrDefault(a => a.Name == RelationalAnnotationNames.TableName); - var schemaAnnotation = annotations.FirstOrDefault(a => a.Name == RelationalAnnotationNames.Schema); + var annotations = entityType.GetAnnotations().ToDictionary(a => a.Name, a => a); + + Dependencies.AnnotationCodeGenerator.RemoveIgnoredAnnotations(annotations); + + var tableNameAnnotation = annotations.Find(RelationalAnnotationNames.TableName); + var schemaAnnotation = annotations.Find(RelationalAnnotationNames.Schema); var nonDefaultName = false; if (tableNameAnnotation?.Value != null @@ -835,7 +766,10 @@ protected virtual void GenerateEntityTypeAnnotations( .Append(nameof(RelationalEntityTypeBuilderExtensions.ToTable)) .Append("(") .Append(Code.Literal((string)tableNameAnnotation?.Value ?? entityType.GetTableName())); - annotations.Remove(tableNameAnnotation); + if (tableNameAnnotation != null) + { + annotations.Remove(tableNameAnnotation.Name); + } nonDefaultName = true; } @@ -844,7 +778,7 @@ protected virtual void GenerateEntityTypeAnnotations( stringBuilder .Append(",") .Append(Code.Literal((string)schemaAnnotation.Value)); - annotations.Remove(schemaAnnotation); + annotations.Remove(schemaAnnotation.Name); nonDefaultName = true; } @@ -853,9 +787,9 @@ protected virtual void GenerateEntityTypeAnnotations( stringBuilder.AppendLine(");"); } - var discriminatorPropertyAnnotation = annotations.FirstOrDefault(a => a.Name == CoreAnnotationNames.DiscriminatorProperty); - var discriminatorMappingCompleteAnnotation = annotations.FirstOrDefault(a => a.Name == CoreAnnotationNames.DiscriminatorMappingComplete); - var discriminatorValueAnnotation = annotations.FirstOrDefault(a => a.Name == CoreAnnotationNames.DiscriminatorValue); + var discriminatorPropertyAnnotation = annotations.Find(CoreAnnotationNames.DiscriminatorProperty); + var discriminatorMappingCompleteAnnotation = annotations.Find(CoreAnnotationNames.DiscriminatorMappingComplete); + var discriminatorValueAnnotation = annotations.Find(CoreAnnotationNames.DiscriminatorValue); if ((discriminatorPropertyAnnotation?.Value ?? discriminatorMappingCompleteAnnotation?.Value @@ -921,56 +855,37 @@ protected virtual void GenerateEntityTypeAnnotations( stringBuilder.AppendLine(";"); - annotations.Remove(discriminatorPropertyAnnotation); - annotations.Remove(discriminatorMappingCompleteAnnotation); - annotations.Remove(discriminatorValueAnnotation); + if (discriminatorPropertyAnnotation != null) + { + annotations.Remove(discriminatorPropertyAnnotation.Name); + } + if (discriminatorMappingCompleteAnnotation != null) + { + annotations.Remove(discriminatorMappingCompleteAnnotation.Name); + } + if (discriminatorValueAnnotation != null) + { + annotations.Remove(discriminatorValueAnnotation.Name); + } } - var commentAnnotation = annotations.FirstOrDefault(a => a.Name == RelationalAnnotationNames.Comment); - - if (commentAnnotation != null) + var fluentApiCalls = Dependencies.AnnotationCodeGenerator.GenerateFluentApiCalls(entityType, annotations); + if (fluentApiCalls.Count > 0 || annotations.Count > 0) { stringBuilder .AppendLine() - .Append(builderName) - .Append(".") - .Append(nameof(RelationalPropertyBuilderExtensions.HasComment)) - .Append("(") - .Append(Code.UnknownLiteral(commentAnnotation.Value)) - .AppendLine(");"); + .Append(builderName); - annotations.Remove(commentAnnotation); - } - - IgnoreAnnotations( - annotations, - CoreAnnotationNames.NavigationCandidates, - CoreAnnotationNames.AmbiguousNavigations, - CoreAnnotationNames.InverseNavigations, - CoreAnnotationNames.NavigationAccessMode, - CoreAnnotationNames.PropertyAccessMode, - CoreAnnotationNames.ChangeTrackingStrategy, - CoreAnnotationNames.ConstructorBinding, - CoreAnnotationNames.DefiningQuery, - CoreAnnotationNames.QueryFilter, - RelationalAnnotationNames.CheckConstraints, - RelationalAnnotationNames.TableMappings, - RelationalAnnotationNames.ViewMappings); - - if (annotations.Count > 0) - { - foreach (var annotation in annotations) + using (stringBuilder.Indent()) { - if (annotation.Value == null) + foreach (var methodCallCodeFragment in fluentApiCalls) { - continue; + stringBuilder + .AppendLine() + .Append(Code.Fragment(methodCallCodeFragment)); } - stringBuilder - .AppendLine() - .Append(builderName); - - GenerateAnnotation(annotation, stringBuilder); + GenerateRawAnnotations(annotations.Values, stringBuilder); stringBuilder .AppendLine(";"); @@ -1197,79 +1112,19 @@ protected virtual void GenerateForeignKeyAnnotations( Check.NotNull(foreignKey, nameof(foreignKey)); Check.NotNull(stringBuilder, nameof(stringBuilder)); - var annotations = foreignKey.GetAnnotations().ToList(); - - IgnoreAnnotations( - annotations, - RelationalAnnotationNames.ForeignKeyMappings); + var annotations = foreignKey.GetAnnotations().ToDictionary(a => a.Name, a => a); - GenerateFluentApiForAnnotation( - ref annotations, - RelationalAnnotationNames.Name, - "HasConstraintName", - stringBuilder); + Dependencies.AnnotationCodeGenerator.RemoveIgnoredAnnotations(annotations); - GenerateAnnotations(annotations, stringBuilder); - } - - /// - /// Removes ignored annotations. - /// - /// The annotations to remove from. - /// The ignored annotation names. - protected virtual void IgnoreAnnotations( - [NotNull] IList annotations, [NotNull] params string[] annotationNames) - { - Check.NotNull(annotations, nameof(annotations)); - Check.NotNull(annotationNames, nameof(annotationNames)); - - foreach (var annotationName in annotationNames) + foreach (var methodCallCodeFragment in + Dependencies.AnnotationCodeGenerator.GenerateFluentApiCalls(foreignKey, annotations)) { - var annotation = annotations.FirstOrDefault(a => a.Name == annotationName); - if (annotation != null) - { - annotations.Remove(annotation); - } - } - } - - /// - /// Removes ignored annotations. - /// - /// The annotations to remove from. - /// The ignored annotation names. - protected virtual void IgnoreAnnotations( - [NotNull] IList annotations, [NotNull] IReadOnlyList annotationNames) - { - Check.NotNull(annotations, nameof(annotations)); - Check.NotNull(annotationNames, nameof(annotationNames)); - - foreach (var annotationName in annotationNames) - { - var annotation = annotations.FirstOrDefault(a => a.Name == annotationName); - if (annotation != null) - { - annotations.Remove(annotation); - } + stringBuilder + .AppendLine() + .Append(Code.Fragment(methodCallCodeFragment)); } - } - /// - /// Removes ignored annotations. - /// - /// The annotations to remove from. - /// The ignored annotation prefixes. - protected virtual void IgnoreAnnotationTypes( - [NotNull] IList annotations, [NotNull] params string[] annotationPrefixes) - { - Check.NotNull(annotations, nameof(annotations)); - Check.NotNull(annotationPrefixes, nameof(annotationPrefixes)); - - foreach (var ignoreAnnotation in annotations - .Where(a => annotationPrefixes.Any(pre => a.Name.StartsWith(pre, StringComparison.OrdinalIgnoreCase))).ToList()) - { - annotations.Remove(ignoreAnnotation); - } + GenerateRawAnnotations(annotations.Values, stringBuilder); } /// @@ -1277,166 +1132,25 @@ protected virtual void IgnoreAnnotationTypes( /// /// The annotations. /// The builder code is added to. - protected virtual void GenerateAnnotations( - [NotNull] IReadOnlyList annotations, [NotNull] IndentedStringBuilder stringBuilder) + protected virtual void GenerateRawAnnotations( + [NotNull] IEnumerable annotations, [NotNull] IndentedStringBuilder stringBuilder) { Check.NotNull(annotations, nameof(annotations)); Check.NotNull(stringBuilder, nameof(stringBuilder)); foreach (var annotation in annotations) { - if (annotation.Value == null) - { - continue; - } - stringBuilder.AppendLine(); - GenerateAnnotation(annotation, stringBuilder); - } - } - - /// - /// Generates a Fluent API calls for an annotation. - /// - /// The list of annotations. - /// The name of the annotation to generate code for. - /// The Fluent API method name. - /// The builder code is added to. - protected virtual void GenerateFluentApiForAnnotation( - [NotNull] ref List annotations, - [NotNull] string annotationName, - [NotNull] string fluentApiMethodName, - [NotNull] IndentedStringBuilder stringBuilder) - => GenerateFluentApiForAnnotation( - ref annotations, - annotationName, - a => a?.Value, - fluentApiMethodName, - stringBuilder); - - /// - /// Generates a Fluent API calls for an annotation. - /// - /// The list of annotations. - /// The name of the annotation to generate code for. - /// A delegate to generate the value from the annotation. - /// The Fluent API method name. - /// The builder code is added to. - protected virtual void GenerateFluentApiForAnnotation( - [NotNull] ref List annotations, - [NotNull] string annotationName, - [CanBeNull] Func annotationValueFunc, - [NotNull] string fluentApiMethodName, - [NotNull] IndentedStringBuilder stringBuilder) - { - var annotation = annotations.FirstOrDefault(a => a.Name == annotationName); - var annotationValue = annotationValueFunc?.Invoke(annotation); - - if (annotationValue != null) - { - stringBuilder - .AppendLine() - .Append(".") - .Append(fluentApiMethodName) - .Append("(") - .Append(Code.UnknownLiteral(annotationValue)) - .Append(")"); - - annotations.Remove(annotation); - } - } - - /// - /// Generates a Fluent API call for the Precision and Scale annotations. - /// - /// The list of annotations. - /// The builder code is added to. - protected virtual void GenerateFluentApiForPrecisionAndScale( - [NotNull] ref List annotations, - [NotNull] IndentedStringBuilder stringBuilder) - { - var precisionAnnotation = annotations - .FirstOrDefault(a => a.Name == CoreAnnotationNames.Precision); - var precisionValue = precisionAnnotation?.Value; - - if (precisionValue != null) - { - stringBuilder - .AppendLine() - .Append(".") - .Append(nameof(PropertyBuilder.HasPrecision)) - .Append("(") - .Append(Code.UnknownLiteral(precisionValue)); - - var scaleAnnotation = annotations - .FirstOrDefault(a => a.Name == CoreAnnotationNames.Scale); - var scaleValue = (int?)scaleAnnotation?.Value; - - if (scaleValue != null) - { - if (scaleValue != 0) - { - stringBuilder - .Append(", ") - .Append(Code.UnknownLiteral(scaleValue)); - } - - annotations.Remove(scaleAnnotation); - } - - stringBuilder.Append(")"); - - annotations.Remove(precisionAnnotation); + GenerateRawAnnotation(annotation, stringBuilder); } } /// - /// Generates a Fluent API call for the computed column annotations. - /// - /// The list of annotations. - /// The builder code is added to. - protected virtual void GenerateFluentApiForComputedColumn( - [NotNull] ref List annotations, - [NotNull] IndentedStringBuilder stringBuilder) - { - var sql = annotations - .FirstOrDefault(a => a.Name == RelationalAnnotationNames.ComputedColumnSql); - - if (sql is null) - { - return; - } - - stringBuilder - .AppendLine() - .Append(".") - .Append(nameof(RelationalPropertyBuilderExtensions.HasComputedColumnSql)) - .Append("(") - .Append(Code.UnknownLiteral(sql.Value)); - - var isStored = annotations - .FirstOrDefault(a => a.Name == RelationalAnnotationNames.ComputedColumnIsStored); - - if (isStored != null) - { - stringBuilder - .Append(", ") - .Append(Code.UnknownLiteral(isStored.Value)); - - annotations.Remove(isStored); - } - - stringBuilder.Append(")"); - - annotations.Remove(sql); - } - - /// - /// Generates code for an annotation. + /// 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( + protected virtual void GenerateRawAnnotation( [NotNull] IAnnotation annotation, [NotNull] IndentedStringBuilder stringBuilder) { Check.NotNull(annotation, nameof(annotation)); diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGeneratorDependencies.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGeneratorDependencies.cs index 525217516c6..1b1a71caff5 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGeneratorDependencies.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGeneratorDependencies.cs @@ -50,12 +50,14 @@ public sealed class CSharpSnapshotGeneratorDependencies [EntityFrameworkInternal] public CSharpSnapshotGeneratorDependencies( [NotNull] ICSharpHelper csharpHelper, - [NotNull] IRelationalTypeMappingSource relationalTypeMappingSource) + [NotNull] IRelationalTypeMappingSource relationalTypeMappingSource, + [NotNull] IAnnotationCodeGenerator annotationCodeGenerator) { Check.NotNull(csharpHelper, nameof(csharpHelper)); CSharpHelper = csharpHelper; RelationalTypeMappingSource = relationalTypeMappingSource; + AnnotationCodeGenerator = annotationCodeGenerator; } /// @@ -68,13 +70,18 @@ public CSharpSnapshotGeneratorDependencies( /// public IRelationalTypeMappingSource RelationalTypeMappingSource { get; } + /// + /// The annotation code generator. + /// + public IAnnotationCodeGenerator AnnotationCodeGenerator { get; } + /// /// Clones this dependency parameter object with one service replaced. /// /// A replacement for the current dependency of this type. /// A new parameter object with the given service replaced. public CSharpSnapshotGeneratorDependencies With([NotNull] ICSharpHelper csharpHelper) - => new CSharpSnapshotGeneratorDependencies(csharpHelper, RelationalTypeMappingSource); + => new CSharpSnapshotGeneratorDependencies(csharpHelper, RelationalTypeMappingSource, AnnotationCodeGenerator); /// /// Clones this dependency parameter object with one service replaced. @@ -82,6 +89,14 @@ public CSharpSnapshotGeneratorDependencies With([NotNull] ICSharpHelper csharpHe /// A replacement for the current dependency of this type. /// A new parameter object with the given service replaced. public CSharpSnapshotGeneratorDependencies With([NotNull] IRelationalTypeMappingSource relationalTypeMappingSource) - => new CSharpSnapshotGeneratorDependencies(CSharpHelper, relationalTypeMappingSource); + => new CSharpSnapshotGeneratorDependencies(CSharpHelper, relationalTypeMappingSource, AnnotationCodeGenerator); + + /// + /// Clones this dependency parameter object with one service replaced. + /// + /// A replacement for the current dependency of this type. + /// A new parameter object with the given service replaced. + public CSharpSnapshotGeneratorDependencies With([NotNull] IAnnotationCodeGenerator annotationCodeGenerator) + => new CSharpSnapshotGeneratorDependencies(CSharpHelper, RelationalTypeMappingSource, annotationCodeGenerator); } } diff --git a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs index cc9685b26fd..862c36a4561 100644 --- a/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/MigrationsCodeGenerator.cs @@ -2,12 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations.Operations; @@ -216,53 +216,14 @@ private static IEnumerable GetAnnotatables(IModel model) } private IEnumerable GetAnnotationNamespaces(IEnumerable items) - { - var ignoredAnnotations = new List - { - CoreAnnotationNames.NavigationCandidates, - CoreAnnotationNames.AmbiguousNavigations, - CoreAnnotationNames.InverseNavigations, - ChangeDetector.SkipDetectChangesAnnotation, - CoreAnnotationNames.OwnedTypes, - CoreAnnotationNames.ChangeTrackingStrategy, - CoreAnnotationNames.BeforeSaveBehavior, - CoreAnnotationNames.AfterSaveBehavior, - CoreAnnotationNames.TypeMapping, - CoreAnnotationNames.ValueComparer, -#pragma warning disable 618 - CoreAnnotationNames.KeyValueComparer, - CoreAnnotationNames.StructuralValueComparer, -#pragma warning restore 618 - CoreAnnotationNames.ConstructorBinding, - CoreAnnotationNames.NavigationAccessMode, - CoreAnnotationNames.PropertyAccessMode, - CoreAnnotationNames.ProviderClrType, - CoreAnnotationNames.ValueConverter, - CoreAnnotationNames.ValueGeneratorFactory, - CoreAnnotationNames.DefiningQuery, - CoreAnnotationNames.QueryFilter, - RelationalAnnotationNames.RelationalModel, - RelationalAnnotationNames.CheckConstraints, - RelationalAnnotationNames.Sequences, - RelationalAnnotationNames.DbFunctions, - RelationalAnnotationNames.TableMappings, - RelationalAnnotationNames.TableColumnMappings, - RelationalAnnotationNames.ViewMappings, - RelationalAnnotationNames.ViewColumnMappings, - RelationalAnnotationNames.ForeignKeyMappings, - RelationalAnnotationNames.TableIndexMappings, - RelationalAnnotationNames.UniqueConstraintMappings, - RelationalAnnotationNames.RelationalOverrides - }; - - return items.SelectMany( - i => i.GetAnnotations().Select( - a => new { Annotatable = i, Annotation = a }) - .Where( - a => a.Annotation.Value != null - && !ignoredAnnotations.Contains(a.Annotation.Name)) - .SelectMany(a => GetProviderType(a.Annotatable, a.Annotation.Value.GetType()).GetNamespaces())); - } + => items.SelectMany( + i => + { + var annotations = i.GetAnnotations().ToDictionary(a => a.Name, a => a); + Dependencies.AnnotationCodeGenerator.RemoveIgnoredAnnotations(annotations); + return annotations.Values.Select(a => new { Annotatable = i, Annotation = a }); + }) + .SelectMany(a => GetProviderType(a.Annotatable, a.Annotation.Value.GetType()).GetNamespaces()); private ValueConverter FindValueConverter(IProperty property) => (property.FindTypeMapping() @@ -270,8 +231,8 @@ private ValueConverter FindValueConverter(IProperty property) private Type GetProviderType(IAnnotatable annotatable, Type valueType) => annotatable is IProperty property - && valueType.UnwrapNullableType() == property.ClrType.UnwrapNullableType() - ? FindValueConverter(property)?.ProviderClrType ?? valueType - : valueType; + && valueType.UnwrapNullableType() == property.ClrType.UnwrapNullableType() + ? FindValueConverter(property)?.ProviderClrType ?? valueType + : valueType; } } diff --git a/src/EFCore.Design/Migrations/Design/MigrationsCodeGeneratorDependencies.cs b/src/EFCore.Design/Migrations/Design/MigrationsCodeGeneratorDependencies.cs index 2f9d76a83b5..74ee7f6f284 100644 --- a/src/EFCore.Design/Migrations/Design/MigrationsCodeGeneratorDependencies.cs +++ b/src/EFCore.Design/Migrations/Design/MigrationsCodeGeneratorDependencies.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; @@ -46,9 +47,12 @@ public sealed class MigrationsCodeGeneratorDependencies /// /// [EntityFrameworkInternal] - public MigrationsCodeGeneratorDependencies([NotNull] IRelationalTypeMappingSource relationalTypeMappingSource) + public MigrationsCodeGeneratorDependencies( + [NotNull] IRelationalTypeMappingSource relationalTypeMappingSource, + [NotNull] IAnnotationCodeGenerator annotationCodeGenerator) { RelationalTypeMappingSource = relationalTypeMappingSource; + AnnotationCodeGenerator = annotationCodeGenerator; } /// @@ -56,12 +60,25 @@ public MigrationsCodeGeneratorDependencies([NotNull] IRelationalTypeMappingSourc /// public IRelationalTypeMappingSource RelationalTypeMappingSource { get; } + /// + /// The annotation code generator. + /// + public IAnnotationCodeGenerator AnnotationCodeGenerator { get; } + /// /// Clones this dependency parameter object with one service replaced. /// /// A replacement for the current dependency of this type. /// A new parameter object with the given service replaced. public MigrationsCodeGeneratorDependencies With([NotNull] IRelationalTypeMappingSource relationalTypeMappingSource) - => new MigrationsCodeGeneratorDependencies(relationalTypeMappingSource); + => new MigrationsCodeGeneratorDependencies(relationalTypeMappingSource, AnnotationCodeGenerator); + + /// + /// Clones this dependency parameter object with one service replaced. + /// + /// A replacement for the current dependency of this type. + /// A new parameter object with the given service replaced. + public MigrationsCodeGeneratorDependencies With([NotNull] IAnnotationCodeGenerator annotationCodeGenerator) + => new MigrationsCodeGeneratorDependencies(RelationalTypeMappingSource, annotationCodeGenerator); } } diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs index 9d931c74f44..0592faeb5a0 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs @@ -264,41 +264,21 @@ protected virtual void GenerateOnModelCreating( _sb.AppendLine("protected override void OnModelCreating(ModelBuilder modelBuilder)"); _sb.Append("{"); - var annotations = model.GetAnnotations().ToList(); - RemoveAnnotation(ref annotations, CoreAnnotationNames.ProductVersion); - RemoveAnnotation(ref annotations, CoreAnnotationNames.ChangeTrackingStrategy); - RemoveAnnotation(ref annotations, CoreAnnotationNames.OwnedTypes); - RemoveAnnotation(ref annotations, ChangeDetector.SkipDetectChangesAnnotation); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.MaxIdentifierLength); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.RelationalModel); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.CheckConstraints); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.Sequences); - RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.DatabaseName); - RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.EntityTypeErrors); - - var annotationsToRemove = new List(); + var annotations = model.GetAnnotations().ToDictionary(a => a.Name, a => a); - var lines = new List(); + _annotationCodeGenerator.RemoveIgnoredAnnotations(annotations); + _annotationCodeGenerator.RemoveConventionalAnnotations(model, annotations); - foreach (var annotation in annotations) - { - if (annotation.Value == null - || _annotationCodeGenerator.IsHandledByConvention(model, annotation)) - { - annotationsToRemove.Add(annotation); - } - else - { - var methodCall = _annotationCodeGenerator.GenerateFluentApi(model, annotation); - if (methodCall != null) - { - lines.Add(_code.Fragment(methodCall)); - annotationsToRemove.Add(annotation); - } - } - } + annotations.Remove(CoreAnnotationNames.ProductVersion); + annotations.Remove(RelationalAnnotationNames.MaxIdentifierLength); // ? + annotations.Remove(ScaffoldingAnnotationNames.DatabaseName); + annotations.Remove(ScaffoldingAnnotationNames.EntityTypeErrors); - lines.AddRange(GenerateAnnotations(annotations.Except(annotationsToRemove))); + var lines = new List(); + + lines.AddRange( + _annotationCodeGenerator.GenerateFluentApiCalls(model, annotations).Select(m => _code.Fragment(m)) + .Concat(GenerateRawAnnotations(annotations.Values))); if (lines.Count > 0) { @@ -366,63 +346,45 @@ private void GenerateEntityType(IEntityType entityType, bool useDataAnnotations) { GenerateKey(entityType.FindPrimaryKey(), entityType, useDataAnnotations); - var annotations = entityType.GetAnnotations().ToList(); - RemoveAnnotation(ref annotations, CoreAnnotationNames.ConstructorBinding); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.TableName); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.Schema); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewName); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewSchema); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.TableMappings); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewMappings); - RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.DbSetName); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.Comment); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewDefinitionSql); + var annotations = entityType.GetAnnotations().ToDictionary(a => a.Name, a => a); - if (!useDataAnnotations || entityType.GetViewName() != null) - { - GenerateTableName(entityType); - } + _annotationCodeGenerator.RemoveIgnoredAnnotations(annotations); + _annotationCodeGenerator.RemoveConventionalAnnotations(entityType, annotations); - var annotationsToRemove = new List(); - var lines = new List(); + annotations.Remove(RelationalAnnotationNames.TableName); + annotations.Remove(RelationalAnnotationNames.Schema); + annotations.Remove(RelationalAnnotationNames.ViewName); + annotations.Remove(RelationalAnnotationNames.ViewSchema); + annotations.Remove(ScaffoldingAnnotationNames.DbSetName); + annotations.Remove(RelationalAnnotationNames.ViewDefinitionSql); - foreach (var annotation in annotations) + if (useDataAnnotations) { - if (annotation.Value == null - || _annotationCodeGenerator.IsHandledByConvention(entityType, annotation)) - { - annotationsToRemove.Add(annotation); - } - else - { - var methodCall = _annotationCodeGenerator.GenerateFluentApi(entityType, annotation); - if (methodCall != null) - { - lines.Add(_code.Fragment(methodCall)); - annotationsToRemove.Add(annotation); - } - } + // Strip out any annotations handled as attributes - these are already handled when generating + // the entity's properties + _ = _annotationCodeGenerator.GenerateDataAnnotationAttributes(entityType, annotations); } - lines.AddRange(GenerateAnnotations(annotations.Except(annotationsToRemove))); - - if (entityType.GetComment() != null) + if (!useDataAnnotations || entityType.GetViewName() != null) { - lines.Add( - $".{nameof(RelationalEntityTypeBuilderExtensions.HasComment)}" + - $"({_code.Literal(entityType.GetComment())})"); + GenerateTableName(entityType); } + var lines = new List( + _annotationCodeGenerator.GenerateFluentApiCalls(entityType, annotations).Select(m => _code.Fragment(m)) + .Concat(GenerateRawAnnotations(annotations.Values))); + AppendMultiLineFluentApi(entityType, lines); foreach (var index in entityType.GetIndexes()) { - // If there are annotations that cannot be represented - // using an IndexAttribute then use fluent API even + // If there are annotations that cannot be represented using an IndexAttribute then use fluent API even // if useDataAnnotations is true. - if (!useDataAnnotations - || index.GetAnnotations().Any( - a => !CSharpModelGenerator.IgnoredIndexAnnotations.Contains(a.Name))) + var indexAnnotations = index.GetAnnotations().ToDictionary(a => a.Name, a => a); + _annotationCodeGenerator.RemoveIgnoredAnnotations(indexAnnotations); + _annotationCodeGenerator.RemoveConventionalAnnotations(index, indexAnnotations); + + if (!useDataAnnotations || indexAnnotations.Count > 0) { GenerateIndex(index); } @@ -481,11 +443,13 @@ private void GenerateKey(IKey key, IEntityType entityType, bool useDataAnnotatio return; } - var annotations = key.GetAnnotations().ToList(); + var annotations = key.GetAnnotations().ToDictionary(a => a.Name, a => a); + + _annotationCodeGenerator.RemoveIgnoredAnnotations(annotations); + _annotationCodeGenerator.RemoveConventionalAnnotations(key, annotations); var explicitName = key.GetName() != key.GetDefaultName(); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.Name); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.UniqueConstraintMappings); + annotations.Remove(RelationalAnnotationNames.Name); if (key.Properties.Count == 1 && annotations.Count == 0) @@ -515,27 +479,9 @@ private void GenerateKey(IKey key, IEntityType entityType, bool useDataAnnotatio $"({_code.Literal(key.GetName())})"); } - var annotationsToRemove = new List(); - - foreach (var annotation in annotations) - { - if (annotation.Value == null - || _annotationCodeGenerator.IsHandledByConvention(key, annotation)) - { - annotationsToRemove.Add(annotation); - } - else - { - var methodCall = _annotationCodeGenerator.GenerateFluentApi(key, annotation); - if (methodCall != null) - { - lines.Add(_code.Fragment(methodCall)); - annotationsToRemove.Add(annotation); - } - } - } - - lines.AddRange(GenerateAnnotations(annotations.Except(annotationsToRemove))); + lines.AddRange( + _annotationCodeGenerator.GenerateFluentApiCalls(key, annotations).Select(m => _code.Fragment(m)) + .Concat(GenerateRawAnnotations(annotations.Values))); AppendMultiLineFluentApi(key.DeclaringEntityType, lines); } @@ -589,53 +535,25 @@ private void GenerateTableName(IEntityType entityType) private void GenerateIndex(IIndex index) { - var annotations = index.GetAnnotations().ToList(); + var annotations = index.GetAnnotations().ToDictionary(a => a.Name, a => a); + + _annotationCodeGenerator.RemoveIgnoredAnnotations(annotations); + _annotationCodeGenerator.RemoveConventionalAnnotations(index, annotations); var lines = new List { $".{nameof(EntityTypeBuilder.HasIndex)}" + $"({_code.Lambda(index.Properties)}, " + $"{_code.Literal(index.GetDatabaseName())})" }; - RemoveAnnotation(ref annotations, RelationalAnnotationNames.Name); - - foreach (var annotation in CSharpModelGenerator.IgnoredIndexAnnotations) - { - RemoveAnnotation(ref annotations, annotation); - } + annotations.Remove(RelationalAnnotationNames.Name); if (index.IsUnique) { lines.Add($".{nameof(IndexBuilder.IsUnique)}()"); } - if (index.GetFilter() != null) - { - lines.Add( - $".{nameof(RelationalIndexBuilderExtensions.HasFilter)}" + - $"({_code.Literal(index.GetFilter())})"); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.Filter); - } - - var annotationsToRemove = new List(); - - foreach (var annotation in annotations) - { - if (annotation.Value == null - || _annotationCodeGenerator.IsHandledByConvention(index, annotation)) - { - annotationsToRemove.Add(annotation); - } - else - { - var methodCall = _annotationCodeGenerator.GenerateFluentApi(index, annotation); - if (methodCall != null) - { - lines.Add(_code.Fragment(methodCall)); - annotationsToRemove.Add(annotation); - } - } - } - - lines.AddRange(GenerateAnnotations(annotations.Except(annotationsToRemove))); + lines.AddRange( + _annotationCodeGenerator.GenerateFluentApiCalls(index, annotations).Select(m => _code.Fragment(m)) + .Concat(GenerateRawAnnotations(annotations.Values))); AppendMultiLineFluentApi(index.DeclaringEntityType, lines); } @@ -644,29 +562,19 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations) { var lines = new List { $".{nameof(EntityTypeBuilder.Property)}(e => e.{property.Name})" }; - var annotations = property.GetAnnotations().ToList(); - - RemoveAnnotation(ref annotations, CoreAnnotationNames.MaxLength); - RemoveAnnotation(ref annotations, CoreAnnotationNames.Precision); - RemoveAnnotation(ref annotations, CoreAnnotationNames.Scale); - RemoveAnnotation(ref annotations, CoreAnnotationNames.TypeMapping); - RemoveAnnotation(ref annotations, CoreAnnotationNames.Unicode); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.ColumnName); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewColumnName); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.ColumnType); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.DefaultValue); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.DefaultValueSql); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.Comment); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.Collation); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.ComputedColumnSql); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.ComputedColumnIsStored); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.IsFixedLength); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.TableColumnMappings); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.ViewColumnMappings); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.RelationalOverrides); - RemoveAnnotation(ref annotations, ScaffoldingAnnotationNames.ColumnOrdinal); - - if (!useDataAnnotations) + var annotations = property.GetAnnotations().ToDictionary(a => a.Name, a => a); + + _annotationCodeGenerator.RemoveIgnoredAnnotations(annotations); + _annotationCodeGenerator.RemoveConventionalAnnotations(property, annotations); + annotations.Remove(ScaffoldingAnnotationNames.ColumnOrdinal); + + if (useDataAnnotations) + { + // Strip out any annotations handled as attributes - these are already handled when generating + // the entity's properties + _ = _annotationCodeGenerator.GenerateDataAnnotationAttributes(property, annotations); + } + else { if (!property.IsNullable && property.ClrType.IsNullableType() @@ -675,26 +583,6 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations) lines.Add($".{nameof(PropertyBuilder.IsRequired)}()"); } - var columnName = property.GetColumnName(); - - if (columnName != null - && columnName != property.Name) - { - lines.Add( - $".{nameof(RelationalPropertyBuilderExtensions.HasColumnName)}" + - $"({_code.Literal(columnName)})"); - } - - var viewColumnName = property.GetViewColumnName(); - - if (viewColumnName != null - && viewColumnName != columnName) - { - lines.Add( - $".{nameof(RelationalPropertyBuilderExtensions.HasViewColumnName)}" + - $"({_code.Literal(columnName)})"); - } - var columnType = property.GetConfiguredColumnType(); if (columnType != null) @@ -703,81 +591,6 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations) $".{nameof(RelationalPropertyBuilderExtensions.HasColumnType)}" + $"({_code.Literal(columnType)})"); } - - var maxLength = property.GetMaxLength(); - - if (maxLength.HasValue) - { - lines.Add( - $".{nameof(PropertyBuilder.HasMaxLength)}" + - $"({_code.Literal(maxLength.Value)})"); - } - } - - var precision = property.GetPrecision(); - var scale = property.GetScale(); - if (precision != null && scale != null && scale != 0) - { - lines.Add( - $".{nameof(PropertyBuilder.HasPrecision)}" + - $"({_code.Literal(precision.Value)}, {_code.Literal(scale.Value)})"); - } - else if (precision != null) - { - lines.Add( - $".{nameof(PropertyBuilder.HasPrecision)}" + - $"({_code.Literal(precision.Value)})"); - } - - if (property.IsUnicode() != null) - { - lines.Add( - $".{nameof(PropertyBuilder.IsUnicode)}" + - $"({(property.IsUnicode() == false ? "false" : "")})"); - } - - if (property.IsFixedLength() != null) - { - lines.Add( - $".{nameof(RelationalPropertyBuilderExtensions.IsFixedLength)}()"); - } - - if (property.GetDefaultValue() != null) - { - lines.Add( - $".{nameof(RelationalPropertyBuilderExtensions.HasDefaultValue)}" + - $"({_code.UnknownLiteral(property.GetDefaultValue())})"); - } - - if (property.GetDefaultValueSql() != null) - { - lines.Add( - $".{nameof(RelationalPropertyBuilderExtensions.HasDefaultValueSql)}" + - $"({_code.Literal(property.GetDefaultValueSql())})"); - } - - if (property.GetComputedColumnSql() != null) - { - lines.Add( - $".{nameof(RelationalPropertyBuilderExtensions.HasComputedColumnSql)}" + - $"({_code.Literal(property.GetComputedColumnSql())}" + - (property.GetComputedColumnIsStored() is bool computedColumnIsStored - ? $", stored: {_code.Literal(computedColumnIsStored)})" - : ")")); - } - - if (property.GetComment() != null) - { - lines.Add( - $".{nameof(RelationalPropertyBuilderExtensions.HasComment)}" + - $"({_code.Literal(property.GetComment())})"); - } - - if (property.GetCollation() != null) - { - lines.Add( - $".{nameof(RelationalPropertyBuilderExtensions.UseCollation)}" + - $"({_code.Literal(property.GetCollation())})"); } var valueGenerated = property.ValueGenerated; @@ -805,27 +618,9 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations) lines.Add($".{nameof(PropertyBuilder.IsConcurrencyToken)}()"); } - var annotationsToRemove = new List(); - - foreach (var annotation in annotations) - { - if (annotation.Value == null - || _annotationCodeGenerator.IsHandledByConvention(property, annotation)) - { - annotationsToRemove.Add(annotation); - } - else - { - var methodCall = _annotationCodeGenerator.GenerateFluentApi(property, annotation); - if (methodCall != null) - { - lines.Add(_code.Fragment(methodCall)); - annotationsToRemove.Add(annotation); - } - } - } - - lines.AddRange(GenerateAnnotations(annotations.Except(annotationsToRemove))); + lines.AddRange( + _annotationCodeGenerator.GenerateFluentApiCalls(property, annotations).Select(m => _code.Fragment(m)) + .Concat(GenerateRawAnnotations(annotations.Values))); switch (lines.Count) { @@ -842,9 +637,10 @@ private void GenerateProperty(IProperty property, bool useDataAnnotations) private void GenerateRelationship(IForeignKey foreignKey, bool useDataAnnotations) { var canUseDataAnnotations = true; - var annotations = foreignKey.GetAnnotations().ToList(); + var annotations = foreignKey.GetAnnotations().ToDictionary(a => a.Name, a => a); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.ForeignKeyMappings); + _annotationCodeGenerator.RemoveIgnoredAnnotations(annotations); + _annotationCodeGenerator.RemoveConventionalAnnotations(foreignKey, annotations); var lines = new List { @@ -883,34 +679,11 @@ private void GenerateRelationship(IForeignKey foreignKey, bool useDataAnnotation if (!string.IsNullOrEmpty((string)foreignKey[RelationalAnnotationNames.Name])) { canUseDataAnnotations = false; - lines.Add( - ".HasConstraintName" + - $"({_code.Literal(foreignKey.GetConstraintName())})"); - RemoveAnnotation(ref annotations, RelationalAnnotationNames.Name); - } - - var annotationsToRemove = new List(); - - foreach (var annotation in annotations) - { - if (annotation.Value == null - || _annotationCodeGenerator.IsHandledByConvention(foreignKey, annotation)) - { - annotationsToRemove.Add(annotation); - } - else - { - var methodCall = _annotationCodeGenerator.GenerateFluentApi(foreignKey, annotation); - if (methodCall != null) - { - canUseDataAnnotations = false; - lines.Add(_code.Fragment(methodCall)); - annotationsToRemove.Add(annotation); - } - } } - lines.AddRange(GenerateAnnotations(annotations.Except(annotationsToRemove))); + lines.AddRange( + _annotationCodeGenerator.GenerateFluentApiCalls(foreignKey, annotations).Select(m => _code.Fragment(m)) + .Concat(GenerateRawAnnotations(annotations.Values))); if (!useDataAnnotations || !canUseDataAnnotations) @@ -983,14 +756,8 @@ private void GenerateSequence(ISequence sequence) _sb.AppendLine(";"); } - private static void RemoveAnnotation(ref List annotations, string annotationName) - => annotations.Remove(annotations.SingleOrDefault(a => a.Name == annotationName)); - - private IList GenerateAnnotations(IEnumerable annotations) - => annotations.Select(GenerateAnnotation).ToList(); - - private string GenerateAnnotation(IAnnotation annotation) - => $".HasAnnotation({_code.Literal(annotation.Name)}, " + - $"{_code.UnknownLiteral(annotation.Value)})"; + private IList GenerateRawAnnotations(IEnumerable annotations) + => annotations.Select(a => + $".HasAnnotation({_code.Literal(a.Name)}, " + $"{_code.UnknownLiteral(a.Value)})").ToList(); } } diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs index ec585b33a08..bcd0b1c1684 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs @@ -27,6 +27,7 @@ namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal /// public class CSharpEntityTypeGenerator : ICSharpEntityTypeGenerator { + private readonly IAnnotationCodeGenerator _annotationCodeGenerator; private readonly ICSharpHelper _code; private IndentedStringBuilder _sb = null!; @@ -39,10 +40,12 @@ public class CSharpEntityTypeGenerator : ICSharpEntityTypeGenerator /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public CSharpEntityTypeGenerator( + [NotNull] IAnnotationCodeGenerator annotationCodeGenerator, [NotNull] ICSharpHelper cSharpHelper) { Check.NotNull(cSharpHelper, nameof(cSharpHelper)); + _annotationCodeGenerator = annotationCodeGenerator; _code = cSharpHelper; } @@ -102,8 +105,7 @@ public virtual string WriteCode(IEntityType entityType, string @namespace, bool /// 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. /// - protected virtual void GenerateClass( - [NotNull] IEntityType entityType) + protected virtual void GenerateClass([NotNull] IEntityType entityType) { Check.NotNull(entityType, nameof(entityType)); @@ -132,14 +134,26 @@ protected virtual void GenerateClass( /// 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. /// - protected virtual void GenerateEntityTypeDataAnnotations( - [NotNull] IEntityType entityType) + protected virtual void GenerateEntityTypeDataAnnotations([NotNull] IEntityType entityType) { Check.NotNull(entityType, nameof(entityType)); GenerateKeylessAttribute(entityType); GenerateTableAttribute(entityType); GenerateIndexAttributes(entityType); + + var annotations = entityType.GetAnnotations().ToDictionary(a => a.Name, a => a); + _annotationCodeGenerator.RemoveIgnoredAnnotations(annotations); + _annotationCodeGenerator.RemoveConventionalAnnotations(entityType, annotations); + + foreach (var attribute in _annotationCodeGenerator.GenerateDataAnnotationAttributes(entityType, annotations)) + { + var attributeWriter = new AttributeWriter(attribute.Type.Name); + foreach (var argument in attribute.Arguments) + { + attributeWriter.AddParameter(_code.UnknownLiteral(argument)); + } + } } private void GenerateKeylessAttribute(IEntityType entityType) @@ -181,10 +195,12 @@ private void GenerateIndexAttributes(IEntityType entityType) foreach (var index in entityType.GetIndexes().Where(i => ConfigurationSource.Convention != ((IConventionIndex)i).GetConfigurationSource())) { - // If there are annotations that cannot be represented - // using an IndexAttribute then use fluent API instead. - if (!index.GetAnnotations().Any( - a => !CSharpModelGenerator.IgnoredIndexAnnotations.Contains(a.Name))) + // If there are annotations that cannot be represented using an IndexAttribute then use fluent API instead. + var indexAnnotations = index.GetAnnotations().ToDictionary(a => a.Name, a => a); + _annotationCodeGenerator.RemoveIgnoredAnnotations(indexAnnotations); + _annotationCodeGenerator.RemoveConventionalAnnotations(index, indexAnnotations); + + if (indexAnnotations.Count == 0) { var indexAttribute = new AttributeWriter(nameof(IndexAttribute)); foreach (var property in index.Properties) @@ -213,8 +229,7 @@ private void GenerateIndexAttributes(IEntityType entityType) /// 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. /// - protected virtual void GenerateConstructor( - [NotNull] IEntityType entityType) + protected virtual void GenerateConstructor([NotNull] IEntityType entityType) { Check.NotNull(entityType, nameof(entityType)); @@ -244,8 +259,7 @@ protected virtual void GenerateConstructor( /// 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. /// - protected virtual void GenerateProperties( - [NotNull] IEntityType entityType) + protected virtual void GenerateProperties([NotNull] IEntityType entityType) { Check.NotNull(entityType, nameof(entityType)); @@ -266,15 +280,26 @@ protected virtual void GenerateProperties( /// 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. /// - protected virtual void GeneratePropertyDataAnnotations( - [NotNull] IProperty property) + protected virtual void GeneratePropertyDataAnnotations([NotNull] IProperty property) { Check.NotNull(property, nameof(property)); GenerateKeyAttribute(property); GenerateRequiredAttribute(property); GenerateColumnAttribute(property); - GenerateMaxLengthAttribute(property); + + var annotations = property.GetAnnotations().ToDictionary(a => a.Name, a => a); + _annotationCodeGenerator.RemoveIgnoredAnnotations(annotations); + _annotationCodeGenerator.RemoveConventionalAnnotations(property, annotations); + + foreach (var attribute in _annotationCodeGenerator.GenerateDataAnnotationAttributes(property, annotations)) + { + var attributeWriter = new AttributeWriter(attribute.Type.Name); + foreach (var argument in attribute.Arguments) + { + attributeWriter.AddParameter(_code.UnknownLiteral(argument)); + } + } } private void GenerateKeyAttribute(IProperty property) @@ -312,23 +337,6 @@ private void GenerateColumnAttribute(IProperty property) } } - private void GenerateMaxLengthAttribute(IProperty property) - { - var maxLength = property.GetMaxLength(); - - if (maxLength.HasValue) - { - var lengthAttribute = new AttributeWriter( - property.ClrType == typeof(string) - ? nameof(StringLengthAttribute) - : nameof(MaxLengthAttribute)); - - lengthAttribute.AddParameter(_code.Literal(maxLength.Value)); - - _sb.AppendLine(lengthAttribute.ToString()); - } - } - private void GenerateRequiredAttribute(IProperty property) { if (!property.IsNullable @@ -345,8 +353,7 @@ private void GenerateRequiredAttribute(IProperty property) /// 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. /// - protected virtual void GenerateNavigationProperties( - [NotNull] IEntityType entityType) + protected virtual void GenerateNavigationProperties([NotNull] IEntityType entityType) { Check.NotNull(entityType, nameof(entityType)); diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpModelGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpModelGenerator.cs index c3254f6bd21..07616483551 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpModelGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpModelGenerator.cs @@ -129,11 +129,5 @@ public override ScaffoldedModel GenerateModel( return resultingFiles; } - - /// - /// The set of annotations ignored for the purposes of code generation for indexes. - /// - public static IReadOnlyList IgnoredIndexAnnotations - => new List { RelationalAnnotationNames.TableIndexMappings }; } } diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index 352a0794385..3e50b5a69c7 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -1,11 +1,20 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; +#pragma warning disable EF1001 // Accessing annotation names (internal) + namespace Microsoft.EntityFrameworkCore.Design { /// @@ -36,13 +45,304 @@ public AnnotationCodeGenerator([NotNull] AnnotationCodeGeneratorDependencies dep /// protected virtual AnnotationCodeGeneratorDependencies Dependencies { get; } + /// + public virtual void RemoveIgnoredAnnotations(IDictionary annotations) + { + annotations.Remove(CoreAnnotationNames.NavigationCandidates); + annotations.Remove(CoreAnnotationNames.AmbiguousNavigations); + annotations.Remove(CoreAnnotationNames.InverseNavigations); + annotations.Remove(ChangeDetector.SkipDetectChangesAnnotation); + annotations.Remove(CoreAnnotationNames.OwnedTypes); + annotations.Remove(CoreAnnotationNames.ChangeTrackingStrategy); + annotations.Remove(CoreAnnotationNames.BeforeSaveBehavior); + annotations.Remove(CoreAnnotationNames.AfterSaveBehavior); + annotations.Remove(CoreAnnotationNames.TypeMapping); + annotations.Remove(CoreAnnotationNames.ValueComparer); +#pragma warning disable 618 + annotations.Remove(CoreAnnotationNames.KeyValueComparer); + annotations.Remove(CoreAnnotationNames.StructuralValueComparer); +#pragma warning restore 618 + annotations.Remove(CoreAnnotationNames.ConstructorBinding); + annotations.Remove(CoreAnnotationNames.NavigationAccessMode); + annotations.Remove(CoreAnnotationNames.PropertyAccessMode); + annotations.Remove(CoreAnnotationNames.ProviderClrType); + annotations.Remove(CoreAnnotationNames.ValueConverter); + annotations.Remove(CoreAnnotationNames.ValueGeneratorFactory); + annotations.Remove(CoreAnnotationNames.DefiningQuery); + annotations.Remove(CoreAnnotationNames.QueryFilter); + annotations.Remove(RelationalAnnotationNames.RelationalModel); + annotations.Remove(RelationalAnnotationNames.CheckConstraints); + annotations.Remove(RelationalAnnotationNames.Sequences); + annotations.Remove(RelationalAnnotationNames.DbFunctions); + annotations.Remove(RelationalAnnotationNames.TableMappings); + annotations.Remove(RelationalAnnotationNames.TableColumnMappings); + annotations.Remove(RelationalAnnotationNames.ViewMappings); + annotations.Remove(RelationalAnnotationNames.ViewColumnMappings); + annotations.Remove(RelationalAnnotationNames.ForeignKeyMappings); + annotations.Remove(RelationalAnnotationNames.TableIndexMappings); + annotations.Remove(RelationalAnnotationNames.UniqueConstraintMappings); + annotations.Remove(RelationalAnnotationNames.RelationalOverrides); + + foreach (var (name, annotation) in EnumerateForRemoval(annotations)) + { + if (annotation.Value is null) + { + annotations.Remove(name); + } + } + } + + /// + public virtual void RemoveConventionalAnnotations(IModel model, IDictionary annotations) + => RemoveConventionalAnnotationsHelper(model, annotations, IsHandledByConvention); + + /// + public virtual void RemoveConventionalAnnotations( + IEntityType entityType, IDictionary annotations) + => RemoveConventionalAnnotationsHelper(entityType, annotations, IsHandledByConvention); + + /// + public virtual void RemoveConventionalAnnotations( + IProperty property, IDictionary annotations) + { + var columnName = property.GetColumnName(); + + if (columnName == property.Name) + { + annotations.Remove(RelationalAnnotationNames.ColumnName); + } + + if (annotations.TryGetValue(RelationalAnnotationNames.ViewColumnName, out var viewColumnNameAnnotation) + && viewColumnNameAnnotation.Value is string viewColumnName + && viewColumnName != columnName) + { + annotations.Remove(RelationalAnnotationNames.ViewColumnName); + } + + RemoveConventionalAnnotationsHelper(property, annotations, IsHandledByConvention); + } + + /// + public virtual void RemoveConventionalAnnotations(IKey key, IDictionary annotations) + => RemoveConventionalAnnotationsHelper(key, annotations, IsHandledByConvention); + + /// + public virtual void RemoveConventionalAnnotations( + IForeignKey foreignKey, IDictionary annotations) + => RemoveConventionalAnnotationsHelper(foreignKey, annotations, IsHandledByConvention); + + /// + public virtual void RemoveConventionalAnnotations(IIndex index, IDictionary annotations) + => RemoveConventionalAnnotationsHelper(index, annotations, IsHandledByConvention); + + /// + public virtual IReadOnlyList GenerateFluentApiCalls( + IModel model, IDictionary annotations) + { + var methodCallCodeFragments = new List(); + + GenerateSimpleFluentApiCall( + annotations, + RelationalAnnotationNames.DefaultSchema, nameof(RelationalModelBuilderExtensions.HasDefaultSchema), + methodCallCodeFragments); + + methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(model, annotations, GenerateFluentApi)); + return methodCallCodeFragments; + } + + /// + public virtual IReadOnlyList GenerateFluentApiCalls( + IEntityType entityType, IDictionary annotations) + { + var methodCallCodeFragments = new List(); + + GenerateSimpleFluentApiCall( + annotations, + RelationalAnnotationNames.Comment, nameof(RelationalEntityTypeBuilderExtensions.HasComment), methodCallCodeFragments); + + methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(entityType, annotations, GenerateFluentApi)); + + return methodCallCodeFragments; + } + + /// + public virtual IReadOnlyList GenerateFluentApiCalls( + IProperty property, IDictionary annotations) + { + var methodCallCodeFragments = new List(); + + GenerateSimpleFluentApiCall( + annotations, + RelationalAnnotationNames.ColumnName, nameof(RelationalPropertyBuilderExtensions.HasColumnName), methodCallCodeFragments); + + GenerateSimpleFluentApiCall( + annotations, + RelationalAnnotationNames.ViewColumnName, nameof(RelationalPropertyBuilderExtensions.HasViewColumnName), + methodCallCodeFragments); + + GenerateSimpleFluentApiCall( + annotations, + RelationalAnnotationNames.DefaultValueSql, nameof(RelationalPropertyBuilderExtensions.HasDefaultValueSql), + methodCallCodeFragments); + + if (TryGetAndRemove(annotations, RelationalAnnotationNames.DefaultValue, out object defaultValue)) + { + var valueConverter = property.GetValueConverter() + ?? (property.FindTypeMapping() + ?? Dependencies.RelationalTypeMappingSource.FindMapping(property))?.Converter; + + methodCallCodeFragments.Add( + new MethodCallCodeFragment( + nameof(RelationalPropertyBuilderExtensions.HasDefaultValue), + valueConverter == null ? defaultValue : valueConverter.ConvertToProvider(defaultValue))); + } + + if (TryGetAndRemove(annotations, RelationalAnnotationNames.ComputedColumnSql, out object computedColumnSql)) + { + methodCallCodeFragments.Add( + TryGetAndRemove(annotations, RelationalAnnotationNames.ComputedColumnIsStored, out bool isStored) + ? new MethodCallCodeFragment( + nameof(RelationalPropertyBuilderExtensions.HasComputedColumnSql), + computedColumnSql, + isStored) + : new MethodCallCodeFragment( + nameof(RelationalPropertyBuilderExtensions.HasComputedColumnSql), + computedColumnSql)); + } + + GenerateSimpleFluentApiCall( + annotations, + RelationalAnnotationNames.IsFixedLength, nameof(RelationalPropertyBuilderExtensions.IsFixedLength), + methodCallCodeFragments); + + GenerateSimpleFluentApiCall( + annotations, + RelationalAnnotationNames.Comment, nameof(RelationalPropertyBuilderExtensions.HasComment), methodCallCodeFragments); + + GenerateSimpleFluentApiCall( + annotations, + RelationalAnnotationNames.Collation, nameof(RelationalPropertyBuilderExtensions.UseCollation), methodCallCodeFragments); + + GenerateSimpleFluentApiCall( + annotations, + CoreAnnotationNames.MaxLength, nameof(PropertyBuilder.HasMaxLength), methodCallCodeFragments); + + var hasScale = TryGetAndRemove(annotations, CoreAnnotationNames.Scale, out int scale) && scale != 0; + + if (TryGetAndRemove(annotations, CoreAnnotationNames.Precision, out int precision)) + { + methodCallCodeFragments.Add( + hasScale + ? new MethodCallCodeFragment(nameof(PropertyBuilder.HasPrecision), precision, scale) + : new MethodCallCodeFragment(nameof(PropertyBuilder.HasPrecision), precision)); + } + + GenerateSimpleFluentApiCall( + annotations, + CoreAnnotationNames.Unicode, nameof(PropertyBuilder.IsUnicode), methodCallCodeFragments); + + methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(property, annotations, GenerateFluentApi)); + + return methodCallCodeFragments; + } + + /// + public virtual IReadOnlyList GenerateFluentApiCalls( + IKey key, IDictionary annotations) + { + var methodCallCodeFragments = new List(); + + GenerateSimpleFluentApiCall( + annotations, + RelationalAnnotationNames.Name, nameof(RelationalKeyBuilderExtensions.HasName), methodCallCodeFragments); + + methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(key, annotations, GenerateFluentApi)); + + return methodCallCodeFragments; + } + + /// + public virtual IReadOnlyList GenerateFluentApiCalls( + IForeignKey foreignKey, IDictionary annotations) + { + var methodCallCodeFragments = new List(); + + GenerateSimpleFluentApiCall( + annotations, + RelationalAnnotationNames.Name, nameof(RelationalForeignKeyBuilderExtensions.HasConstraintName), methodCallCodeFragments); + + methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(foreignKey, annotations, GenerateFluentApi)); + + return methodCallCodeFragments; + } + + /// + public virtual IReadOnlyList GenerateFluentApiCalls( + IIndex index, IDictionary annotations) + { + var methodCallCodeFragments = new List(); + + GenerateSimpleFluentApiCall( + annotations, + RelationalAnnotationNames.Name, nameof(RelationalIndexBuilderExtensions.HasDatabaseName), methodCallCodeFragments); + + GenerateSimpleFluentApiCall( + annotations, + RelationalAnnotationNames.Filter, nameof(RelationalIndexBuilderExtensions.HasFilter), methodCallCodeFragments); + + methodCallCodeFragments.AddRange(GenerateFluentApiCallsHelper(index, annotations, GenerateFluentApi)); + + return methodCallCodeFragments; + } + + /// + public virtual IReadOnlyList GenerateDataAnnotationAttributes( + IEntityType entityType, IDictionary annotations) + { + var attributeCodeFragments = new List(); + + attributeCodeFragments.AddRange(GenerateFluentApiCallsHelper(entityType, annotations, GenerateDataAnnotation)); + + return attributeCodeFragments; + } + + /// + public virtual IReadOnlyList GenerateDataAnnotationAttributes( + IProperty property, IDictionary annotations) + { + var attributeCodeFragments = new List(); + + if (TryGetAndRemove(annotations, CoreAnnotationNames.MaxLength, out int maxLength)) + { + attributeCodeFragments.Add( + new AttributeCodeFragment( + property.ClrType == typeof(string) + ? typeof(StringLengthAttribute) + : typeof(MaxLengthAttribute), + maxLength)); + } + + attributeCodeFragments.AddRange(GenerateFluentApiCallsHelper(property, annotations, GenerateDataAnnotation)); + + return attributeCodeFragments; + } + /// - /// Returns unless overridden to do otherwise. + /// + /// Checks if the given is handled by convention when + /// applied to the given . + /// + /// + /// The default implementation always returns . + /// /// /// The . /// The . - /// . - public virtual bool IsHandledByConvention(IModel model, IAnnotation annotation) + /// + /// if the annotation is handled by convention; + /// if code must be generated. + /// + public virtual bool IsHandledByConvention([NotNull] IModel model, [NotNull] IAnnotation annotation) { Check.NotNull(model, nameof(model)); Check.NotNull(annotation, nameof(annotation)); @@ -51,12 +351,18 @@ public virtual bool IsHandledByConvention(IModel model, IAnnotation annotation) } /// - /// Returns unless overridden to do otherwise. + /// + /// Checks if the given is handled by convention when + /// applied to the given . + /// + /// + /// The default implementation always returns . + /// /// /// The . /// The . /// . - public virtual bool IsHandledByConvention(IEntityType entityType, IAnnotation annotation) + public virtual bool IsHandledByConvention([NotNull] IEntityType entityType, [NotNull] IAnnotation annotation) { Check.NotNull(entityType, nameof(entityType)); Check.NotNull(annotation, nameof(annotation)); @@ -65,12 +371,18 @@ public virtual bool IsHandledByConvention(IEntityType entityType, IAnnotation an } /// - /// Returns unless overridden to do otherwise. + /// + /// Checks if the given is handled by convention when + /// applied to the given . + /// + /// + /// The default implementation always returns . + /// /// /// The . /// The . /// . - public virtual bool IsHandledByConvention(IKey key, IAnnotation annotation) + public virtual bool IsHandledByConvention([NotNull] IKey key, [NotNull] IAnnotation annotation) { Check.NotNull(key, nameof(key)); Check.NotNull(annotation, nameof(annotation)); @@ -79,12 +391,18 @@ public virtual bool IsHandledByConvention(IKey key, IAnnotation annotation) } /// - /// Returns unless overridden to do otherwise. + /// + /// Checks if the given is handled by convention when + /// applied to the given . + /// + /// + /// The default implementation always returns . + /// /// /// The . /// The . /// . - public virtual bool IsHandledByConvention(IProperty property, IAnnotation annotation) + public virtual bool IsHandledByConvention([NotNull] IProperty property, [NotNull] IAnnotation annotation) { Check.NotNull(property, nameof(property)); Check.NotNull(annotation, nameof(annotation)); @@ -93,12 +411,18 @@ public virtual bool IsHandledByConvention(IProperty property, IAnnotation annota } /// - /// Returns unless overridden to do otherwise. + /// + /// Checks if the given is handled by convention when + /// applied to the given . + /// + /// + /// The default implementation always returns . + /// /// /// The . /// The . /// . - public virtual bool IsHandledByConvention(IForeignKey foreignKey, IAnnotation annotation) + public virtual bool IsHandledByConvention([NotNull] IForeignKey foreignKey, [NotNull] IAnnotation annotation) { Check.NotNull(foreignKey, nameof(foreignKey)); Check.NotNull(annotation, nameof(annotation)); @@ -107,12 +431,18 @@ public virtual bool IsHandledByConvention(IForeignKey foreignKey, IAnnotation an } /// - /// Returns unless overridden to do otherwise. + /// + /// Checks if the given is handled by convention when + /// applied to the given . + /// + /// + /// The default implementation always returns . + /// /// /// The . /// The . /// . - public virtual bool IsHandledByConvention(IIndex index, IAnnotation annotation) + public virtual bool IsHandledByConvention([NotNull] IIndex index, [NotNull] IAnnotation annotation) { Check.NotNull(index, nameof(index)); Check.NotNull(annotation, nameof(annotation)); @@ -121,12 +451,18 @@ public virtual bool IsHandledByConvention(IIndex index, IAnnotation annotation) } /// - /// Returns unless overridden to do otherwise. + /// + /// Returns a fluent API call for the given , or + /// if no fluent API call exists for it. + /// + /// + /// The default implementation always returns . + /// /// /// The . /// The . /// . - public virtual MethodCallCodeFragment GenerateFluentApi(IModel model, IAnnotation annotation) + public virtual MethodCallCodeFragment GenerateFluentApi([NotNull] IModel model, [NotNull] IAnnotation annotation) { Check.NotNull(model, nameof(model)); Check.NotNull(annotation, nameof(annotation)); @@ -135,12 +471,18 @@ public virtual MethodCallCodeFragment GenerateFluentApi(IModel model, IAnnotatio } /// - /// Returns unless overridden to do otherwise. + /// + /// Returns a fluent API call for the given , or + /// if no fluent API call exists for it. + /// + /// + /// The default implementation always returns . + /// /// /// The . /// The . /// . - public virtual MethodCallCodeFragment GenerateFluentApi(IEntityType entityType, IAnnotation annotation) + public virtual MethodCallCodeFragment GenerateFluentApi([NotNull] IEntityType entityType, [NotNull] IAnnotation annotation) { Check.NotNull(entityType, nameof(entityType)); Check.NotNull(annotation, nameof(annotation)); @@ -149,12 +491,18 @@ public virtual MethodCallCodeFragment GenerateFluentApi(IEntityType entityType, } /// - /// Returns unless overridden to do otherwise. + /// + /// Returns a fluent API call for the given , or + /// if no fluent API call exists for it. + /// + /// + /// The default implementation always returns . + /// /// /// The . /// The . /// . - public virtual MethodCallCodeFragment GenerateFluentApi(IKey key, IAnnotation annotation) + public virtual MethodCallCodeFragment GenerateFluentApi([NotNull] IKey key, [NotNull] IAnnotation annotation) { Check.NotNull(key, nameof(key)); Check.NotNull(annotation, nameof(annotation)); @@ -163,12 +511,18 @@ public virtual MethodCallCodeFragment GenerateFluentApi(IKey key, IAnnotation an } /// - /// Returns unless overridden to do otherwise. + /// + /// Returns a fluent API call for the given , or + /// if no fluent API call exists for it. + /// + /// + /// The default implementation always returns . + /// /// /// The . /// The . /// . - public virtual MethodCallCodeFragment GenerateFluentApi(IProperty property, IAnnotation annotation) + public virtual MethodCallCodeFragment GenerateFluentApi([NotNull] IProperty property, [NotNull] IAnnotation annotation) { Check.NotNull(property, nameof(property)); Check.NotNull(annotation, nameof(annotation)); @@ -177,12 +531,18 @@ public virtual MethodCallCodeFragment GenerateFluentApi(IProperty property, IAnn } /// - /// Returns unless overridden to do otherwise. + /// + /// Returns a fluent API call for the given , or + /// if no fluent API call exists for it. + /// + /// + /// The default implementation always returns . + /// /// /// The . /// The . /// . - public virtual MethodCallCodeFragment GenerateFluentApi(IForeignKey foreignKey, IAnnotation annotation) + public virtual MethodCallCodeFragment GenerateFluentApi([NotNull] IForeignKey foreignKey, [NotNull] IAnnotation annotation) { Check.NotNull(foreignKey, nameof(foreignKey)); Check.NotNull(annotation, nameof(annotation)); @@ -191,17 +551,128 @@ public virtual MethodCallCodeFragment GenerateFluentApi(IForeignKey foreignKey, } /// - /// Returns unless overridden to do otherwise. + /// + /// Returns a fluent API call for the given , or + /// if no fluent API call exists for it. + /// + /// + /// The default implementation always returns . + /// /// /// The . /// The . /// . - public virtual MethodCallCodeFragment GenerateFluentApi(IIndex index, IAnnotation annotation) + public virtual MethodCallCodeFragment GenerateFluentApi([NotNull] IIndex index, [NotNull] IAnnotation annotation) { Check.NotNull(index, nameof(index)); Check.NotNull(annotation, nameof(annotation)); return null; } + + /// + /// + /// Returns a data annotation attribute code fragment for the given , + /// or if no data annotation exists for it. + /// + /// + /// The default implementation always returns . + /// + /// + /// The . + /// The . + /// . + public virtual AttributeCodeFragment GenerateDataAnnotation([NotNull] IEntityType entityType, [NotNull] IAnnotation annotation) + { + Check.NotNull(entityType, nameof(entityType)); + Check.NotNull(annotation, nameof(annotation)); + + return null; + } + + /// + /// + /// Returns a data annotation attribute code fragment for the given , + /// or if no data annotation exists for it. + /// + /// + /// The default implementation always returns . + /// + /// + /// The . + /// The . + /// . + public virtual AttributeCodeFragment GenerateDataAnnotation([NotNull] IProperty property, [NotNull] IAnnotation annotation) + { + Check.NotNull(property, nameof(property)); + Check.NotNull(annotation, nameof(annotation)); + + return null; + } + + private IEnumerable GenerateFluentApiCallsHelper( + TAnnotatable annotatable, + IDictionary annotations, + Func generateCodeFragment) + { + foreach (var (name, annotation) in EnumerateForRemoval(annotations)) + { + var codeFragment = generateCodeFragment(annotatable, annotation); + if (codeFragment != null) + { + yield return codeFragment; + annotations.Remove(name); + } + } + } + + private void RemoveConventionalAnnotationsHelper( + TAnnotatable annotatable, + IDictionary annotations, + Func isHandledByConvention) + { + foreach (var (name, annotation) in EnumerateForRemoval(annotations)) + { + if (isHandledByConvention(annotatable, annotation)) + { + annotations.Remove(name); + } + } + } + + private static bool TryGetAndRemove(IDictionary annotations, string annotationName, out T annotationValue) + { + if (annotations.TryGetValue(annotationName, out var annotation) + && annotation.Value != null) + { + annotations.Remove(annotationName); + annotationValue = (T)annotation.Value; + return true; + } + + annotationValue = default; + return false; + } + + private static void GenerateSimpleFluentApiCall( + IDictionary annotations, + string annotationName, + string methodName, + List methodCallCodeFragments) + { + if (annotations.TryGetValue(annotationName, out var annotation) + && annotation.Value is { } annotationValue) + { + annotations.Remove(annotationName); + methodCallCodeFragments.Add( + new MethodCallCodeFragment(methodName, annotationValue)); + } + } + + // Dictionary is safe for removal during enumeration + private static IEnumerable> EnumerateForRemoval(IDictionary annotations) + => annotations is Dictionary + ? (IEnumerable>)annotations + : annotations.ToList(); } } diff --git a/src/EFCore.Relational/Design/AnnotationCodeGeneratorDependencies.cs b/src/EFCore.Relational/Design/AnnotationCodeGeneratorDependencies.cs index 7638de44317..fa75e94ec00 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGeneratorDependencies.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGeneratorDependencies.cs @@ -1,7 +1,10 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Design { @@ -44,8 +47,17 @@ public sealed class AnnotationCodeGeneratorDependencies /// /// [EntityFrameworkInternal] - public AnnotationCodeGeneratorDependencies() + public AnnotationCodeGeneratorDependencies( + [NotNull] IRelationalTypeMappingSource relationalTypeMappingSource) { + Check.NotNull(relationalTypeMappingSource, nameof(relationalTypeMappingSource)); + + RelationalTypeMappingSource = relationalTypeMappingSource; } + + /// + /// The type mapper. + /// + public IRelationalTypeMappingSource RelationalTypeMappingSource { get; } } } diff --git a/src/EFCore.Relational/Design/AttributeCodeFragment.cs b/src/EFCore.Relational/Design/AttributeCodeFragment.cs new file mode 100644 index 00000000000..e74f9049a08 --- /dev/null +++ b/src/EFCore.Relational/Design/AttributeCodeFragment.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Utilities; + +namespace Microsoft.EntityFrameworkCore.Design +{ + /// + /// Represents usage of an attribute. + /// + public class AttributeCodeFragment + { + private readonly List _arguments; + + /// + /// Initializes a new instance of the class. + /// + /// The attribute's CLR type. + /// The attribute's arguments. + public AttributeCodeFragment([NotNull] Type type, [NotNull] params object[] arguments) + { + Check.NotNull(type, nameof(type)); + Check.NotNull(arguments, nameof(arguments)); + + Type = type; + _arguments = new List(arguments); + } + + /// + /// Gets or sets the attribute's type. + /// + /// The attribute's type. + public virtual Type Type { get; } + + /// + /// Gets the method call's arguments. + /// + /// The method call's arguments. + public virtual IReadOnlyList Arguments => _arguments; + } +} diff --git a/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs index 917b779594b..7684d5c2a03 100644 --- a/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/IAnnotationCodeGenerator.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Collections.Generic; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -15,123 +17,137 @@ namespace Microsoft.EntityFrameworkCore.Design public interface IAnnotationCodeGenerator { /// - /// Checks if the given is handled by convention when - /// applied to the given . + /// Removes annotations from for which code should never be generated. /// - /// The . - /// The . - /// - /// if the annotation is handled by convention; - /// if code must be generated. - /// - bool IsHandledByConvention([NotNull] IModel model, [NotNull] IAnnotation annotation); + /// The set of annotations from which to remove the ignored ones. + void RemoveIgnoredAnnotations([NotNull] IDictionary annotations) { } /// - /// Checks if the given is handled by convention when - /// applied to the given . + /// Removes annotation whose configuration is already applied by convention, and do not need to be + /// specified explicitly. /// - /// The . - /// The . - /// - /// if the annotation is handled by convention; - /// if code must be generated. - /// - bool IsHandledByConvention([NotNull] IEntityType entityType, [NotNull] IAnnotation annotation); + /// The model to which the annotations are applied. + /// The set of annotations from which to remove the conventional ones. + void RemoveConventionalAnnotations([NotNull] IModel model, [NotNull] IDictionary annotations) { } /// - /// Checks if the given is handled by convention when - /// applied to the given . + /// Removes annotation whose configuration is already applied by convention, and do not need to be + /// specified explicitly. /// - /// The . - /// The . - /// - /// if the annotation is handled by convention; - /// if code must be generated. - /// - bool IsHandledByConvention([NotNull] IKey key, [NotNull] IAnnotation annotation); + /// The entity to which the annotations are applied. + /// The set of annotations from which to remove the conventional ones. + void RemoveConventionalAnnotations([NotNull] IEntityType entity, [NotNull] IDictionary annotations) { } /// - /// Checks if the given is handled by convention when - /// applied to the given . + /// Removes annotation whose configuration is already applied by convention, and do not need to be + /// specified explicitly. /// - /// The . - /// The . - /// - /// if the annotation is handled by convention; - /// if code must be generated. - /// - bool IsHandledByConvention([NotNull] IProperty property, [NotNull] IAnnotation annotation); + /// The property to which the annotations are applied. + /// The set of annotations from which to remove the conventional ones. + void RemoveConventionalAnnotations([NotNull] IProperty property, [NotNull] IDictionary annotations) { } /// - /// Checks if the given is handled by convention when - /// applied to the given . + /// Removes annotation whose configuration is already applied by convention, and do not need to be + /// specified explicitly. /// - /// The . - /// The . - /// - /// if the annotation is handled by convention; - /// if code must be generated. - /// - bool IsHandledByConvention([NotNull] IForeignKey foreignKey, [NotNull] IAnnotation annotation); + /// The key to which the annotations are applied. + /// The set of annotations from which to remove the conventional ones. + void RemoveConventionalAnnotations([NotNull] IKey key, [NotNull] IDictionary annotations) { } /// - /// Checks if the given is handled by convention when - /// applied to the given . + /// Removes annotation whose configuration is already applied by convention, and do not need to be + /// specified explicitly. /// - /// The . - /// The . - /// - /// if the annotation is handled by convention; - /// if code must be generated. - /// - bool IsHandledByConvention([NotNull] IIndex index, [NotNull] IAnnotation annotation); + /// The foreign key to which the annotations are applied. + /// The set of annotations from which to remove the conventional ones. + void RemoveConventionalAnnotations([NotNull] IForeignKey foreignKey, [NotNull] IDictionary annotations) { } /// - /// Generates fluent API calls for the given . + /// Removes annotation whose configuration is already applied by convention, and do not need to be + /// specified explicitly. /// - /// The for which code should be generated. - /// The for which code should be generated. - /// The generated code. - MethodCallCodeFragment GenerateFluentApi([NotNull] IModel model, [NotNull] IAnnotation annotation); + /// The index to which the annotations are applied. + /// The set of annotations from which to remove the conventional ones. + void RemoveConventionalAnnotations([NotNull] IIndex index, [NotNull] IDictionary annotations) { } /// - /// Generates fluent API calls for the given . + /// For the given annotations which have corresponding fluent API calls, returns those fluent API calls + /// and removes the annotations. /// - /// The for which code should be generated. - /// The for which code should be generated. - /// The generated code. - MethodCallCodeFragment GenerateFluentApi([NotNull] IEntityType entityType, [NotNull] IAnnotation annotation); + /// The model to which the annotations are applied. + /// The set of annotations from which to generate fluent API calls. + IReadOnlyList GenerateFluentApiCalls( + [NotNull] IModel model, [NotNull] IDictionary annotations) + => Array.Empty(); /// - /// Generates fluent API calls for the given . + /// For the given annotations which have corresponding fluent API calls, returns those fluent API calls + /// and removes the annotations. /// - /// The for which code should be generated. - /// The for which code should be generated. - /// The generated code. - MethodCallCodeFragment GenerateFluentApi([NotNull] IKey key, [NotNull] IAnnotation annotation); + /// The entity type to which the annotations are applied. + /// The set of annotations from which to generate fluent API calls. + IReadOnlyList GenerateFluentApiCalls( + [NotNull] IEntityType entityType, [NotNull] IDictionary annotations) + => Array.Empty(); /// - /// Generates fluent API calls for the given . + /// For the given annotations which have corresponding fluent API calls, returns those fluent API calls + /// and removes the annotations. /// - /// The for which code should be generated. - /// The for which code should be generated. - /// The generated code. - MethodCallCodeFragment GenerateFluentApi([NotNull] IProperty property, [NotNull] IAnnotation annotation); + /// The property to which the annotations are applied. + /// The set of annotations from which to generate fluent API calls. + IReadOnlyList GenerateFluentApiCalls( + [NotNull] IProperty property, [NotNull] IDictionary annotations) + => Array.Empty(); /// - /// Generates fluent API calls for the given . + /// For the given annotations which have corresponding fluent API calls, returns those fluent API calls + /// and removes the annotations. /// - /// The for which code should be generated. - /// The for which code should be generated. - /// The generated code. - MethodCallCodeFragment GenerateFluentApi([NotNull] IForeignKey foreignKey, [NotNull] IAnnotation annotation); + /// The key to which the annotations are applied. + /// The set of annotations from which to generate fluent API calls. + IReadOnlyList GenerateFluentApiCalls( + [NotNull] IKey key, [NotNull] IDictionary annotations) + => Array.Empty(); /// - /// Generates fluent API calls for the given . + /// For the given annotations which have corresponding fluent API calls, returns those fluent API calls + /// and removes the annotations. /// - /// The for which code should be generated. - /// The for which code should be generated. - /// The generated code. - MethodCallCodeFragment GenerateFluentApi([NotNull] IIndex index, [NotNull] IAnnotation annotation); + /// The foreign key to which the annotations are applied. + /// The set of annotations from which to generate fluent API calls. + IReadOnlyList GenerateFluentApiCalls( + [NotNull] IForeignKey foreignKey, [NotNull] IDictionary annotations) + => Array.Empty(); + + /// + /// For the given annotations which have corresponding fluent API calls, returns those fluent API calls + /// and removes the annotations. + /// + /// The index to which the annotations are applied. + /// The set of annotations from which to generate fluent API calls. + IReadOnlyList GenerateFluentApiCalls( + [NotNull] IIndex index, [NotNull] IDictionary annotations) + => Array.Empty(); + + /// + /// For the given annotations which have corresponding data annotation attributes, returns those attribute code fragments + /// and removes the annotations. + /// + /// The entity type to which the annotations are applied. + /// The set of annotations from which to generate fluent API calls. + IReadOnlyList GenerateDataAnnotationAttributes( + [NotNull] IEntityType entityType, [NotNull] IDictionary annotations) + => Array.Empty(); + + /// + /// For the given annotations which have corresponding data annotation attributes, returns those attribute code fragments + /// and removes the annotations. + /// + /// The property to which the annotations are applied. + /// The set of annotations from which to generate fluent API calls. + IReadOnlyList GenerateDataAnnotationAttributes( + [NotNull] IProperty property, [NotNull] IDictionary annotations) + => Array.Empty(); } } diff --git a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs index 74b68930c77..5620f90d86f 100644 --- a/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs +++ b/src/EFCore.Relational/Infrastructure/EntityFrameworkRelationalServicesBuilder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Internal; diff --git a/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs b/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs index 930e8a7643d..b3ed0eaff38 100644 --- a/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs +++ b/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs @@ -1,6 +1,9 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -29,6 +32,28 @@ public SqlServerAnnotationCodeGenerator([NotNull] AnnotationCodeGeneratorDepende { } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override IReadOnlyList GenerateFluentApiCalls(IModel model, IDictionary annotations) + => base.GenerateFluentApiCalls(model, annotations) + .Concat(GenerateValueGenerationStrategy(annotations, onModel: true)) + .ToList(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public override IReadOnlyList GenerateFluentApiCalls(IProperty property, IDictionary annotations) + => base.GenerateFluentApiCalls(property, annotations) + .Concat(GenerateValueGenerationStrategy(annotations, onModel: false)) + .ToList(); + /// /// 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 @@ -69,25 +94,78 @@ public override MethodCallCodeFragment GenerateFluentApi(IKey key, IAnnotation a /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public override MethodCallCodeFragment GenerateFluentApi(IIndex index, IAnnotation annotation) - { - if (annotation.Name == SqlServerAnnotationNames.Clustered) + => annotation.Name switch { - return (bool)annotation.Value == false + SqlServerAnnotationNames.Clustered => (bool)annotation.Value == false ? new MethodCallCodeFragment(nameof(SqlServerIndexBuilderExtensions.IsClustered), false) - : new MethodCallCodeFragment(nameof(SqlServerIndexBuilderExtensions.IsClustered)); - } + : new MethodCallCodeFragment(nameof(SqlServerIndexBuilderExtensions.IsClustered)), + + SqlServerAnnotationNames.Include => new MethodCallCodeFragment( + nameof(SqlServerIndexBuilderExtensions.IncludeProperties), annotation.Value), + + SqlServerAnnotationNames.FillFactor => new MethodCallCodeFragment( + nameof(SqlServerIndexBuilderExtensions.HasFillFactor), annotation.Value), + + _ => null + }; + + private IReadOnlyList GenerateValueGenerationStrategy( + IDictionary annotations, bool onModel) + { + var strategy = GetAndRemove(SqlServerAnnotationNames.ValueGenerationStrategy); - if (annotation.Name == SqlServerAnnotationNames.Include) + switch (strategy) { - return new MethodCallCodeFragment(nameof(SqlServerIndexBuilderExtensions.IncludeProperties), annotation.Value); - } + case SqlServerValueGenerationStrategy.IdentityColumn: + var seed = GetAndRemove(SqlServerAnnotationNames.IdentitySeed); + var increment = GetAndRemove(SqlServerAnnotationNames.IdentityIncrement); + return new List + { + new MethodCallCodeFragment( + onModel + ? nameof(SqlServerModelBuilderExtensions.UseIdentityColumns) + : nameof(SqlServerPropertyBuilderExtensions.UseIdentityColumn), + (seed, increment) switch + { + (null, null) => Array.Empty(), + (_, null) => new object[] { seed }, + _ => new object[] { seed, increment } + }) + }; + + case SqlServerValueGenerationStrategy.SequenceHiLo: + var name = GetAndRemove(SqlServerAnnotationNames.HiLoSequenceName); + var schema = GetAndRemove(SqlServerAnnotationNames.HiLoSequenceSchema); + return new List + { + new MethodCallCodeFragment( + nameof(SqlServerModelBuilderExtensions.UseHiLo), + (name, schema) switch + { + (null, null) => Array.Empty(), + (_, null) => new object[] { name }, + _ => new object[] { name, schema } + }) + }; + + case SqlServerValueGenerationStrategy.None: + return Array.Empty(); - if (annotation.Name == SqlServerAnnotationNames.FillFactor) - { - return new MethodCallCodeFragment(nameof(SqlServerIndexBuilderExtensions.HasFillFactor), annotation.Value); + default: + throw new ArgumentOutOfRangeException(); } - return null; + T GetAndRemove(string annotationName) + { + if (annotations.TryGetValue(annotationName, out var annotation) + && annotation.Value != null) + { + annotations.Remove(annotationName); + return (T)annotation.Value; + } + + return default; + } } } } diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs index c9a1c1d54dd..5d668cf7cf4 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationsGeneratorTest.cs @@ -16,6 +16,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations.Internal; using Microsoft.EntityFrameworkCore.Migrations.Operations; +using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; @@ -117,8 +118,9 @@ public void Test_new_annotations_handled_for_entity_types() RelationalAnnotationNames.Comment, ("My Comment", _toTable + _nl - + "modelBuilder.HasComment" - + @"(""My Comment"");" + + "modelBuilder" + + _nl + + @" .HasComment(""My Comment"");" + _nl) } }; @@ -184,7 +186,7 @@ public void Test_new_annotations_handled_for_properties() }, { RelationalAnnotationNames.ColumnName, - ("MyColumn", $@"{_nl}.{nameof(RelationalPropertyBuilderExtensions.HasColumnName)}(""MyColumn""){columnMapping}") + ("MyColumn", $@"{columnMapping}{_nl}.{nameof(RelationalPropertyBuilderExtensions.HasColumnName)}(""MyColumn"")") }, { RelationalAnnotationNames.ColumnType, @@ -238,11 +240,14 @@ private static void MissingAnnotationCheck( TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()); + var sqlServerAnnotationCodeGenerator = new SqlServerAnnotationCodeGenerator( + new AnnotationCodeGeneratorDependencies(sqlServerTypeMappingSource)); + var codeHelper = new CSharpHelper( sqlServerTypeMappingSource); var generator = new TestCSharpSnapshotGenerator( - new CSharpSnapshotGeneratorDependencies(codeHelper, sqlServerTypeMappingSource)); + new CSharpSnapshotGeneratorDependencies(codeHelper, sqlServerTypeMappingSource, sqlServerAnnotationCodeGenerator)); var coreAnnotations = typeof(CoreAnnotationNames).GetFields().Where(f => f.FieldType == typeof(string)).ToList(); @@ -327,8 +332,13 @@ public void Snapshot_with_enum_discriminator_uses_converted_values() var codeHelper = new CSharpHelper( sqlServerTypeMappingSource); + var sqlServerAnnotationCodeGenerator = new SqlServerAnnotationCodeGenerator( + new AnnotationCodeGeneratorDependencies(sqlServerTypeMappingSource)); + var generator = new CSharpMigrationsGenerator( - new MigrationsCodeGeneratorDependencies(sqlServerTypeMappingSource), + new MigrationsCodeGeneratorDependencies( + sqlServerTypeMappingSource, + sqlServerAnnotationCodeGenerator), new CSharpMigrationsGeneratorDependencies( codeHelper, new CSharpMigrationOperationGenerator( @@ -336,7 +346,7 @@ public void Snapshot_with_enum_discriminator_uses_converted_values() codeHelper)), new CSharpSnapshotGenerator( new CSharpSnapshotGeneratorDependencies( - codeHelper, sqlServerTypeMappingSource)))); + codeHelper, sqlServerTypeMappingSource, sqlServerAnnotationCodeGenerator)))); var modelBuilder = RelationalTestHelpers.Instance.CreateConventionBuilder(); modelBuilder.Model.RemoveAnnotation(CoreAnnotationNames.ProductVersion); @@ -378,8 +388,12 @@ private static void AssertConverter(ValueConverter valueConverter, string expect var codeHelper = new CSharpHelper(sqlServerTypeMappingSource); + var sqlServerAnnotationCodeGenerator = new SqlServerAnnotationCodeGenerator( + new AnnotationCodeGeneratorDependencies(sqlServerTypeMappingSource)); + var generator = new TestCSharpSnapshotGenerator( - new CSharpSnapshotGeneratorDependencies(codeHelper, sqlServerTypeMappingSource)); + new CSharpSnapshotGeneratorDependencies( + codeHelper, sqlServerTypeMappingSource, sqlServerAnnotationCodeGenerator)); var sb = new IndentedStringBuilder(); diff --git a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs index d17d07bbe35..7c6f3add1b2 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/MigrationScaffolderTest.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; +using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -13,6 +14,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; @@ -70,6 +72,8 @@ private IMigrationsScaffolder CreateMigrationScaffolder() var sqlServerTypeMappingSource = new SqlServerTypeMappingSource( TestServiceFactory.Instance.Create(), TestServiceFactory.Instance.Create()); + var sqlServerAnnotationCodeGenerator = new SqlServerAnnotationCodeGenerator( + new AnnotationCodeGeneratorDependencies(sqlServerTypeMappingSource)); var code = new CSharpHelper(sqlServerTypeMappingSource); var reporter = new TestOperationReporter(); var migrationAssembly @@ -103,7 +107,9 @@ var migrationAssembly new[] { new CSharpMigrationsGenerator( - new MigrationsCodeGeneratorDependencies(sqlServerTypeMappingSource), + new MigrationsCodeGeneratorDependencies( + sqlServerTypeMappingSource, + sqlServerAnnotationCodeGenerator), new CSharpMigrationsGeneratorDependencies( code, new CSharpMigrationOperationGenerator( @@ -111,7 +117,7 @@ var migrationAssembly code)), new CSharpSnapshotGenerator( new CSharpSnapshotGeneratorDependencies( - code, sqlServerTypeMappingSource)))) + code, sqlServerTypeMappingSource, sqlServerAnnotationCodeGenerator)))) }), historyRepository, reporter, diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 17a4dc0031a..80d5d293dfe 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; @@ -16,6 +17,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Migrations.Design; using Microsoft.EntityFrameworkCore.Migrations.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; using Microsoft.EntityFrameworkCore.Storage; @@ -278,15 +280,15 @@ public virtual void Model_annotations_are_stored_in_snapshot() AddBoilerPlate( @" modelBuilder + .UseIdentityColumns() .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(""SqlServer:ServiceTierSql"", ""'basic'"");"), o => { - Assert.Equal(7, o.GetAnnotations().Count()); + Assert.Equal(9, o.GetAnnotations().Count()); Assert.Equal("AnnotationValue", o["AnnotationName"]); }); } @@ -304,12 +306,12 @@ public virtual void Model_default_schema_annotation_is_stored_in_snapshot_as_flu @" modelBuilder .HasDefaultSchema(""DefaultSchema"") + .UseIdentityColumns() .HasAnnotation(""AnnotationName"", ""AnnotationValue"") - .HasAnnotation(""Relational:MaxIdentifierLength"", 128) - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);"), + .HasAnnotation(""Relational:MaxIdentifierLength"", 128);"), o => { - Assert.Equal(5, o.GetAnnotations().Count()); + Assert.Equal(7, o.GetAnnotations().Count()); Assert.Equal("AnnotationValue", o["AnnotationName"]); Assert.Equal("DefaultSchema", o[RelationalAnnotationNames.DefaultSchema]); }); @@ -332,7 +334,7 @@ public virtual void Entities_are_stored_in_model_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.HasKey(""Id""); @@ -344,7 +346,7 @@ public virtual void Entities_are_stored_in_model_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -383,7 +385,7 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Discriminator"") .HasColumnType(""nvarchar(max)""); @@ -404,7 +406,7 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT() });"), o => { - Assert.Equal(3, o.GetAnnotations().Count()); + Assert.Equal(5, o.GetAnnotations().Count()); Assert.Equal("DerivedEntity", o.FindEntityType("Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+DerivedEntity").GetTableName()); @@ -444,7 +446,7 @@ public virtual void Sequence_is_stored_in_snapshot_as_fluent_api() .IsCyclic();"), o => { - Assert.Equal(4, o.GetAnnotations().Count()); + Assert.Equal(6, o.GetAnnotations().Count()); }); } @@ -466,7 +468,7 @@ public virtual void CheckConstraint_is_stored_in_snapshot_as_fluent_api() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -479,7 +481,7 @@ public virtual void CheckConstraint_is_stored_in_snapshot_as_fluent_api() });"), o => { - Assert.Equal(3, o.GetAnnotations().Count()); + Assert.Equal(5, o.GetAnnotations().Count()); }); } @@ -501,7 +503,7 @@ public virtual void CheckConstraint_is_only_stored_in_snapshot_once_for_TPH() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Discriminator"") .IsRequired() @@ -527,7 +529,7 @@ public virtual void CheckConstraint_is_only_stored_in_snapshot_once_for_TPH() });"), o => { - Assert.Equal(3, o.GetAnnotations().Count()); + Assert.Equal(5, o.GetAnnotations().Count()); }); } @@ -552,13 +554,14 @@ public virtual void EntityType_annotations_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.HasKey(""Id""); b.ToTable(""EntityWithOneProperty""); - b.HasAnnotation(""AnnotationName"", ""AnnotationValue""); + b + .HasAnnotation(""AnnotationName"", ""AnnotationValue""); });"), o => { @@ -584,7 +587,7 @@ public virtual void BaseType_is_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Discriminator"") .IsRequired() @@ -652,7 +655,7 @@ public virtual void Discriminator_annotations_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Discriminator"") .IsRequired() @@ -712,7 +715,7 @@ public virtual void Properties_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -810,7 +813,7 @@ public virtual void Alternate_keys_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -848,7 +851,7 @@ public virtual void Indexes_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -884,7 +887,7 @@ public virtual void Indexes_are_stored_in_snapshot_including_composite_index() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -925,7 +928,7 @@ public virtual void Foreign_keys_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.HasKey(""Id""); @@ -937,7 +940,7 @@ public virtual void Foreign_keys_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -1060,7 +1063,7 @@ public virtual void AlternateKey_name_preserved_when_generic() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Property"") .HasColumnType(""uniqueidentifier""); @@ -1097,7 +1100,7 @@ public virtual void Discriminator_of_enum() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Day"") .HasColumnType(""bigint""); @@ -1129,7 +1132,7 @@ public virtual void Discriminator_of_enum_to_string() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Day"") .IsRequired() @@ -1199,7 +1202,7 @@ public virtual void Owned_types_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.HasKey(""Id"") .HasName(""PK_Custom""); @@ -1230,7 +1233,7 @@ public virtual void Owned_types_are_stored_in_snapshot() b1.Property(""AlternateId"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b1.Property(""EntityWithStringKeyId"") .HasColumnType(""nvarchar(450)""); @@ -1273,7 +1276,7 @@ public virtual void Owned_types_are_stored_in_snapshot() b1.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b1.Property(""EntityWithOnePropertyId"") .HasColumnType(""int""); @@ -1376,7 +1379,7 @@ public virtual void Weak_owned_types_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.HasKey(""Id""); @@ -1390,7 +1393,7 @@ public virtual void Weak_owned_types_are_stored_in_snapshot() b1.Property(""OrderId"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b1.HasKey(""OrderId""); @@ -1404,7 +1407,7 @@ public virtual void Weak_owned_types_are_stored_in_snapshot() b2.Property(""OrderDetailsOrderId"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b2.Property(""City"") .HasColumnType(""nvarchar(max)""); @@ -1423,7 +1426,7 @@ public virtual void Weak_owned_types_are_stored_in_snapshot() b1.Property(""OrderId"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b1.HasKey(""OrderId""); @@ -1437,7 +1440,7 @@ public virtual void Weak_owned_types_are_stored_in_snapshot() b2.Property(""OrderDetailsOrderId"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b2.Property(""City"") .HasColumnType(""nvarchar(max)""); @@ -1456,7 +1459,7 @@ public virtual void Weak_owned_types_are_stored_in_snapshot() b1.Property(""OrderId"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b1.HasKey(""OrderId""); @@ -1470,7 +1473,7 @@ public virtual void Weak_owned_types_are_stored_in_snapshot() b2.Property(""OrderInfoOrderId"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b2.Property(""City"") .HasColumnType(""nvarchar(max)""); @@ -1539,15 +1542,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation(""Relational:MaxIdentifierLength"", 128) - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumns() + .HasAnnotation(""Relational:MaxIdentifierLength"", 128); modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+TestOwner"", b => { b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.HasKey(""Id""); @@ -1564,7 +1567,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) b1.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b1.Property(""TestEnum"") .HasColumnType(""int""); @@ -1641,8 +1644,8 @@ public virtual void Property_annotations_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""AnnotationName"", ""AnnotationValue"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn() + .HasAnnotation(""AnnotationName"", ""AnnotationValue""); b.HasKey(""Id""); @@ -1669,7 +1672,7 @@ public virtual void Custom_value_generator_is_ignored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.HasKey(""Id""); @@ -1692,7 +1695,7 @@ public virtual void Property_isNullable_is_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Name"") .IsRequired() @@ -1722,7 +1725,7 @@ public virtual void Property_ValueGenerated_value_is_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .ValueGeneratedOnAdd() @@ -1749,7 +1752,7 @@ public virtual void Property_maxLength_is_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Name"") .HasColumnType(""nvarchar(100)"") @@ -1775,7 +1778,7 @@ public virtual void Property_unicodeness_is_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Name"") .HasColumnType(""varchar(max)"") @@ -1801,7 +1804,7 @@ public virtual void Property_fixedlengthness_is_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Name"") .HasColumnType(""nchar(100)"") @@ -1835,7 +1838,7 @@ public virtual void Many_facets_chained_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Name"") .HasColumnType(""varchar(100)"") @@ -1873,7 +1876,7 @@ public virtual void Property_concurrencyToken_is_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .IsConcurrencyToken() @@ -1903,11 +1906,11 @@ public virtual void Property_column_name_annotation_is_stored_in_snapshot_as_flu b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") - .HasColumnName(""CName"") - .HasColumnType(""int""); + .HasColumnType(""int"") + .HasColumnName(""CName""); b.HasKey(""Id""); @@ -1933,7 +1936,7 @@ public virtual void Property_column_type_annotation_is_stored_in_snapshot_as_flu b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""CType""); @@ -1962,7 +1965,7 @@ public virtual void Property_default_value_annotation_is_stored_in_snapshot_as_f b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .ValueGeneratedOnAdd() @@ -1993,7 +1996,7 @@ public virtual void Property_default_value_sql_annotation_is_stored_in_snapshot_ b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .ValueGeneratedOnAdd() @@ -2024,7 +2027,7 @@ public virtual void Property_computed_column_sql_annotation_is_stored_in_snapsho b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .ValueGeneratedOnAddOrUpdate() @@ -2051,7 +2054,7 @@ 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); + .UseIdentityColumn(); b.Property(""Day"") .ValueGeneratedOnAdd() @@ -2085,7 +2088,7 @@ public virtual void Property_enum_type_is_stored_in_snapshot_with_custom_convers b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Day"") .IsRequired() @@ -2126,7 +2129,7 @@ public virtual void Property_of_nullable_enum() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Day"") .HasColumnType(""bigint""); @@ -2152,7 +2155,7 @@ public virtual void Property_of_enum_to_nullable() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Day"") .HasColumnType(""bigint""); @@ -2177,7 +2180,7 @@ public virtual void Property_of_nullable_enum_to_string() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Day"") .HasColumnType(""nvarchar(max)""); @@ -2207,11 +2210,11 @@ public virtual void Property_multiple_annotations_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") - .HasColumnName(""CName"") .HasColumnType(""int"") + .HasColumnName(""CName"") .HasAnnotation(""AnnotationName"", ""AnnotationValue""); b.HasKey(""Id""); @@ -2254,14 +2257,14 @@ public virtual void Property_without_column_type() AddBoilerPlate( @" modelBuilder - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumns(); modelBuilder.Entity(""Building"", b => { b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.HasKey(""Id""); @@ -2296,7 +2299,7 @@ public virtual void Key_annotations_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -2329,7 +2332,7 @@ public virtual void Key_name_annotation_is_stored_in_snapshot_as_fluent_api() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -2363,7 +2366,7 @@ public virtual void Key_multiple_annotations_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -2407,7 +2410,7 @@ public virtual void Index_annotations_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -2439,7 +2442,7 @@ public virtual void Index_isUnique_is_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -2473,7 +2476,7 @@ public virtual void Index_database_name_annotation_is_stored_in_snapshot_as_flue b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -2511,7 +2514,7 @@ public virtual void Index_filter_is_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -2546,7 +2549,7 @@ public virtual void Index_multiple_annotations_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -2588,7 +2591,7 @@ public virtual void Index_with_default_constraint_name_exceeding_max() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Name"") .HasColumnType(""nvarchar(max)""); @@ -2618,7 +2621,7 @@ public virtual void IndexAttribute_causes_column_to_have_key_or_index_column_len b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""FirstName"") .HasColumnType(""nvarchar(450)""); @@ -2661,7 +2664,7 @@ public virtual void IndexAttribute_name_is_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""FirstName"") .HasColumnType(""nvarchar(450)""); @@ -2709,7 +2712,7 @@ public virtual void IndexAttribute_IsUnique_is_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""FirstName"") .HasColumnType(""nvarchar(450)""); @@ -2769,7 +2772,7 @@ public virtual void ForeignKey_annotations_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.HasKey(""Id""); @@ -2781,7 +2784,7 @@ public virtual void ForeignKey_annotations_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -2838,7 +2841,7 @@ public virtual void ForeignKey_isRequired_is_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Name"") .IsRequired() @@ -2892,7 +2895,7 @@ public virtual void ForeignKey_isUnique_is_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Name"") .HasColumnType(""nvarchar(450)""); @@ -2943,7 +2946,7 @@ public virtual void ForeignKey_deleteBehavior_is_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -2994,7 +2997,7 @@ public virtual void ForeignKey_deleteBehavior_is_stored_in_snapshot_for_one_to_o b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -3048,7 +3051,7 @@ public virtual void ForeignKey_name_preserved_when_generic() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Property"") .HasColumnType(""uniqueidentifier""); @@ -3116,7 +3119,7 @@ public virtual void ForeignKey_constraint_name_is_stored_in_snapshot_as_fluent_a b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.HasKey(""Id""); @@ -3128,7 +3131,7 @@ public virtual void ForeignKey_constraint_name_is_stored_in_snapshot_as_fluent_a b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -3175,7 +3178,7 @@ public virtual void ForeignKey_multiple_annotations_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.HasKey(""Id""); @@ -3187,7 +3190,7 @@ public virtual void ForeignKey_multiple_annotations_are_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -3237,7 +3240,7 @@ 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); + .UseIdentityColumn(); b.Property(""Discriminator"") .IsRequired() @@ -3260,7 +3263,7 @@ 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); + .UseIdentityColumn(); b.HasKey(""Id""); @@ -3313,7 +3316,7 @@ public virtual void ForeignKey_principal_key_is_stored_in_snapshot() b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -3371,7 +3374,7 @@ public virtual void ForeignKey_principal_key_with_non_default_name_is_stored_in_ b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -3563,15 +3566,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation(""Relational:MaxIdentifierLength"", 128) - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumns() + .HasAnnotation(""Relational:MaxIdentifierLength"", 128); modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithManyProperties"", b => { b.Property(""Id"") .ValueGeneratedOnAdd() .HasColumnType(""int"") - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + .UseIdentityColumn(); b.Property(""Boolean"") .HasColumnType(""bit""); @@ -3908,8 +3911,8 @@ protected override void BuildModel(ModelBuilder modelBuilder) protected virtual string GetHeading(bool empty = false) => @" modelBuilder - .HasAnnotation(""Relational:MaxIdentifierLength"", 128) - .HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn);" + .UseIdentityColumns() + .HasAnnotation(""Relational:MaxIdentifierLength"", 128);" + (empty ? null : @" @@ -3974,11 +3977,17 @@ protected void Test(IModel model, string expectedCode, Action as { new SqlServerNetTopologySuiteTypeMappingSourcePlugin(NtsGeometryServices.Instance) })); + var codeHelper = new CSharpHelper( sqlServerTypeMappingSource); + var sqlServerAnnotationCodeGenerator = new SqlServerAnnotationCodeGenerator( + new AnnotationCodeGeneratorDependencies(sqlServerTypeMappingSource)); + var generator = new CSharpMigrationsGenerator( - new MigrationsCodeGeneratorDependencies(sqlServerTypeMappingSource), + new MigrationsCodeGeneratorDependencies( + sqlServerTypeMappingSource, + sqlServerAnnotationCodeGenerator), new CSharpMigrationsGeneratorDependencies( codeHelper, new CSharpMigrationOperationGenerator( @@ -3986,7 +3995,7 @@ protected void Test(IModel model, string expectedCode, Action as codeHelper)), new CSharpSnapshotGenerator( new CSharpSnapshotGeneratorDependencies( - codeHelper, sqlServerTypeMappingSource)))); + codeHelper, sqlServerTypeMappingSource, sqlServerAnnotationCodeGenerator)))); var code = generator.GenerateSnapshot("RootNamespace", typeof(DbContext), "Snapshot", model); Assert.Equal(expectedCode, code, ignoreLineEndingDifferences: true); diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs index d15c2fa3a2c..4abc30fc313 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs @@ -422,7 +422,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasFilter(""Filter SQL"") .HasAnnotation(""AnnotationName"", ""AnnotationValue""); - entity.Property(e => e.Id).HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + entity.Property(e => e.Id).UseIdentityColumn(); }); OnModelCreatingPartial(modelBuilder); @@ -503,7 +503,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) .HasFilter(""Filter SQL"") .HasAnnotation(""AnnotationName"", ""AnnotationValue""); - entity.Property(e => e.Id).HasAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); + entity.Property(e => e.Id).UseIdentityColumn(); }); OnModelCreatingPartial(modelBuilder); diff --git a/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs b/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs index 6b8edaaff4f..91986b82eb1 100644 --- a/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs +++ b/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs @@ -1,10 +1,14 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Linq; using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Xunit; namespace Microsoft.EntityFrameworkCore.Design.Internal @@ -14,7 +18,8 @@ public class SqlServerAnnotationCodeGeneratorTest [ConditionalFact] public void GenerateFluentApi_IKey_works_when_clustered() { - var generator = new SqlServerAnnotationCodeGenerator(new AnnotationCodeGeneratorDependencies()); + var generator = CreateGenerator(); + var modelBuilder = new ModelBuilder(SqlServerConventionSetBuilder.Build()); modelBuilder.Entity( "Post", @@ -36,7 +41,7 @@ public void GenerateFluentApi_IKey_works_when_clustered() [ConditionalFact] public void GenerateFluentApi_IKey_works_when_nonclustered() { - var generator = new SqlServerAnnotationCodeGenerator(new AnnotationCodeGeneratorDependencies()); + var generator = CreateGenerator(); var modelBuilder = new ModelBuilder(SqlServerConventionSetBuilder.Build()); modelBuilder.Entity( "Post", @@ -59,7 +64,7 @@ public void GenerateFluentApi_IKey_works_when_nonclustered() [ConditionalFact] public void GenerateFluentApi_IIndex_works_when_clustered() { - var generator = new SqlServerAnnotationCodeGenerator(new AnnotationCodeGeneratorDependencies()); + var generator = CreateGenerator(); var modelBuilder = new ModelBuilder(SqlServerConventionSetBuilder.Build()); modelBuilder.Entity( "Post", @@ -82,7 +87,7 @@ public void GenerateFluentApi_IIndex_works_when_clustered() [ConditionalFact] public void GenerateFluentApi_IIndex_works_when_nonclustered() { - var generator = new SqlServerAnnotationCodeGenerator(new AnnotationCodeGeneratorDependencies()); + var generator = CreateGenerator(); var modelBuilder = new ModelBuilder(SqlServerConventionSetBuilder.Build()); modelBuilder.Entity( "Post", @@ -106,7 +111,7 @@ public void GenerateFluentApi_IIndex_works_when_nonclustered() [ConditionalFact] public void GenerateFluentApi_IIndex_works_with_fillfactor() { - var generator = new SqlServerAnnotationCodeGenerator(new AnnotationCodeGeneratorDependencies()); + var generator = CreateGenerator(); var modelBuilder = new ModelBuilder(SqlServerConventionSetBuilder.Build()); modelBuilder.Entity( "Post", @@ -118,7 +123,7 @@ public void GenerateFluentApi_IIndex_works_with_fillfactor() }); var index = modelBuilder.Model.FindEntityType("Post").GetIndexes().Single(); - var annotation = index.FindAnnotation(SqlServerAnnotationNames.FillFactor); + var annotation = index.FindAnnotation(SqlServerAnnotationNames.FillFactor); var result = generator.GenerateFluentApi(index, annotation); Assert.Equal("HasFillFactor", result.Method); @@ -129,7 +134,7 @@ public void GenerateFluentApi_IIndex_works_with_fillfactor() [ConditionalFact] public void GenerateFluentApi_IIndex_works_with_includes() { - var generator = new SqlServerAnnotationCodeGenerator(new AnnotationCodeGeneratorDependencies()); + var generator = CreateGenerator(); var modelBuilder = new ModelBuilder(SqlServerConventionSetBuilder.Build()); modelBuilder.Entity( "Post", @@ -151,5 +156,86 @@ public void GenerateFluentApi_IIndex_works_with_includes() var properties = Assert.IsType(result.Arguments[0]); Assert.Equal(new[] { "FirstName" }, properties.AsEnumerable()); } + + [ConditionalFact] + public void GenerateFluentApi_IModel_works_with_identity() + { + var generator = CreateGenerator(); + var modelBuilder = new ModelBuilder(SqlServerConventionSetBuilder.Build()); + modelBuilder.UseIdentityColumns(seed: 5, increment: 10); + + var annotations = modelBuilder.Model.GetAnnotations().ToDictionary(a => a.Name, a => a); + var result = generator.GenerateFluentApiCalls(modelBuilder.Model, annotations).Single(); + + Assert.Equal("UseIdentityColumns", result.Method); + + Assert.Collection(result.Arguments, + seed => Assert.Equal(5, seed), + increment => Assert.Equal(10, increment)); + } + + [ConditionalFact] + public void GenerateFluentApi_IProperty_works_with_identity() + { + var generator = CreateGenerator(); + var modelBuilder = new ModelBuilder(SqlServerConventionSetBuilder.Build()); + modelBuilder.Entity("Post", x => x.Property("Id").UseIdentityColumn(5, 10)); + var property = modelBuilder.Model.FindEntityType("Post").FindProperty("Id"); + + var annotations = property.GetAnnotations().ToDictionary(a => a.Name, a => a); + var result = generator.GenerateFluentApiCalls(property, annotations).Single(); + + Assert.Equal("UseIdentityColumn", result.Method); + + Assert.Collection(result.Arguments, + seed => Assert.Equal(5, seed), + increment => Assert.Equal(10, increment)); + } + + [ConditionalFact] + public void GenerateFluentApi_IModel_works_with_HiLo() + { + var generator = CreateGenerator(); + var modelBuilder = new ModelBuilder(SqlServerConventionSetBuilder.Build()); + modelBuilder.UseHiLo("HiLoIndexName", "HiLoIndexSchema"); + + var annotations = modelBuilder.Model.GetAnnotations().ToDictionary(a => a.Name, a => a); + var result = generator.GenerateFluentApiCalls(modelBuilder.Model, annotations).Single(); + + Assert.Equal("UseHiLo", result.Method); + + Assert.Collection(result.Arguments, + name => Assert.Equal("HiLoIndexName", name), + schema => Assert.Equal("HiLoIndexSchema", schema)); + } + + [ConditionalFact] + public void GenerateFluentApi_IProperty_works_with_HiLo() + { + var generator = CreateGenerator(); + var modelBuilder = new ModelBuilder(SqlServerConventionSetBuilder.Build()); + modelBuilder.Entity("Post", x => x.Property("Id").UseHiLo("HiLoIndexName", "HiLoIndexSchema")); + var property = modelBuilder.Model.FindEntityType("Post").FindProperty("Id"); + + var annotations = property.GetAnnotations().ToDictionary(a => a.Name, a => a); + var result = generator.GenerateFluentApiCalls(property, annotations).Single(); + + Assert.Equal("UseHiLo", result.Method); + + Assert.Collection(result.Arguments, + name => Assert.Equal("HiLoIndexName", name), + schema => Assert.Equal("HiLoIndexSchema", schema)); + } + + private SqlServerAnnotationCodeGenerator CreateGenerator() + => new SqlServerAnnotationCodeGenerator( + new AnnotationCodeGeneratorDependencies( + new SqlServerTypeMappingSource( + new TypeMappingSourceDependencies( + new ValueConverterSelector( + new ValueConverterSelectorDependencies()), + Array.Empty()), + new RelationalTypeMappingSourceDependencies( + Array.Empty())))); } }