From c146f7a38e126f5001cd3383d4b70f250fd6bf3c Mon Sep 17 00:00:00 2001 From: Brice Lambson Date: Thu, 17 Feb 2022 10:00:18 -0800 Subject: [PATCH] Scaffolding: Refactor some code Part of #4038, part of #27588, resolves #8434, resolves #21882, part of #26349 --- .../DesignTimeServiceCollectionExtensions.cs | 8 - .../Design/Internal/CSharpHelper.cs | 224 +++++++++++--- .../Design/Internal/IOperationReporter.cs | 52 ++++ .../{Internal => }/NamespaceComparer.cs | 2 +- .../Internal/DatabaseColumnExtensions.cs | 0 .../Internal/DatabaseForeignKeyExtensions.cs | 0 .../Internal/DatabaseTableExtensions.cs | 0 .../InternalScaffoldingModelExtensions.cs} | 34 ++- .../Extensions/ScaffoldingModelExtensions.cs | 61 ++++ .../Internal/ScaffoldingAnnotationNames.cs | 2 +- .../ScaffoldingEntityTypeAnnotations.cs | 34 --- .../Design/CSharpSnapshotGenerator.cs | 104 +++---- .../Properties/DesignStrings.Designer.cs | 20 ++ .../Properties/DesignStrings.resx | 9 + .../Internal/CSharpDbContextGenerator.cs | 51 +--- .../Internal/CSharpEntityTypeGenerator.cs | 2 +- .../Internal/CSharpModelGenerator.cs | 32 +- .../Internal/ICSharpDbContextGenerator.cs | 30 -- .../Internal/ICSharpEntityTypeGenerator.cs | 21 -- .../RelationalScaffoldingModelFactory.cs | 4 +- .../Internal/TextTemplatingEngineHost.cs} | 104 ++++--- .../Internal/TextTemplatingModelGenerator.cs | 234 ++++++++------- .../Scaffolding/TemplatedModelGenerator.cs | 2 +- .../TextTemplating/ITextTemplating.cs | 21 -- .../TextTemplating/ITextTemplatingCallback.cs | 32 -- .../Internal/TextTemplatingCallback.cs | 65 ----- .../Design/AnnotationCodeGenerator.cs | 48 ++- ...nalCSharpRuntimeAnnotationCodeGenerator.cs | 16 +- .../SqlServerAnnotationCodeGenerator.cs | 12 +- src/EFCore/Design/AttributeCodeFragment.cs | 34 ++- src/EFCore/Design/ICSharpHelper.cs | 58 +++- src/EFCore/Design/MethodCallCodeFragment.cs | 61 +++- .../Infrastructure/IndentedStringBuilder.cs | 7 + .../Design/DesignTimeServicesTest.cs | 5 - .../Design/Internal/CSharpHelperTest.cs | 132 ++++++++- .../Migrations/ModelSnapshotSqlServerTest.cs | 274 +++++++++--------- .../Internal/CSharpDbContextGeneratorTest.cs | 50 +++- .../CSharpRuntimeModelCodeGeneratorTest.cs | 4 +- .../RelationalScaffoldingModelFactoryTest.cs | 2 +- .../ScaffoldingMetadataExtensionsTest.cs | 16 +- .../Internal/TextTemplatingEngineHostTest.cs | 162 +++++++++++ .../TextTemplatingModelGeneratorTest.cs | 153 +++++++++- .../Internal/TextTemplatingServiceTest.cs | 183 ------------ .../ApiConsistencyTestBase.cs | 2 + .../TestUtilities/TestOperationReporter.cs | 24 +- .../SqlServerAnnotationCodeGeneratorTest.cs | 5 +- .../Design/MethodCallCodeFragmentTest.cs | 9 +- 47 files changed, 1449 insertions(+), 956 deletions(-) rename src/EFCore.Design/Design/{Internal => }/NamespaceComparer.cs (93%) rename src/EFCore.Design/{Scaffolding/Metadata => Extensions}/Internal/DatabaseColumnExtensions.cs (100%) rename src/EFCore.Design/{Scaffolding/Metadata => Extensions}/Internal/DatabaseForeignKeyExtensions.cs (100%) rename src/EFCore.Design/{Scaffolding/Metadata => Extensions}/Internal/DatabaseTableExtensions.cs (100%) rename src/EFCore.Design/{Metadata/Internal/ScaffoldingModelExtensions.cs => Extensions/Internal/InternalScaffoldingModelExtensions.cs} (81%) create mode 100644 src/EFCore.Design/Extensions/ScaffoldingModelExtensions.cs delete mode 100644 src/EFCore.Design/Metadata/Internal/ScaffoldingEntityTypeAnnotations.cs delete mode 100644 src/EFCore.Design/Scaffolding/Internal/ICSharpDbContextGenerator.cs delete mode 100644 src/EFCore.Design/Scaffolding/Internal/ICSharpEntityTypeGenerator.cs rename src/EFCore.Design/{TextTemplating/Internal/TextTemplatingService.cs => Scaffolding/Internal/TextTemplatingEngineHost.cs} (79%) delete mode 100644 src/EFCore.Design/TextTemplating/ITextTemplating.cs delete mode 100644 src/EFCore.Design/TextTemplating/ITextTemplatingCallback.cs delete mode 100644 src/EFCore.Design/TextTemplating/Internal/TextTemplatingCallback.cs create mode 100644 test/EFCore.Design.Tests/Scaffolding/Internal/TextTemplatingEngineHostTest.cs delete mode 100644 test/EFCore.Design.Tests/TextTemplating/Internal/TextTemplatingServiceTest.cs diff --git a/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs b/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs index e198619bc55..60d4ef6301a 100644 --- a/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs +++ b/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs @@ -2,13 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.EntityFrameworkCore.Design.Internal; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Migrations.Design; using Microsoft.EntityFrameworkCore.Migrations.Internal; -using Microsoft.EntityFrameworkCore.Scaffolding; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; -using Microsoft.EntityFrameworkCore.TextTemplating; -using Microsoft.EntityFrameworkCore.TextTemplating.Internal; namespace Microsoft.EntityFrameworkCore.Design; @@ -45,8 +40,6 @@ public static IServiceCollection AddEntityFrameworkDesignTimeServices( .TryAddSingleton() .TryAddSingleton() .TryAddSingleton() - .TryAddSingleton() - .TryAddSingleton() .TryAddSingleton() .TryAddSingleton() .TryAddSingleton() @@ -54,7 +47,6 @@ public static IServiceCollection AddEntityFrameworkDesignTimeServices( .TryAddSingleton(reporter) .TryAddSingleton() .TryAddSingleton() - .TryAddSingleton() .TryAddSingletonEnumerable() .TryAddSingletonEnumerable() .TryAddSingleton() diff --git a/src/EFCore.Design/Design/Internal/CSharpHelper.cs b/src/EFCore.Design/Design/Internal/CSharpHelper.cs index 562fb412e2b..878df46c822 100644 --- a/src/EFCore.Design/Design/Internal/CSharpHelper.cs +++ b/src/EFCore.Design/Design/Internal/CSharpHelper.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Globalization; using System.Numerics; +using System.Security; using System.Text; using Microsoft.EntityFrameworkCore.Internal; @@ -131,7 +132,7 @@ public CSharpHelper(ITypeMappingSource typeMappingSource) { typeof(Guid), (c, v) => c.Literal((Guid)v) }, { typeof(int), (c, v) => c.Literal((int)v) }, { typeof(long), (c, v) => c.Literal((long)v) }, - { typeof(NestedClosureCodeFragment), (c, v) => c.Fragment((NestedClosureCodeFragment)v, 0) }, + { typeof(NestedClosureCodeFragment), (c, v) => c.Fragment((NestedClosureCodeFragment)v) }, { typeof(object[]), (c, v) => c.Literal((object[])v) }, { typeof(object[,]), (c, v) => c.Literal((object[,])v) }, { typeof(sbyte), (c, v) => c.Literal((sbyte)v) }, @@ -195,7 +196,7 @@ public virtual string Reference(Type type, bool? fullName = null) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool ShouldUseFullName(Type type) + protected virtual bool ShouldUseFullName(Type type) => ShouldUseFullName(type.Name); /// @@ -204,7 +205,7 @@ public virtual bool ShouldUseFullName(Type type) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual bool ShouldUseFullName(string shortTypeName) + protected virtual bool ShouldUseFullName(string shortTypeName) => false; /// @@ -310,9 +311,11 @@ public virtual string Namespace(params string[] name) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual string Literal(string value) + public virtual string Literal(string? value) // do not use @"" syntax as in Migrations this can get indented at a newline and so add spaces to the literal - => "\"" + value.Replace(@"\", @"\\").Replace("\"", "\\\"").Replace("\n", @"\n").Replace("\r", @"\r") + "\""; + => value is not null + ? "\"" + value.Replace(@"\", @"\\").Replace("\"", "\\\"").Replace("\n", @"\n").Replace("\r", @"\r") + "\"" + : "null"; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1084,13 +1087,9 @@ private bool HandleList(IEnumerable argumentExpressions, StringBuild /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual string Fragment(MethodCallCodeFragment fragment, string? instanceIdentifier = null, bool typeQualified = false) - => Fragment(fragment, typeQualified, instanceIdentifier, indent: 0); - - private string Fragment(MethodCallCodeFragment fragment, bool typeQualified, string? instanceIdentifier, int indent) + public virtual string Fragment(MethodCallCodeFragment fragment, string? instanceIdentifier, bool typeQualified) { - var builder = new IndentedStringBuilder(); - var current = fragment; + var builder = new StringBuilder(); if (typeQualified) { @@ -1109,7 +1108,15 @@ private string Fragment(MethodCallCodeFragment fragment, bool typeQualified, str for (var i = 0; i < fragment.Arguments.Count; i++) { builder.Append(", "); - Argument(fragment.Arguments[i]); + + if (fragment.Arguments[i] is NestedClosureCodeFragment nestedFragment) + { + builder.Append(Fragment(nestedFragment, 1)); + } + else + { + builder.Append(UnknownLiteral(fragment.Arguments[i])); + } } builder.Append(')'); @@ -1117,21 +1124,56 @@ private string Fragment(MethodCallCodeFragment fragment, bool typeQualified, str return builder.ToString(); } - // Non-type-qualified fragment - if (instanceIdentifier is not null) { builder.Append(instanceIdentifier); + } + + builder.Append(Fragment(fragment, indent: 1)); - if (current.ChainedCall is not null) + return builder.ToString(); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string Fragment(MethodCallCodeFragment? fragment, int indent = 0) + { + if (fragment is null) + { + return string.Empty; + } + + var builder = new IndentedStringBuilder(); + + if (fragment.ChainedCall is null) + { + AppendMethodCall(fragment); + } + else + { + for (var i = 0; i < indent; i++) { - builder - .AppendLine() - .IncrementIndent(); + builder.IncrementIndent(); } + + var current = fragment; + do + { + builder.AppendLine(); + AppendMethodCall(current); + + current = current.ChainedCall; + } + while (current is not null); } - while (true) + return builder.ToString(); + + void AppendMethodCall(MethodCallCodeFragment current) { builder .Append('.') @@ -1145,45 +1187,37 @@ private string Fragment(MethodCallCodeFragment fragment, bool typeQualified, str builder.Append(", "); } - Argument(current.Arguments[i]); + if (current.Arguments[i] is NestedClosureCodeFragment nestedFragment) + { + builder.Append(Fragment(nestedFragment, indent + 1)); + } + else + { + builder.Append(UnknownLiteral(current.Arguments[i])); + } } builder.Append(')'); - if (current.ChainedCall is null) - { - break; - } - - builder.AppendLine(); - current = current.ChainedCall; - } - - return builder.ToString(); - - void Argument(object? argument) - { - if (argument is NestedClosureCodeFragment nestedFragment) - { - builder.Append(Fragment(nestedFragment, indent)); - } - else - { - builder.Append(UnknownLiteral(argument)); - } } } - private string Fragment(NestedClosureCodeFragment fragment, int indent) + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string Fragment(NestedClosureCodeFragment fragment, int indent = 0) { if (fragment.MethodCalls.Count == 1) { - return fragment.Parameter + " => " + Fragment(fragment.MethodCalls[0], typeQualified: false, fragment.Parameter, indent); + return fragment.Parameter + " => " + fragment.Parameter + Fragment(fragment.MethodCalls[0], indent); } var builder = new IndentedStringBuilder(); builder.AppendLine(fragment.Parameter + " =>"); - for (var i = -1; i < indent; i++) + for (var i = 0; i < indent - 1; i++) { builder.IncrementIndent(); } @@ -1193,12 +1227,110 @@ private string Fragment(NestedClosureCodeFragment fragment, int indent) { foreach (var methodCall in fragment.MethodCalls) { - builder.AppendLines(Fragment(methodCall, typeQualified: false, fragment.Parameter, indent + 1), skipFinalNewline: true); + builder + .Append(fragment.Parameter) + .Append(Fragment(methodCall, indent + 1)); builder.AppendLine(";"); } } - builder.AppendLine("}"); + builder.Append("}"); + + return builder.ToString(); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string Fragment(AttributeCodeFragment fragment) + { + var builder = new StringBuilder(); + + var attributeName = fragment.Type.Name; + if (attributeName.EndsWith("Attribute", StringComparison.Ordinal)) + { + attributeName = attributeName[..^9]; + } + + builder + .Append("[") + .Append(attributeName); + + if (fragment.Arguments.Count != 0 + || fragment.NamedArguments.Count != 0) + { + builder.Append("("); + + var first = true; + foreach (var value in fragment.Arguments) + { + if (!first) + { + builder.Append(", "); + } + else + { + first = false; + } + + builder.Append(UnknownLiteral(value)); + } + + foreach (var item in fragment.NamedArguments) + { + if (!first) + { + builder.Append(", "); + } + else + { + first = false; + } + + builder + .Append(item.Key) + .Append(" = ") + .Append(UnknownLiteral(item.Value)); + } + + builder.Append(")"); + } + + builder.Append("]"); + + return builder.ToString(); + } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual string XmlComment(string comment, int indent = 0) + { + var builder = new StringBuilder(); + + var first = true; + foreach (var line in comment.Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.None)) + { + if (!first) + { + builder + .AppendLine() + .Append(' ', indent * 4) + .Append("/// "); + } + else + { + first = false; + } + + builder.Append(SecurityElement.Escape(line)); + } return builder.ToString(); } diff --git a/src/EFCore.Design/Design/Internal/IOperationReporter.cs b/src/EFCore.Design/Design/Internal/IOperationReporter.cs index 5bbaf3991eb..6ec8de9b69b 100644 --- a/src/EFCore.Design/Design/Internal/IOperationReporter.cs +++ b/src/EFCore.Design/Design/Internal/IOperationReporter.cs @@ -1,6 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.CodeDom.Compiler; +using System.Text; + namespace Microsoft.EntityFrameworkCore.Design.Internal; /// @@ -42,4 +45,53 @@ public interface IOperationReporter /// doing so can result in application failures when updating to a new Entity Framework Core release. /// void WriteVerbose(string message); + + /// + /// 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. + /// + void Write(CompilerError error) + { + var builder = new StringBuilder(); + + if (!string.IsNullOrEmpty(error.FileName)) + { + builder.Append(error.FileName); + + if (error.Line > 0) + { + builder + .Append("(") + .Append(error.Line); + + if (error.Column > 0) + { + builder + .Append(",") + .Append(error.Column); + } + builder.Append(")"); + } + + builder.Append(" : "); + } + + builder + .Append(error.IsWarning ? "warning" : "error") + .Append(" ") + .Append(error.ErrorNumber) + .Append(": ") + .AppendLine(error.ErrorText); + + if (error.IsWarning) + { + WriteWarning(builder.ToString()); + } + else + { + WriteError(builder.ToString()); + } + } } diff --git a/src/EFCore.Design/Design/Internal/NamespaceComparer.cs b/src/EFCore.Design/Design/NamespaceComparer.cs similarity index 93% rename from src/EFCore.Design/Design/Internal/NamespaceComparer.cs rename to src/EFCore.Design/Design/NamespaceComparer.cs index 0f4a21235b8..64265c59bd8 100644 --- a/src/EFCore.Design/Design/Internal/NamespaceComparer.cs +++ b/src/EFCore.Design/Design/NamespaceComparer.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.Design.Internal; +namespace Microsoft.EntityFrameworkCore.Design; /// /// A custom string comparer to sort using statements to have System prefixed namespaces first. diff --git a/src/EFCore.Design/Scaffolding/Metadata/Internal/DatabaseColumnExtensions.cs b/src/EFCore.Design/Extensions/Internal/DatabaseColumnExtensions.cs similarity index 100% rename from src/EFCore.Design/Scaffolding/Metadata/Internal/DatabaseColumnExtensions.cs rename to src/EFCore.Design/Extensions/Internal/DatabaseColumnExtensions.cs diff --git a/src/EFCore.Design/Scaffolding/Metadata/Internal/DatabaseForeignKeyExtensions.cs b/src/EFCore.Design/Extensions/Internal/DatabaseForeignKeyExtensions.cs similarity index 100% rename from src/EFCore.Design/Scaffolding/Metadata/Internal/DatabaseForeignKeyExtensions.cs rename to src/EFCore.Design/Extensions/Internal/DatabaseForeignKeyExtensions.cs diff --git a/src/EFCore.Design/Scaffolding/Metadata/Internal/DatabaseTableExtensions.cs b/src/EFCore.Design/Extensions/Internal/DatabaseTableExtensions.cs similarity index 100% rename from src/EFCore.Design/Scaffolding/Metadata/Internal/DatabaseTableExtensions.cs rename to src/EFCore.Design/Extensions/Internal/DatabaseTableExtensions.cs diff --git a/src/EFCore.Design/Metadata/Internal/ScaffoldingModelExtensions.cs b/src/EFCore.Design/Extensions/Internal/InternalScaffoldingModelExtensions.cs similarity index 81% rename from src/EFCore.Design/Metadata/Internal/ScaffoldingModelExtensions.cs rename to src/EFCore.Design/Extensions/Internal/InternalScaffoldingModelExtensions.cs index 987fb2d4d67..187d240c288 100644 --- a/src/EFCore.Design/Metadata/Internal/ScaffoldingModelExtensions.cs +++ b/src/EFCore.Design/Extensions/Internal/InternalScaffoldingModelExtensions.cs @@ -9,7 +9,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public static class ScaffoldingModelExtensions +public static class InternalScaffoldingModelExtensions { /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -17,22 +17,13 @@ public static class ScaffoldingModelExtensions /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public static IReadOnlyDictionary GetEntityTypeErrors(this IReadOnlyModel model) - => (IReadOnlyDictionary?)model[ScaffoldingAnnotationNames.EntityTypeErrors] ?? new Dictionary(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public static IDictionary GetOrCreateEntityTypeErrors(this IReadOnlyModel model) + public static ICollection GetOrCreateReverseEngineeringErrors(this IMutableModel model) { - var errors = (IDictionary?)model[ScaffoldingAnnotationNames.EntityTypeErrors]; + var errors = (ICollection?)model[ScaffoldingAnnotationNames.ReverseEngineeringErrors]; if (errors == null) { - errors = new Dictionary(); - (model as IMutableModel)?.SetEntityTypeErrors(errors); + errors = new List(); + model.SetReverseEngineeringErrors(errors); } return errors; @@ -44,9 +35,9 @@ public static IDictionary GetOrCreateEntityTypeErrors(this IRead /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public static void SetEntityTypeErrors(this IMutableModel model, IDictionary value) + public static void SetReverseEngineeringErrors(this IMutableModel model, ICollection value) => model.SetAnnotation( - ScaffoldingAnnotationNames.EntityTypeErrors, + ScaffoldingAnnotationNames.ReverseEngineeringErrors, value); /// @@ -68,4 +59,15 @@ public static void SetDatabaseName(this IMutableModel model, string? value) => model.SetAnnotation( ScaffoldingAnnotationNames.DatabaseName, value); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static void SetDbSetName(this IMutableEntityType entityType, string? value) + => entityType.SetAnnotation( + ScaffoldingAnnotationNames.DbSetName, + value); } diff --git a/src/EFCore.Design/Extensions/ScaffoldingModelExtensions.cs b/src/EFCore.Design/Extensions/ScaffoldingModelExtensions.cs new file mode 100644 index 00000000000..e73e3bf13eb --- /dev/null +++ b/src/EFCore.Design/Extensions/ScaffoldingModelExtensions.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +// ReSharper disable once CheckNamespace +namespace Microsoft.EntityFrameworkCore; + +/// +/// Design-time model extensions. +/// +public static class ScaffoldingModelExtensions +{ + /// + /// Check whether an entity type could be considered a many-to-many join entity type. + /// + /// The entity type to check. + /// if the entity type could be considered a join entity type. + public static bool IsSimpleManyToManyJoinEntityType(this IEntityType entityType) + { + if (!entityType.GetNavigations().Any() + && !entityType.GetSkipNavigations().Any()) + { + var primaryKey = entityType.FindPrimaryKey(); + var properties = entityType.GetProperties().ToList(); + var foreignKeys = entityType.GetForeignKeys().ToList(); + if (primaryKey != null + && primaryKey.Properties.Count > 1 + && foreignKeys.Count == 2 + && primaryKey.Properties.Count == properties.Count + && foreignKeys[0].Properties.Count + foreignKeys[1].Properties.Count == properties.Count + && !foreignKeys[0].Properties.Intersect(foreignKeys[1].Properties).Any() + && foreignKeys[0].IsRequired + && foreignKeys[1].IsRequired + && !foreignKeys[0].IsUnique + && !foreignKeys[1].IsUnique) + { + return true; + } + } + + return false; + } + + /// + /// Gets the errors encountered while reverse engineering the model. + /// + /// The model. + /// The errors. + public static IEnumerable GetReverseEngineeringErrors(this IReadOnlyModel model) + => (IEnumerable?)model[ScaffoldingAnnotationNames.ReverseEngineeringErrors] ?? new List(); + + /// + /// Gets the name that should be used for the property on the class for this entity type. + /// + /// The entity type. + /// The property name. + public static string GetDbSetName(this IReadOnlyEntityType entityType) + => (string?)entityType[ScaffoldingAnnotationNames.DbSetName] + ?? entityType.ShortName(); +} diff --git a/src/EFCore.Design/Metadata/Internal/ScaffoldingAnnotationNames.cs b/src/EFCore.Design/Metadata/Internal/ScaffoldingAnnotationNames.cs index 38c75ef387d..0785d4e6206 100644 --- a/src/EFCore.Design/Metadata/Internal/ScaffoldingAnnotationNames.cs +++ b/src/EFCore.Design/Metadata/Internal/ScaffoldingAnnotationNames.cs @@ -25,7 +25,7 @@ public static class ScaffoldingAnnotationNames /// 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 const string EntityTypeErrors = Prefix + "EntityTypeErrors"; + public const string ReverseEngineeringErrors = Prefix + "ReverseEngineeringErrors"; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Design/Metadata/Internal/ScaffoldingEntityTypeAnnotations.cs b/src/EFCore.Design/Metadata/Internal/ScaffoldingEntityTypeAnnotations.cs deleted file mode 100644 index 3769f85bc5f..00000000000 --- a/src/EFCore.Design/Metadata/Internal/ScaffoldingEntityTypeAnnotations.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Metadata.Internal; - -/// -/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to -/// the same compatibility standards as public APIs. It may be changed or removed without notice in -/// any release. You should only use it directly in your code with extreme caution and knowing that -/// doing so can result in application failures when updating to a new Entity Framework Core release. -/// -public static class ScaffoldingEntityTypeAnnotations -{ - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public static string GetDbSetName(this IReadOnlyEntityType entityType) - => (string?)entityType[ScaffoldingAnnotationNames.DbSetName] - ?? entityType.Name; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public static void SetDbSetName(this IMutableEntityType entityType, string? value) - => entityType.SetAnnotation( - ScaffoldingAnnotationNames.DbSetName, - value); -} diff --git a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs index 634aae7f1df..bf7f36a7b6b 100644 --- a/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs +++ b/src/EFCore.Design/Migrations/Design/CSharpSnapshotGenerator.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Text; +using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Migrations.Design; @@ -411,7 +412,7 @@ protected virtual void GenerateSequenceAnnotations( var annotations = Dependencies.AnnotationCodeGenerator .FilterIgnoredAnnotations(sequence.GetAnnotations()) .ToDictionary(a => a.Name, a => a); - + GenerateAnnotations(sequenceBuilderName, sequence, stringBuilder, annotations, inChainedCall: true); } @@ -505,17 +506,22 @@ protected virtual void GeneratePropertyAnnotations( GenerateFluentApiForPrecisionAndScale(property, stringBuilder); GenerateFluentApiForIsUnicode(property, stringBuilder); - stringBuilder - .AppendLine() - .Append(".") - .Append(nameof(RelationalPropertyBuilderExtensions.HasColumnType)) - .Append("(") - .Append(Code.Literal(property.GetColumnType())) - .Append(")"); - annotations.Remove(RelationalAnnotationNames.ColumnType); + if (!annotations.ContainsKey(RelationalAnnotationNames.ColumnType)) + { + annotations[RelationalAnnotationNames.ColumnType] = new Annotation( + RelationalAnnotationNames.ColumnType, + property.GetColumnType()); + } - GenerateFluentApiForDefaultValue(property, stringBuilder); - annotations.Remove(RelationalAnnotationNames.DefaultValue); + if (annotations.ContainsKey(RelationalAnnotationNames.DefaultValue) + && property.TryGetDefaultValue(out var defaultValue) + && defaultValue != DBNull.Value + && FindValueConverter(property) is ValueConverter valueConverter) + { + annotations[RelationalAnnotationNames.DefaultValue] = new Annotation( + RelationalAnnotationNames.DefaultValue, + valueConverter.ConvertToProvider(defaultValue)); + } GenerateAnnotations(propertyBuilderName, property, stringBuilder, annotations, inChainedCall: true); } @@ -735,7 +741,7 @@ protected virtual void GenerateEntityTypeAnnotations( .AppendLine() .Append(entityTypeBuilderName) .Append(".ToFunction(") - .Append(Code.UnknownLiteral(functionName)) + .Append(Code.Literal(functionName)) .AppendLine(");"); if (functionNameAnnotation != null) { @@ -756,7 +762,7 @@ protected virtual void GenerateEntityTypeAnnotations( .AppendLine() .Append(entityTypeBuilderName) .Append(".ToSqlQuery(") - .Append(Code.UnknownLiteral(sqlQuery)) + .Append(Code.Literal(sqlQuery)) .AppendLine(");"); if (sqlQueryAnnotation != null) { @@ -797,13 +803,13 @@ protected virtual void GenerateEntityTypeAnnotations( if (discriminatorMappingCompleteAnnotation?.Value != null) { - var value = discriminatorMappingCompleteAnnotation.Value; + var value = (bool)discriminatorMappingCompleteAnnotation.Value; stringBuilder .Append(".") .Append("IsComplete") .Append("(") - .Append(Code.UnknownLiteral(value)) + .Append(Code.Literal(value)) .Append(")"); } @@ -863,7 +869,7 @@ private void GenerateTableMapping( stringBuilder.Append("(string)"); } - stringBuilder.Append(Code.UnknownLiteral(tableName)); + stringBuilder.Append(Code.Literal(tableName)); annotations.TryGetAndRemove(RelationalAnnotationNames.IsTableExcludedFromMigrations, out IAnnotation isExcludedAnnotation); var isExcludedFromMigrations = (isExcludedAnnotation?.Value as bool?) == true; @@ -890,7 +896,7 @@ private void GenerateTableMapping( stringBuilder.Append("(string)"); } - stringBuilder.Append(Code.UnknownLiteral(schema)); + stringBuilder.Append(Code.Literal(schema)); } if (requiresTableBuilder) @@ -941,9 +947,9 @@ private void GenerateSplitTableMapping( .AppendLine() .Append(entityTypeBuilderName) .Append(".SplitToTable(") - .Append(Code.UnknownLiteral(table.Name)) + .Append(Code.Literal(table.Name)) .Append(", ") - .Append(Code.UnknownLiteral(table.Schema)) + .Append(Code.Literal(table.Schema)) .AppendLine(", t =>"); using (stringBuilder.Indent()) @@ -983,7 +989,7 @@ private void GenerateViewMapping(string entityTypeBuilderName, IEntityType entit .AppendLine() .Append(entityTypeBuilderName) .Append(".ToView(") - .Append(Code.UnknownLiteral(viewName)); + .Append(Code.Literal(viewName)); if (viewNameAnnotation != null) { annotations.Remove(viewNameAnnotation.Name); @@ -1004,7 +1010,7 @@ private void GenerateViewMapping(string entityTypeBuilderName, IEntityType entit stringBuilder.Append("(string)"); } - stringBuilder.Append(Code.UnknownLiteral(schema)); + stringBuilder.Append(Code.Literal(schema)); } if (hasOverrides) @@ -1038,9 +1044,9 @@ private void GenerateSplitViewMapping( .AppendLine() .Append(entityTypeBuilderName) .Append(".SplitToView(") - .Append(Code.UnknownLiteral(fragment.StoreObject.Name)) + .Append(Code.Literal(fragment.StoreObject.Name)) .Append(", ") - .Append(Code.UnknownLiteral(fragment.StoreObject.Schema)) + .Append(Code.Literal(fragment.StoreObject.Schema)) .AppendLine(", v =>"); using (stringBuilder.Indent()) @@ -1302,7 +1308,7 @@ protected virtual void GenerateOverride( .Append(".") .Append(nameof(ColumnBuilder.HasColumnName)) .Append("(") - .Append(Code.UnknownLiteral(overrides.ColumnName)) + .Append(Code.Literal(overrides.ColumnName)) .Append(")"); } @@ -1365,10 +1371,7 @@ protected virtual void GenerateForeignKey( .Append(".HasOne(") .Append(Code.Literal(GetFullName(foreignKey.PrincipalEntityType))) .Append(", ") - .Append( - foreignKey.DependentToPrincipal == null - ? Code.UnknownLiteral(null) - : Code.Literal(foreignKey.DependentToPrincipal.Name)); + .Append(Code.Literal(foreignKey.DependentToPrincipal?.Name)); } else { @@ -1704,7 +1707,7 @@ private void GenerateFluentApiForPrecisionAndScale( .Append(".") .Append(nameof(PropertyBuilder.HasPrecision)) .Append("(") - .Append(Code.UnknownLiteral(precision)); + .Append(Code.Literal(precision)); if (property.GetScale() is int scale) { @@ -1712,7 +1715,7 @@ private void GenerateFluentApiForPrecisionAndScale( { stringBuilder .Append(", ") - .Append(Code.UnknownLiteral(scale)); + .Append(Code.Literal(scale)); } } @@ -1736,35 +1739,6 @@ private void GenerateFluentApiForIsUnicode( } } - private void GenerateFluentApiForDefaultValue( - IProperty property, - IndentedStringBuilder stringBuilder) - { - if (!property.TryGetDefaultValue(out var defaultValue)) - { - return; - } - - stringBuilder - .AppendLine() - .Append(".") - .Append(nameof(RelationalPropertyBuilderExtensions.HasDefaultValue)) - .Append("("); - - if (defaultValue != DBNull.Value) - { - stringBuilder - .Append( - Code.UnknownLiteral( - FindValueConverter(property) is ValueConverter valueConverter - ? valueConverter.ConvertToProvider(defaultValue) - : defaultValue)); - } - - stringBuilder - .Append(")"); - } - private void GenerateAnnotations( string builderName, IAnnotatable annotatable, @@ -1806,9 +1780,12 @@ private void GenerateAnnotations( { if (inChainedCall) { - stringBuilder - .AppendLine() - .AppendLines(Code.Fragment(chainedCall), skipFinalNewline: true); + if (chainedCall.ChainedCall is null) + { + stringBuilder.AppendLine(); + } + + stringBuilder.Append(Code.Fragment(chainedCall, stringBuilder.CurrentIndent)); } else { @@ -1817,7 +1794,8 @@ private void GenerateAnnotations( stringBuilder.AppendLine(); } - stringBuilder.AppendLines(Code.Fragment(chainedCall, builderName), skipFinalNewline: true); + stringBuilder.Append(builderName); + stringBuilder.Append(Code.Fragment(chainedCall, stringBuilder.CurrentIndent + 1)); stringBuilder.AppendLine(";"); } diff --git a/src/EFCore.Design/Properties/DesignStrings.Designer.cs b/src/EFCore.Design/Properties/DesignStrings.Designer.cs index 76ab6a991a6..4fdd8657213 100644 --- a/src/EFCore.Design/Properties/DesignStrings.Designer.cs +++ b/src/EFCore.Design/Properties/DesignStrings.Designer.cs @@ -223,6 +223,14 @@ public static string ErrorConnecting(object? message) GetString("ErrorConnecting", nameof(message)), message); + /// + /// Processing '{inputFile}' failed. + /// + public static string ErrorGeneratingOutput(object? inputFile) + => string.Format( + GetString("ErrorGeneratingOutput", nameof(inputFile)), + inputFile); + /// /// The following file(s) already exist in directory '{outputDirectoryName}': {existingFiles}. Use the Force flag to overwrite these files. /// @@ -455,6 +463,18 @@ public static string NoContext(object? assembly) GetString("NoContext", nameof(assembly)), assembly); + /// + /// You must provide a DbContext.t4 file in order to scaffold using custom templates. + /// + public static string NoContextTemplate + => GetString("NoContextTemplate"); + + /// + /// You've provided an EntityTypeConfiguration.t4 file without a corresponding DbContext.t4 file. The generated DbContext code must be modified to work with your configuration classes. Provide a DbContext.t4 file and try again. + /// + public static string NoContextTemplateButConfiguration + => GetString("NoContextTemplateButConfiguration"); + /// /// No DbContext named '{name}' was found. /// diff --git a/src/EFCore.Design/Properties/DesignStrings.resx b/src/EFCore.Design/Properties/DesignStrings.resx index 11027c22402..0412c0f7079 100644 --- a/src/EFCore.Design/Properties/DesignStrings.resx +++ b/src/EFCore.Design/Properties/DesignStrings.resx @@ -198,6 +198,9 @@ An error occurred while accessing the database. Continuing without the information provided by the database. Error: {message} + + Processing '{inputFile}' failed. + The following file(s) already exist in directory '{outputDirectoryName}': {existingFiles}. Use the Force flag to overwrite these files. @@ -293,6 +296,12 @@ Change your target project to the migrations project by using the Package Manage No DbContext was found in assembly '{assembly}'. Ensure that you're using the correct assembly and that the type is neither abstract nor generic. + + You must provide a DbContext.t4 file in order to scaffold using custom templates. + + + You've provided an EntityTypeConfiguration.t4 file without a corresponding DbContext.t4 file. The generated DbContext code must be modified to work with your configuration classes. Provide a DbContext.t4 file and try again. + No DbContext named '{name}' was found. diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs index 45f5999c6b0..eb2aee49edc 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpDbContextGenerator.cs @@ -13,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class CSharpDbContextGenerator : ICSharpDbContextGenerator +public class CSharpDbContextGenerator { private const string EntityLambdaIdentifier = "entity"; @@ -184,7 +184,7 @@ private void GenerateDbSets(IModel model) var generated = false; foreach (var entityType in model.GetEntityTypes()) { - if (IsManyToManyJoinEntityType(entityType)) + if (entityType.IsSimpleManyToManyJoinEntityType()) { continue; } @@ -208,13 +208,13 @@ private void GenerateDbSets(IModel model) private void GenerateEntityTypeErrors(IModel model) { - var errors = model.GetEntityTypeErrors(); + var errors = model.GetReverseEngineeringErrors(); foreach (var entityTypeError in errors) { - _builder.AppendLine($"// {entityTypeError.Value} Please see the warning messages."); + _builder.AppendLine($"// {entityTypeError} Please see the warning messages."); } - if (errors.Count > 0) + if (errors.Any()) { _builder.AppendLine(); } @@ -251,7 +251,8 @@ protected virtual void GenerateOnConfiguring( connectionString); _builder - .AppendLines(_code.Fragment(useProviderCall, "optionsBuilder"), skipFinalNewline: true) + .Append("optionsBuilder") + .Append(_code.Fragment(useProviderCall, _builder.CurrentIndent + 1)) .AppendLine(";"); } @@ -283,7 +284,7 @@ protected virtual void GenerateOnModelCreating(IModel model) annotations.Remove(CoreAnnotationNames.ProductVersion); annotations.Remove(RelationalAnnotationNames.MaxIdentifierLength); annotations.Remove(ScaffoldingAnnotationNames.DatabaseName); - annotations.Remove(ScaffoldingAnnotationNames.EntityTypeErrors); + annotations.Remove(ScaffoldingAnnotationNames.ReverseEngineeringErrors); var lines = new List(); @@ -313,7 +314,7 @@ protected virtual void GenerateOnModelCreating(IModel model) { foreach (var entityType in model.GetEntityTypes()) { - if (IsManyToManyJoinEntityType(entityType)) + if (entityType.IsSimpleManyToManyJoinEntityType()) { continue; } @@ -452,7 +453,7 @@ private void GenerateEntityType(IEntityType entityType) private void GenerateTrigger(string tableBuilderName, ITrigger trigger) { var lines = new List { $".HasTrigger({_code.Literal(trigger.Name!)})" }; - + var annotations = _annotationCodeGenerator .FilterIgnoredAnnotations(trigger.GetAnnotations()) .ToDictionary(a => a.Name, a => a); @@ -460,7 +461,7 @@ private void GenerateTrigger(string tableBuilderName, ITrigger trigger) _annotationCodeGenerator.RemoveAnnotationsHandledByConventions(trigger, annotations); GenerateAnnotations(trigger, annotations, lines); - + AppendMultiLineFluentApi(null, lines, tableBuilderName); } @@ -481,7 +482,7 @@ private void AppendMultiLineFluentApi(IEntityType? entityType, IList lin _builder .AppendLine() .Append(builderName ?? EntityLambdaIdentifier) - .Append(lines[0]); + .AppendLines(lines[0], skipFinalNewline: true); using (_builder.Indent()) { @@ -1156,7 +1157,7 @@ private void GenerateAnnotations(IAnnotatable annotatable, Dictionary $".HasAnnotation({_code.Literal(a.Name)}, {_code.UnknownLiteral(a.Value)})")); } - - internal static bool IsManyToManyJoinEntityType(IEntityType entityType) - { - if (!entityType.GetNavigations().Any() - && !entityType.GetSkipNavigations().Any()) - { - var primaryKey = entityType.FindPrimaryKey(); - var properties = entityType.GetProperties().ToList(); - var foreignKeys = entityType.GetForeignKeys().ToList(); - if (primaryKey != null - && primaryKey.Properties.Count > 1 - && foreignKeys.Count == 2 - && primaryKey.Properties.Count == properties.Count - && foreignKeys[0].Properties.Count + foreignKeys[1].Properties.Count == properties.Count - && !foreignKeys[0].Properties.Intersect(foreignKeys[1].Properties).Any() - && foreignKeys[0].IsRequired - && foreignKeys[1].IsRequired - && !foreignKeys[0].IsUnique - && !foreignKeys[1].IsUnique) - { - return true; - } - } - - return false; - } } diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs index 7e9eafcc7f4..0ccca2d1ba8 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpEntityTypeGenerator.cs @@ -15,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class CSharpEntityTypeGenerator : ICSharpEntityTypeGenerator +public class CSharpEntityTypeGenerator { private readonly IAnnotationCodeGenerator _annotationCodeGenerator; private readonly ICSharpHelper _code; diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpModelGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpModelGenerator.cs index d0c6ad4041b..1f79b16a46f 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpModelGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpModelGenerator.cs @@ -11,21 +11,8 @@ namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal; /// public class CSharpModelGenerator : ModelCodeGenerator { - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ICSharpDbContextGenerator CSharpDbContextGenerator { get; } - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual ICSharpEntityTypeGenerator CSharpEntityTypeGenerator { get; } + private readonly CSharpDbContextGenerator _cSharpDbContextGenerator; + private readonly CSharpEntityTypeGenerator _cSharpEntityTypeGenerator; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -35,12 +22,13 @@ public class CSharpModelGenerator : ModelCodeGenerator /// public CSharpModelGenerator( ModelCodeGeneratorDependencies dependencies, - ICSharpDbContextGenerator cSharpDbContextGenerator, - ICSharpEntityTypeGenerator cSharpEntityTypeGenerator) + IProviderConfigurationCodeGenerator providerConfigurationCodeGenerator, + IAnnotationCodeGenerator annotationCodeGenerator, + ICSharpHelper cSharpHelper) : base(dependencies) { - CSharpDbContextGenerator = cSharpDbContextGenerator; - CSharpEntityTypeGenerator = cSharpEntityTypeGenerator; + _cSharpDbContextGenerator = new CSharpDbContextGenerator(providerConfigurationCodeGenerator, annotationCodeGenerator, cSharpHelper); + _cSharpEntityTypeGenerator = new CSharpEntityTypeGenerator(annotationCodeGenerator, cSharpHelper); } private const string FileExtension = ".cs"; @@ -76,7 +64,7 @@ public override ScaffoldedModel GenerateModel( CoreStrings.ArgumentPropertyNull(nameof(options.ConnectionString), nameof(options)), nameof(options)); } - var generatedCode = CSharpDbContextGenerator.WriteCode( + var generatedCode = _cSharpDbContextGenerator.WriteCode( model, options.ContextName, options.ConnectionString, @@ -102,12 +90,12 @@ public override ScaffoldedModel GenerateModel( foreach (var entityType in model.GetEntityTypes()) { - if (Internal.CSharpDbContextGenerator.IsManyToManyJoinEntityType(entityType)) + if (entityType.IsSimpleManyToManyJoinEntityType()) { continue; } - generatedCode = CSharpEntityTypeGenerator.WriteCode( + generatedCode = _cSharpEntityTypeGenerator.WriteCode( entityType, options.ModelNamespace, options.UseDataAnnotations, diff --git a/src/EFCore.Design/Scaffolding/Internal/ICSharpDbContextGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/ICSharpDbContextGenerator.cs deleted file mode 100644 index c571e9163b0..00000000000 --- a/src/EFCore.Design/Scaffolding/Internal/ICSharpDbContextGenerator.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal; - -/// -/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to -/// the same compatibility standards as public APIs. It may be changed or removed without notice in -/// any release. You should only use it directly in your code with extreme caution and knowing that -/// doing so can result in application failures when updating to a new Entity Framework Core release. -/// -public interface ICSharpDbContextGenerator -{ - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - string WriteCode( - IModel model, - string contextName, - string connectionString, - string? contextNamespace, - string? modelNamespace, - bool useDataAnnotations, - bool useNullableReferenceTypes, - bool suppressConnectionStringWarning, - bool suppressOnConfiguring); -} diff --git a/src/EFCore.Design/Scaffolding/Internal/ICSharpEntityTypeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/ICSharpEntityTypeGenerator.cs deleted file mode 100644 index 3d6f09dc7c0..00000000000 --- a/src/EFCore.Design/Scaffolding/Internal/ICSharpEntityTypeGenerator.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal; - -/// -/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to -/// the same compatibility standards as public APIs. It may be changed or removed without notice in -/// any release. You should only use it directly in your code with extreme caution and knowing that -/// doing so can result in application failures when updating to a new Entity Framework Core release. -/// -public interface ICSharpEntityTypeGenerator -{ - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - string WriteCode(IEntityType entityType, string? @namespace, bool useDataAnnotations, bool useNullableReferenceTypes); -} diff --git a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs index 04a2c4b2f59..2c8ac8d9769 100644 --- a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs +++ b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs @@ -325,7 +325,7 @@ protected virtual ModelBuilder VisitTables(ModelBuilder modelBuilder, ICollectio var model = modelBuilder.Model; model.RemoveEntityType(entityTypeName); - model.GetOrCreateEntityTypeErrors().Add(entityTypeName, errorMessage); + model.GetOrCreateReverseEngineeringErrors().Add(errorMessage); return null; } } @@ -675,7 +675,7 @@ protected virtual ModelBuilder VisitForeignKeys( // when there are multiple foreign keys does not work. foreach (var entityType in modelBuilder.Model.GetEntityTypes()) { - if (CSharpDbContextGenerator.IsManyToManyJoinEntityType((IEntityType)entityType)) + if (((IEntityType)entityType).IsSimpleManyToManyJoinEntityType()) { var fks = entityType.GetForeignKeys().ToArray(); var leftEntityType = fks[0].PrincipalEntityType; diff --git a/src/EFCore.Design/TextTemplating/Internal/TextTemplatingService.cs b/src/EFCore.Design/Scaffolding/Internal/TextTemplatingEngineHost.cs similarity index 79% rename from src/EFCore.Design/TextTemplating/Internal/TextTemplatingService.cs rename to src/EFCore.Design/Scaffolding/Internal/TextTemplatingEngineHost.cs index b52984b3ab4..92d8a6b2d36 100644 --- a/src/EFCore.Design/TextTemplating/Internal/TextTemplatingService.cs +++ b/src/EFCore.Design/Scaffolding/Internal/TextTemplatingEngineHost.cs @@ -2,12 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.CodeDom.Compiler; +using System.Diagnostics.CodeAnalysis; using System.Text; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.VisualStudio.TextTemplating; -using Engine = Mono.TextTemplating.TemplatingEngine; -namespace Microsoft.EntityFrameworkCore.TextTemplating.Internal; +namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -15,11 +15,14 @@ namespace Microsoft.EntityFrameworkCore.TextTemplating.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class TextTemplatingService : ITextTemplating, ITextTemplatingEngineHost, IServiceProvider +public class TextTemplatingEngineHost : ITextTemplatingSessionHost, ITextTemplatingEngineHost, IServiceProvider { - private readonly IServiceProvider _serviceProvider; - private ITextTemplatingCallback? _callback; - private string? _templateFile; + private readonly IServiceProvider? _serviceProvider; + private ITextTemplatingSession? _session; + private CompilerErrorCollection? _errors; + private string? _extension; + private Encoding? _outputEncoding; + private bool _fromOutputDirective; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -27,7 +30,7 @@ public class TextTemplatingService : ITextTemplating, ITextTemplatingEngineHost, /// 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 TextTemplatingService(IServiceProvider serviceProvider) + public TextTemplatingEngineHost(IServiceProvider? serviceProvider = null) => _serviceProvider = serviceProvider; /// @@ -36,7 +39,12 @@ public TextTemplatingService(IServiceProvider serviceProvider) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual ITextTemplatingSession? Session { get; set; } + [AllowNull] + public virtual ITextTemplatingSession Session + { + get => _session ??= CreateSession(); + set => _session = value; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -67,8 +75,7 @@ public TextTemplatingService(IServiceProvider serviceProvider) /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual string? TemplateFile - => _templateFile; + public virtual string? TemplateFile { get; set; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -76,32 +83,40 @@ public virtual string? TemplateFile /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public virtual string ProcessTemplate(string inputFile, string content, ITextTemplatingCallback? callback = null) - { - _templateFile = inputFile; - _callback = callback; + public virtual string Extension + => _extension ?? ".cs"; - var sessionCreated = false; - if (Session == null) - { - Session = CreateSession(); - sessionCreated = true; - } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual CompilerErrorCollection Errors + => _errors ??= new CompilerErrorCollection(); - try - { - return new Engine().ProcessTemplate(content, this); - } - finally - { - _templateFile = null; - _callback = null; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual Encoding OutputEncoding + => _outputEncoding ?? Encoding.UTF8; - if (sessionCreated) - { - Session = null; - } - } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void Initialize() + { + _session = null; + _errors = null; + _extension = null; + _outputEncoding = null; + _fromOutputDirective = false; } /// @@ -147,12 +162,7 @@ public virtual bool LoadIncludeText(string requestFileName, out string content, /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual void LogErrors(CompilerErrorCollection errors) - { - foreach (CompilerError error in errors) - { - _callback?.ErrorCallback(error); - } - } + => Errors.AddRange(errors); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -208,7 +218,7 @@ public virtual string ResolveParameterValue(string directiveId, string processor /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual string ResolvePath(string path) - => !Path.IsPathRooted(path) + => !Path.IsPathRooted(path) && Path.IsPathRooted(TemplateFile) ? Path.Combine(Path.GetDirectoryName(TemplateFile)!, path) : path; @@ -219,7 +229,7 @@ public virtual string ResolvePath(string path) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual void SetFileExtension(string extension) - => _callback?.SetFileExtension(extension); + => _extension = extension; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -228,7 +238,15 @@ public virtual void SetFileExtension(string extension) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual void SetOutputEncoding(Encoding encoding, bool fromOutputDirective) - => _callback?.SetOutputEncoding(encoding, fromOutputDirective); + { + if (_fromOutputDirective) + { + return; + } + + _outputEncoding = encoding; + _fromOutputDirective = fromOutputDirective; + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -237,5 +255,5 @@ public virtual void SetOutputEncoding(Encoding encoding, bool fromOutputDirectiv /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual object? GetService(Type serviceType) - => _serviceProvider.GetService(serviceType); + => _serviceProvider?.GetService(serviceType); } diff --git a/src/EFCore.Design/Scaffolding/Internal/TextTemplatingModelGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/TextTemplatingModelGenerator.cs index 4b524c61332..b33b4581262 100644 --- a/src/EFCore.Design/Scaffolding/Internal/TextTemplatingModelGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/TextTemplatingModelGenerator.cs @@ -5,29 +5,77 @@ using System.Text; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.TextTemplating; -using Microsoft.EntityFrameworkCore.TextTemplating.Internal; +using Engine = Mono.TextTemplating.TemplatingEngine; namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal; -internal class TextTemplatingModelGenerator : TemplatedModelGenerator +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class TextTemplatingModelGenerator : TemplatedModelGenerator { - private readonly ITextTemplating _host; - private readonly IOperationReporter _reporter; + private const string DbContextTemplate = "DbContext.t4"; + private const string EntityTypeTemplate = "EntityType.t4"; + private const string EntityTypeConfigurationTemplate = "EntityTypeConfiguration.t4"; + private readonly IOperationReporter _reporter; + private readonly IServiceProvider _serviceProvider; + private Engine? _engine; + + /// + /// 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 TextTemplatingModelGenerator( ModelCodeGeneratorDependencies dependencies, - ITextTemplating textTemplatingService, - IOperationReporter reporter) + IOperationReporter reporter, + IServiceProvider serviceProvider) : base(dependencies) { - _host = textTemplatingService; _reporter = reporter; + _serviceProvider = serviceProvider; } + /// + /// 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. + /// + protected virtual Engine Engine + => _engine ??= new Engine(); + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// public override bool HasTemplates(string projectDir) - => File.Exists(Path.Combine(projectDir, TemplatesDirectory, "DbContext.t4")); + { + var hasContextTemplate = File.Exists(Path.Combine(projectDir, TemplatesDirectory, DbContextTemplate)); + var hasEntityTypeTemplate = File.Exists(Path.Combine(projectDir, TemplatesDirectory, EntityTypeTemplate)); + var hasConfigurationTemplate = File.Exists(Path.Combine(projectDir, TemplatesDirectory, EntityTypeConfigurationTemplate)); + if (hasConfigurationTemplate && !hasContextTemplate) + { + throw new OperationException(DesignStrings.NoContextTemplateButConfiguration); + } + + return hasContextTemplate || hasEntityTypeTemplate || hasConfigurationTemplate; + } + + /// + /// 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 ScaffoldedModel GenerateModel(IModel model, ModelCodeGenerationOptions options) { if (options.ContextName == null) @@ -42,129 +90,119 @@ public override ScaffoldedModel GenerateModel(IModel model, ModelCodeGenerationO CoreStrings.ArgumentPropertyNull(nameof(options.ConnectionString), nameof(options)), nameof(options)); } - var resultingFiles = new ScaffoldedModel(); - - var contextTemplate = Path.Combine(options.ProjectDir!, TemplatesDirectory, "DbContext.t4"); + var host = new TextTemplatingEngineHost(_serviceProvider) + { + Session = + { + { "Model", model }, + { "Options", options }, + { "NamespaceHint", options.ContextNamespace ?? options.ModelNamespace }, + { "ProjectDefaultNamespace", options.RootNamespace } + } + }; + var contextTemplate = Path.Combine(options.ProjectDir!, TemplatesDirectory, DbContextTemplate); - Check.DebugAssert(_host.Session == null, "Session is not null."); - _host.Session = _host.CreateSession(); - try + string generatedCode; + if (File.Exists(contextTemplate)) { - _host.Session.Add("Model", model); - _host.Session.Add("Options", options); - _host.Session.Add("NamespaceHint", options.ContextNamespace ?? options.ModelNamespace); - _host.Session.Add("ProjectDefaultNamespace", options.RootNamespace); + host.TemplateFile = contextTemplate; - var handler = new TextTemplatingCallback(); - var generatedCode = ProcessTemplate(contextTemplate, handler); + generatedCode = ProcessTemplate(contextTemplate, host); + } + else + { + // TODO: Use default generator when C# + throw new OperationException(DesignStrings.NoContextTemplate); + } - var dbContextFileName = options.ContextName + handler.Extension; - resultingFiles.ContextFile = new ScaffoldedFile + var dbContextFileName = options.ContextName + host.Extension; + var resultingFiles = new ScaffoldedModel + { + ContextFile = new ScaffoldedFile { Path = options.ContextDir != null ? Path.Combine(options.ContextDir, dbContextFileName) : dbContextFileName, Code = generatedCode - }; - } - finally - { - _host.Session = null; - } + } + }; - var entityTypeTemplate = Path.Combine(options.ProjectDir!, TemplatesDirectory, "EntityType.t4"); + var entityTypeTemplate = Path.Combine(options.ProjectDir!, TemplatesDirectory, EntityTypeTemplate); if (File.Exists(entityTypeTemplate)) { + host.TemplateFile = entityTypeTemplate; + foreach (var entityType in model.GetEntityTypes()) { - // TODO: Should this be handled inside the template? - if (CSharpDbContextGenerator.IsManyToManyJoinEntityType(entityType)) + host.Initialize(); + host.Session.Add("EntityType", entityType); + host.Session.Add("Options", options); + host.Session.Add("NamespaceHint", options.ModelNamespace); + host.Session.Add("ProjectDefaultNamespace", options.RootNamespace); + + generatedCode = ProcessTemplate(entityTypeTemplate, host); + if (string.IsNullOrWhiteSpace(generatedCode)) { continue; } - _host.Session = _host.CreateSession(); - try - { - _host.Session.Add("EntityType", entityType); - _host.Session.Add("Options", options); - _host.Session.Add("NamespaceHint", options.ModelNamespace); - _host.Session.Add("ProjectDefaultNamespace", options.RootNamespace); - - var handler = new TextTemplatingCallback(); - var generatedCode = ProcessTemplate(entityTypeTemplate, handler); - if (string.IsNullOrWhiteSpace(generatedCode)) - { - continue; - } + var entityTypeFileName = entityType.Name + host.Extension; + resultingFiles.AdditionalFiles.Add( + new ScaffoldedFile { Path = entityTypeFileName, Code = generatedCode }); + } + } - var entityTypeFileName = entityType.Name + handler.Extension; - resultingFiles.AdditionalFiles.Add( - new ScaffoldedFile { Path = entityTypeFileName, Code = generatedCode }); - } - finally + var configurationTemplate = Path.Combine(options.ProjectDir!, TemplatesDirectory, EntityTypeConfigurationTemplate); + if (File.Exists(configurationTemplate)) + { + host.TemplateFile = configurationTemplate; + + foreach (var entityType in model.GetEntityTypes()) + { + host.Initialize(); + host.Session.Add("EntityType", entityType); + host.Session.Add("Options", options); + host.Session.Add("NamespaceHint", options.ContextNamespace ?? options.ModelNamespace); + host.Session.Add("ProjectDefaultNamespace", options.RootNamespace); + + generatedCode = ProcessTemplate(configurationTemplate, host); + if (string.IsNullOrWhiteSpace(generatedCode)) { - _host.Session = null; + continue; } + + var configurationFileName = entityType.Name + "Configuration" + host.Extension; + resultingFiles.AdditionalFiles.Add( + new ScaffoldedFile + { + Path = options.ContextDir != null + ? Path.Combine(options.ContextDir, configurationFileName) + : configurationFileName, + Code = generatedCode + }); } } return resultingFiles; } - private string ProcessTemplate(string inputFile, TextTemplatingCallback handler) + private string ProcessTemplate(string inputFile, TextTemplatingEngineHost host) { - var output = _host.ProcessTemplate( - inputFile, - File.ReadAllText(inputFile), - handler); + var output = Engine.ProcessTemplate(File.ReadAllText(inputFile), host); - foreach (CompilerError error in handler.Errors) + foreach (CompilerError error in host.Errors) { - var builder = new StringBuilder(); - - if (!string.IsNullOrEmpty(error.FileName)) - { - builder.Append(error.FileName); - - if (error.Line > 0) - { - builder - .Append("(") - .Append(error.Line); - - if (error.Column > 0) - { - builder - .Append(",") - .Append(error.Line); - } - builder.Append(")"); - } - - builder.Append(" : "); - } - - builder - .Append(error.IsWarning ? "warning" : "error") - .Append(" ") - .Append(error.ErrorNumber) - .Append(": ") - .AppendLine(error.ErrorText); + _reporter.Write(error); + } - if (error.IsWarning) - { - _reporter.WriteWarning(builder.ToString()); - } - else - { - _reporter.WriteError(builder.ToString()); - } + if (host.OutputEncoding != Encoding.UTF8) + { + _reporter.WriteWarning(DesignStrings.EncodingIgnored(host.OutputEncoding.WebName)); } - if (handler.OutputEncoding != Encoding.UTF8) + if (host.Errors.HasErrors) { - _reporter.WriteWarning(DesignStrings.EncodingIgnored(handler.OutputEncoding.WebName)); + throw new OperationException(DesignStrings.ErrorGeneratingOutput(inputFile)); } return output; diff --git a/src/EFCore.Design/Scaffolding/TemplatedModelGenerator.cs b/src/EFCore.Design/Scaffolding/TemplatedModelGenerator.cs index 8ecd5a0ad91..c7e27204854 100644 --- a/src/EFCore.Design/Scaffolding/TemplatedModelGenerator.cs +++ b/src/EFCore.Design/Scaffolding/TemplatedModelGenerator.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Scaffolding; /// /// Base type for model code generators that use templates. /// -internal abstract class TemplatedModelGenerator : ModelCodeGenerator +public abstract class TemplatedModelGenerator : ModelCodeGenerator { /// /// Initializes a new instance of the class. diff --git a/src/EFCore.Design/TextTemplating/ITextTemplating.cs b/src/EFCore.Design/TextTemplating/ITextTemplating.cs deleted file mode 100644 index c0e7a822cd2..00000000000 --- a/src/EFCore.Design/TextTemplating/ITextTemplating.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.VisualStudio.TextTemplating; - -namespace Microsoft.EntityFrameworkCore.TextTemplating; - -/// -/// The text template transformation service. -/// -public interface ITextTemplating : ITextTemplatingSessionHost -{ - /// - /// Transforms the contents of a text template file to produce the generated text output. - /// - /// The path of the template file. - /// The contents of the template file. - /// The callback used to process errors and information. - /// The output. - string ProcessTemplate(string inputFile, string content, ITextTemplatingCallback? callback = null); -} diff --git a/src/EFCore.Design/TextTemplating/ITextTemplatingCallback.cs b/src/EFCore.Design/TextTemplating/ITextTemplatingCallback.cs deleted file mode 100644 index b29afc78fb4..00000000000 --- a/src/EFCore.Design/TextTemplating/ITextTemplatingCallback.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.CodeDom.Compiler; -using System.Text; - -namespace Microsoft.EntityFrameworkCore.TextTemplating; - -/// -/// Callback interface to be implemented by clients of that wish to process errors and information. -/// -public interface ITextTemplatingCallback -{ - /// - /// Receives errors and warnings. - /// - /// An error or warning. - void ErrorCallback(CompilerError error); - - /// - /// Receives the file name extension that is expected for the generated text output. - /// - /// The extension. - void SetFileExtension(string extension); - - /// - /// Receives the encoding that is expected for the generated text output. - /// - /// The encoding. - /// A value indicating whether the encoding was specified in the encoding parameter of the output directive. - void SetOutputEncoding(Encoding encoding, bool fromOutputDirective); -} diff --git a/src/EFCore.Design/TextTemplating/Internal/TextTemplatingCallback.cs b/src/EFCore.Design/TextTemplating/Internal/TextTemplatingCallback.cs deleted file mode 100644 index 6899abcbe26..00000000000 --- a/src/EFCore.Design/TextTemplating/Internal/TextTemplatingCallback.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.CodeDom.Compiler; -using System.Text; - -namespace Microsoft.EntityFrameworkCore.TextTemplating.Internal; - -/// -/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to -/// the same compatibility standards as public APIs. It may be changed or removed without notice in -/// any release. You should only use it directly in your code with extreme caution and knowing that -/// doing so can result in application failures when updating to a new Entity Framework Core release. -/// -public class TextTemplatingCallback : ITextTemplatingCallback -{ - private CompilerErrorCollection? _errors; - private string _extension = ".cs"; - private Encoding _outputEncoding = Encoding.UTF8; - private bool _fromOutputDirective; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual string Extension - => _extension; - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual CompilerErrorCollection Errors - => _errors ??= new CompilerErrorCollection(); - - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Encoding OutputEncoding - => _outputEncoding; - - void ITextTemplatingCallback.ErrorCallback(CompilerError error) - => Errors.Add(error); - - void ITextTemplatingCallback.SetFileExtension(string extension) - => _extension = extension; - - void ITextTemplatingCallback.SetOutputEncoding(Encoding? encoding, bool fromOutputDirective) - { - if (_fromOutputDirective) - { - return; - } - - _outputEncoding = encoding ?? Encoding.UTF8; - _fromOutputDirective = fromOutputDirective; - } -} diff --git a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs index 44ce8440296..42aaa626e22 100644 --- a/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/AnnotationCodeGenerator.cs @@ -69,6 +69,14 @@ private static readonly MethodInfo PropertyHasColumnOrderMethodInfo = typeof(RelationalPropertyBuilderExtensions).GetRuntimeMethod( nameof(RelationalPropertyBuilderExtensions.HasColumnOrder), new[] { typeof(PropertyBuilder), typeof(int?) })!; + private static readonly MethodInfo PropertyHasDefaultValueMethodInfo1 + = typeof(RelationalPropertyBuilderExtensions).GetRuntimeMethod( + nameof(RelationalPropertyBuilderExtensions.HasDefaultValue), new[] { typeof(PropertyBuilder) })!; + + private static readonly MethodInfo PropertyHasDefaultValueMethodInfo2 + = typeof(RelationalPropertyBuilderExtensions).GetRuntimeMethod( + nameof(RelationalPropertyBuilderExtensions.HasDefaultValue), new[] { typeof(PropertyBuilder), typeof(object) })!; + private static readonly MethodInfo PropertyHasDefaultValueSqlMethodInfo1 = typeof(RelationalPropertyBuilderExtensions).GetRuntimeMethod( nameof(RelationalPropertyBuilderExtensions.HasDefaultValueSql), new[] { typeof(PropertyBuilder) })!; @@ -101,6 +109,10 @@ private static readonly MethodInfo PropertyUseCollationMethodInfo = typeof(RelationalPropertyBuilderExtensions).GetRuntimeMethod( nameof(RelationalPropertyBuilderExtensions.UseCollation), new[] { typeof(PropertyBuilder), typeof(string) })!; + private static readonly MethodInfo PropertyHasColumnTypeMethodInfo + = typeof(RelationalPropertyBuilderExtensions).GetRuntimeMethod( + nameof(RelationalPropertyBuilderExtensions.HasColumnType), new[] { typeof(PropertyBuilder), typeof(string) })!; + private static readonly MethodInfo KeyHasNameMethodInfo = typeof(RelationalKeyBuilderExtensions).GetRuntimeMethod( nameof(RelationalKeyBuilderExtensions.HasName), new[] { typeof(KeyBuilder), typeof(string) })!; @@ -154,6 +166,19 @@ public virtual void RemoveAnnotationsHandledByConventions( { annotations.Remove(RelationalAnnotationNames.IsTableExcludedFromMigrations); + var schema = entityType.GetSchema(); + var defaultSchema = entityType.Model.GetDefaultSchema(); + if (schema != null && schema != defaultSchema) + { + annotations.Remove(RelationalAnnotationNames.Schema); + } + + var viewSchema = entityType.GetViewSchema(); + if (viewSchema != null && viewSchema != defaultSchema) + { + annotations.Remove(RelationalAnnotationNames.ViewSchema); + } + RemoveConventionalAnnotationsHelper(entityType, annotations, IsHandledByConvention); } @@ -179,7 +204,14 @@ public virtual void RemoveAnnotationsHandledByConventions( /// public virtual void RemoveAnnotationsHandledByConventions(IKey key, IDictionary annotations) - => RemoveConventionalAnnotationsHelper(key, annotations, IsHandledByConvention); + { + if (key.GetName() == key.GetDefaultName()) + { + annotations.Remove(RelationalAnnotationNames.Name); + } + + RemoveConventionalAnnotationsHelper(key, annotations, IsHandledByConvention); + } /// public virtual void RemoveAnnotationsHandledByConventions( @@ -288,6 +320,18 @@ public virtual IReadOnlyList GenerateFluentApiCalls( { var methodCallCodeFragments = new List(); + GenerateSimpleFluentApiCall( + annotations, + RelationalAnnotationNames.ColumnType, PropertyHasColumnTypeMethodInfo, methodCallCodeFragments); + + if (TryGetAndRemove(annotations, RelationalAnnotationNames.DefaultValue, out object? defaultValue)) + { + methodCallCodeFragments.Add( + defaultValue == DBNull.Value + ? new MethodCallCodeFragment(PropertyHasDefaultValueMethodInfo1) + : new MethodCallCodeFragment(PropertyHasDefaultValueMethodInfo2, defaultValue)); + } + GenerateSimpleFluentApiCall( annotations, RelationalAnnotationNames.ColumnName, PropertyHasColumnNameMethodInfo, methodCallCodeFragments); @@ -319,7 +363,7 @@ public virtual IReadOnlyList GenerateFluentApiCalls( methodCallCodeFragments.Add( isFixedLength ? new MethodCallCodeFragment(PropertyIsFixedLengthMethodInfo) - : new MethodCallCodeFragment(PropertyIsFixedLengthMethodInfo, isFixedLength)); + : new MethodCallCodeFragment(PropertyIsFixedLengthMethodInfo, false)); } GenerateSimpleFluentApiCall( diff --git a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs index 867ba686d71..99f4681d8c7 100644 --- a/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs +++ b/src/EFCore.Relational/Design/Internal/RelationalCSharpRuntimeAnnotationCodeGenerator.cs @@ -287,7 +287,7 @@ private void Create(ISequence sequence, string sequencesVariable, CSharpRuntimeA mainBuilder .Append(sequencesVariable).Append("[(").Append(code.Literal(sequence.Name)).Append(", ") - .Append(code.UnknownLiteral(sequence.Schema)).Append(")] = ") + .Append(code.Literal(sequence.Schema)).Append(")] = ") .Append(sequenceVariable).AppendLine(";") .AppendLine(); } @@ -343,7 +343,7 @@ public override void Generate(IEntityType entityType, CSharpRuntimeAnnotationCod GenerateSimpleAnnotation(RelationalAnnotationNames.MappingFragments, fragmentsVariable, parameters); } - + if (annotations.TryGetAndRemove( RelationalAnnotationNames.Triggers, out SortedDictionary triggers)) @@ -413,9 +413,9 @@ private void Create(ITrigger trigger, string triggersVariable, CSharpRuntimeAnno .Append("var ").Append(triggerVariable).AppendLine(" = new RuntimeTrigger(").IncrementIndent() .Append(parameters.TargetName).AppendLine(",") .Append(code.Literal(trigger.ModelName)).AppendLine(",") - .Append(code.UnknownLiteral(trigger.Name)).AppendLine(",") + .Append(code.Literal(trigger.Name)).AppendLine(",") .Append(code.Literal(trigger.TableName)).AppendLine(",") - .Append(code.UnknownLiteral(trigger.TableSchema)) + .Append(code.Literal(trigger.TableSchema)) .AppendLine(");") .DecrementIndent() .AppendLine(); @@ -501,10 +501,10 @@ private void Create( .Append("var ").Append(overrideVariable).AppendLine(" = new RuntimeRelationalPropertyOverrides(").IncrementIndent() .Append(parameters.TargetName).AppendLine(","); AppendLiteral(storeObject, mainBuilder, code); - + mainBuilder.AppendLine(",") .Append(code.Literal(overrides.ColumnNameOverridden)).AppendLine(",") - .Append(code.UnknownLiteral(overrides.ColumnName)).AppendLine(");").DecrementIndent(); + .Append(code.Literal(overrides.ColumnName)).AppendLine(");").DecrementIndent(); CreateAnnotations( overrides, @@ -602,12 +602,12 @@ private static void AppendLiteral(StoreObjectIdentifier storeObject, IndentedStr case StoreObjectType.Table: builder .Append("Table(").Append(code.Literal(storeObject.Name)) - .Append(", ").Append(code.UnknownLiteral(storeObject.Schema)).Append(")"); + .Append(", ").Append(code.Literal(storeObject.Schema)).Append(")"); break; case StoreObjectType.View: builder .Append("View(").Append(code.Literal(storeObject.Name)) - .Append(", ").Append(code.UnknownLiteral(storeObject.Schema)).Append(")"); + .Append(", ").Append(code.Literal(storeObject.Schema)).Append(")"); break; case StoreObjectType.SqlQuery: builder diff --git a/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs b/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs index 2f6cf549ec4..373e225fb7c 100644 --- a/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs +++ b/src/EFCore.SqlServer/Design/Internal/SqlServerAnnotationCodeGenerator.cs @@ -360,18 +360,22 @@ protected override bool IsHandledByConvention(IModel model, IAnnotation annotati } var seed = seedAnnotation is null - ? 1 + ? 1L : seedAnnotation.Value is int intValue ? intValue - : (long?)seedAnnotation.Value ?? 1; + : (long?)seedAnnotation.Value ?? 1L; var increment = GetAndRemove(annotations, SqlServerAnnotationNames.IdentityIncrement) ?? model.FindAnnotation(SqlServerAnnotationNames.IdentityIncrement)?.Value as int? ?? 1; return new MethodCallCodeFragment( onModel ? ModelUseIdentityColumnsMethodInfo : PropertyUseIdentityColumnsMethodInfo, - seed, - increment); + (seed, increment) switch + { + (1L, 1) => Array.Empty(), + (_, 1) => new object[] { seed }, + _ => new object[] { seed, increment } + }); case SqlServerValueGenerationStrategy.SequenceHiLo: var name = GetAndRemove(annotations, SqlServerAnnotationNames.HiLoSequenceName); diff --git a/src/EFCore/Design/AttributeCodeFragment.cs b/src/EFCore/Design/AttributeCodeFragment.cs index d3fd4c2ea31..dafae15fa3a 100644 --- a/src/EFCore/Design/AttributeCodeFragment.cs +++ b/src/EFCore/Design/AttributeCodeFragment.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections; + namespace Microsoft.EntityFrameworkCore.Design; /// @@ -12,17 +14,30 @@ namespace Microsoft.EntityFrameworkCore.Design; /// public class AttributeCodeFragment { - private readonly List _arguments; + private readonly List _arguments; + private readonly Dictionary _namedArguments; /// /// Initializes a new instance of the class. /// /// The attribute's CLR type. /// The attribute's arguments. - public AttributeCodeFragment(Type type, params object[] arguments) + public AttributeCodeFragment(Type type, params object?[] arguments) + : this(type, arguments, new Dictionary(0)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The attribute's CLR type. + /// The attribute's positional arguments. + /// The attribute's named arguments. + public AttributeCodeFragment(Type type, IEnumerable arguments, IDictionary namedArguments) { Type = type; - _arguments = new List(arguments); + _arguments = new List(arguments); + _namedArguments = new Dictionary(namedArguments); } /// @@ -32,9 +47,16 @@ public AttributeCodeFragment(Type type, params object[] arguments) public virtual Type Type { get; } /// - /// Gets the method call's arguments. + /// Gets the attribute's positional arguments. /// - /// The method call's arguments. - public virtual IReadOnlyList Arguments + /// The arguments. + public virtual IReadOnlyList Arguments => _arguments; + + /// + /// Gets the attribute's named arguments. + /// + /// The arguments. + public virtual IReadOnlyDictionary NamedArguments + => _namedArguments; } diff --git a/src/EFCore/Design/ICSharpHelper.cs b/src/EFCore/Design/ICSharpHelper.cs index d443a78ded1..f30726a3174 100644 --- a/src/EFCore/Design/ICSharpHelper.cs +++ b/src/EFCore/Design/ICSharpHelper.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Numerics; + namespace Microsoft.EntityFrameworkCore.Design; /// @@ -21,7 +23,23 @@ public interface ICSharpHelper /// if the method call should be type-qualified, for instance/extension syntax. /// /// The fragment. - string Fragment(MethodCallCodeFragment fragment, string? instanceIdentifier = null, bool typeQualified = false); + string Fragment(MethodCallCodeFragment fragment, string? instanceIdentifier, bool typeQualified); + + /// + /// Generates a method call code fragment. + /// + /// The method call. If null, no code is generated. + /// The indentation level to use when multiple lines are generated. + /// The fragment. + string Fragment(MethodCallCodeFragment? fragment, int indent = 0); + + /// + /// Generates a lambda code fragment. + /// + /// The lambda. + /// The indentation level to use when multiple lines are generated. + /// The fragment. + string Fragment(NestedClosureCodeFragment fragment, int indent = 0); /// /// Generates a valid C# identifier from the specified string unique to the scope. @@ -68,6 +86,13 @@ string Lambda(IEnumerable properties, string? lambdaIdentifier = null string Literal(T? value) where T : struct; + /// + /// Generates a BigInteger literal. + /// + /// The value. + /// The literal. + string Literal(BigInteger value); + /// /// Generates a bool literal. /// @@ -89,6 +114,13 @@ string Literal(T? value) /// The literal. string Literal(char value); + /// + /// Generates a DateOnly literal. + /// + /// The value. + /// The literal. + string Literal(DateOnly value); + /// /// Generates a DateTime literal. /// @@ -171,7 +203,14 @@ string Literal(T? value) /// /// The value. /// The literal. - string Literal(string value); + string Literal(string? value); + + /// + /// Generates a TimeOnly literal. + /// + /// The value. + /// The literal. + string Literal(TimeOnly value); /// /// Generates a TimeSpan literal. @@ -238,4 +277,19 @@ string Literal(T? value) /// The value. /// The literal. string UnknownLiteral(object? value); + + /// + /// Generates an XML documentation comment. Handles escaping and newlines. + /// + /// The comment. + /// The indentation level to use when multiple lines are generated. + /// The comment. + string XmlComment(string comment, int indent = 0); + + /// + /// Generates an attribute specification. + /// + /// The attribute code fragment. + /// The attribute specification code. + string Fragment(AttributeCodeFragment fragment); } diff --git a/src/EFCore/Design/MethodCallCodeFragment.cs b/src/EFCore/Design/MethodCallCodeFragment.cs index 6ba500f4889..7fabb5853ca 100644 --- a/src/EFCore/Design/MethodCallCodeFragment.cs +++ b/src/EFCore/Design/MethodCallCodeFragment.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Runtime.CompilerServices; + namespace Microsoft.EntityFrameworkCore.Design; /// @@ -21,13 +23,15 @@ public class MethodCallCodeFragment /// The method call's arguments. Can be . public MethodCallCodeFragment(MethodInfo methodInfo, params object?[] arguments) { - var parameterLength = methodInfo.GetParameters().Length; - if (methodInfo.IsStatic) + var parameters = methodInfo.GetParameters(); + var parameterLength = parameters.Length; + if (methodInfo.IsDefined(typeof(ExtensionAttribute))) { parameterLength--; } - if (arguments.Length > parameterLength) + if (arguments.Length > parameterLength + && !parameters[^1].IsDefined(typeof(ParamArrayAttribute))) { throw new ArgumentException( CoreStrings.IncorrectNumberOfArguments(methodInfo.Name, arguments.Length, parameterLength), @@ -35,44 +39,64 @@ public MethodCallCodeFragment(MethodInfo methodInfo, params object?[] arguments) } MethodInfo = methodInfo; + Namespace = methodInfo.DeclaringType?.Namespace; + DeclaringType = methodInfo.DeclaringType?.Name; + Method = methodInfo.Name; + _arguments = new List(arguments); + } + + /// + /// Initializes a new instance of the class. + /// + /// The method's name. + /// The method call's arguments. Can be . + public MethodCallCodeFragment(string method, params object?[] arguments) + { + Method = method; _arguments = new List(arguments); } private MethodCallCodeFragment( MethodInfo methodInfo, - MethodCallCodeFragment chainedCall, - object?[] arguments) + object?[] arguments, + MethodCallCodeFragment chainedCall) : this(methodInfo, arguments) { ChainedCall = chainedCall; } + private MethodCallCodeFragment( + string method, + object?[] arguments, + MethodCallCodeFragment chainedCall) + : this(method, arguments) + { + ChainedCall = chainedCall; + } + /// /// Gets the for this method call. /// /// The . - public virtual MethodInfo MethodInfo { get; } + public virtual MethodInfo? MethodInfo { get; } /// /// Gets the namespace of the method's declaring type. /// /// The declaring type's name. - public virtual string? Namespace - => MethodInfo.DeclaringType?.Namespace; + public virtual string? Namespace { get; } /// /// Gets the name of the method's declaring type. /// /// The declaring type's name. - public virtual string? DeclaringType - => MethodInfo.DeclaringType?.Name; + public virtual string? DeclaringType { get; } /// /// Gets the method's name. /// /// The method's name. - public virtual string Method - => MethodInfo.Name; + public virtual string Method { get; } /// /// Gets the method call's arguments. @@ -96,11 +120,22 @@ public virtual IReadOnlyList Arguments public virtual MethodCallCodeFragment Chain(MethodInfo methodInfo, params object[] arguments) => Chain(new MethodCallCodeFragment(methodInfo, arguments)); + /// + /// Creates a method chain from this method to another. + /// + /// The next method's name. + /// The next method call's arguments. + /// A new fragment representing the method chain. + public virtual MethodCallCodeFragment Chain(string method, params object[] arguments) + => Chain(new MethodCallCodeFragment(method, arguments)); + /// /// Creates a method chain from this method to another. /// /// The next method. /// A new fragment representing the method chain. public virtual MethodCallCodeFragment Chain(MethodCallCodeFragment call) - => new(MethodInfo, ChainedCall?.Chain(call) ?? call, _arguments.ToArray()); + => MethodInfo is not null + ? new(MethodInfo, _arguments.ToArray(), ChainedCall?.Chain(call) ?? call) + : new(Method, _arguments.ToArray(), ChainedCall?.Chain(call) ?? call); } diff --git a/src/EFCore/Infrastructure/IndentedStringBuilder.cs b/src/EFCore/Infrastructure/IndentedStringBuilder.cs index 8c6bd69b101..5b515b1e88d 100644 --- a/src/EFCore/Infrastructure/IndentedStringBuilder.cs +++ b/src/EFCore/Infrastructure/IndentedStringBuilder.cs @@ -26,6 +26,13 @@ public class IndentedStringBuilder private readonly StringBuilder _stringBuilder = new(); + /// + /// Gets the current indent level. + /// + /// The current indent level. + public virtual byte CurrentIndent + => _indent; + /// /// The current length of the built string. /// diff --git a/test/EFCore.Design.Tests/Design/DesignTimeServicesTest.cs b/test/EFCore.Design.Tests/Design/DesignTimeServicesTest.cs index 7c871ad77d0..c8a19faed77 100644 --- a/test/EFCore.Design.Tests/Design/DesignTimeServicesTest.cs +++ b/test/EFCore.Design.Tests/Design/DesignTimeServicesTest.cs @@ -8,8 +8,6 @@ using Microsoft.EntityFrameworkCore.Scaffolding.Metadata; using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Scaffolding.Internal; -using Microsoft.EntityFrameworkCore.TextTemplating; -using Microsoft.EntityFrameworkCore.TextTemplating.Internal; using Microsoft.Extensions.DependencyInjection.Extensions; namespace Microsoft.EntityFrameworkCore.Design; @@ -85,8 +83,6 @@ public class UserMigrationsIdGenerator : IMigrationsIdGenerator typeof(CSharpSnapshotGeneratorDependencies), serviceProvider.GetRequiredService().GetType()); Assert.Equal(typeof(CandidateNamingService), serviceProvider.GetRequiredService().GetType()); - Assert.Equal(typeof(CSharpDbContextGenerator), serviceProvider.GetRequiredService().GetType()); - Assert.Equal(typeof(CSharpEntityTypeGenerator), serviceProvider.GetRequiredService().GetType()); Assert.Equal(typeof(CSharpHelper), serviceProvider.GetRequiredService().GetType()); Assert.Equal( typeof(CSharpMigrationOperationGenerator), @@ -96,7 +92,6 @@ public class UserMigrationsIdGenerator : IMigrationsIdGenerator Assert.Equal(typeof(CSharpMigrationsGenerator), serviceProvider.GetRequiredService().GetType()); Assert.Equal( typeof(MigrationsCodeGeneratorSelector), serviceProvider.GetRequiredService().GetType()); - Assert.Equal(typeof(TextTemplatingService), serviceProvider.GetRequiredService().GetType()); Assert.Collection( serviceProvider.GetServices(), s => Assert.Equal(typeof(TextTemplatingModelGenerator), s.GetType()), diff --git a/test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs b/test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs index 2fc765dab8f..59402d024a8 100644 --- a/test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs +++ b/test/EFCore.Design.Tests/Design/Internal/CSharpHelperTest.cs @@ -341,7 +341,7 @@ public void Fragment_MethodCallCodeFragment_works_when_chaining() var result = new CSharpHelper(TypeMappingSource).Fragment(method); - Assert.Equal($".TestFunc(){EOL}.TestFunc()", result); + Assert.Equal($"{EOL}.TestFunc(){EOL}.TestFunc()", result); } [ConditionalFact] @@ -353,7 +353,7 @@ public void Fragment_MethodCallCodeFragment_works_when_chaining_on_chain() var result = new CSharpHelper(TypeMappingSource).Fragment(method); - Assert.Equal(@$".TestFunc(""One""){EOL}.TestFunc(""Two""){EOL}.TestFunc(""Three"")", result); + Assert.Equal(@$"{EOL}.TestFunc(""One""){EOL}.TestFunc(""Two""){EOL}.TestFunc(""Three"")", result); } [ConditionalFact] @@ -367,7 +367,7 @@ public void Fragment_MethodCallCodeFragment_works_when_chaining_on_chain_with_ca var result = new CSharpHelper(TypeMappingSource).Fragment(method); - Assert.Equal(@$".TestFunc(""One""){EOL}.TestFunc(""Two""){EOL}.TestFunc(""Three""){EOL}.TestFunc(""Four"")", result); + Assert.Equal(@$"{EOL}.TestFunc(""One""){EOL}.TestFunc(""Two""){EOL}.TestFunc(""Three""){EOL}.TestFunc(""Four"")", result); } [ConditionalFact] @@ -382,12 +382,131 @@ public void Fragment_MethodCallCodeFragment_works_when_nested_closure() Assert.Equal(".TestFunc(x => x.TestFunc())", result); } + [ConditionalFact] + public void Fragment_MethodCallCodeFragment_works_when_nested_closure_with_chain() + { + var method = new MethodCallCodeFragment( + _testFuncMethodInfo, + new NestedClosureCodeFragment( + "x", + new MethodCallCodeFragment(_testFuncMethodInfo, "One") + .Chain(new MethodCallCodeFragment(_testFuncMethodInfo, "Two")))); + + var result = new CSharpHelper(TypeMappingSource).Fragment(method); + + Assert.Equal( + @".TestFunc(x => x + .TestFunc(""One"") + .TestFunc(""Two""))", + result, + ignoreLineEndingDifferences: true); + } + + [ConditionalFact] + public void Fragment_MethodCallCodeFragment_with_indent_works_when_nested_closure_with_chain() + { + var method = new MethodCallCodeFragment( + _testFuncMethodInfo, + new NestedClosureCodeFragment( + "x", + new MethodCallCodeFragment(_testFuncMethodInfo, "One") + .Chain(new MethodCallCodeFragment(_testFuncMethodInfo, "Two")))); + + var result = new CSharpHelper(TypeMappingSource).Fragment(method, indent: 1); + + Assert.Equal( + @".TestFunc(x => x + .TestFunc(""One"") + .TestFunc(""Two""))", + result, + ignoreLineEndingDifferences: true); + } + + [ConditionalFact] + public void Fragment_MethodCallCodeFragment_with_indent_works_when_chain_and_nested_closure_with_chain() + { + var method = new MethodCallCodeFragment(_testFuncMethodInfo, "One") + .Chain( + new MethodCallCodeFragment( + _testFuncMethodInfo, + new NestedClosureCodeFragment( + "x", + new MethodCallCodeFragment(_testFuncMethodInfo, "Two") + .Chain(new MethodCallCodeFragment(_testFuncMethodInfo, "Three"))))); + + var result = new CSharpHelper(TypeMappingSource).Fragment(method, indent: 1); + + Assert.Equal( + @" + .TestFunc(""One"") + .TestFunc(x => x + .TestFunc(""Two"") + .TestFunc(""Three""))", + result, + ignoreLineEndingDifferences: true); + } + + [ConditionalFact] + public void Fragment_MethodCallCodeFragment_works_when_nested_closure_with_two_calls() + { + var method = new MethodCallCodeFragment( + _testFuncMethodInfo, + new NestedClosureCodeFragment( + "x", + new[] + { + new MethodCallCodeFragment(_testFuncMethodInfo, "One"), + new MethodCallCodeFragment(_testFuncMethodInfo, "Two") + })); + + var result = new CSharpHelper(TypeMappingSource).Fragment(method); + + Assert.Equal( + @".TestFunc(x => +{ + x.TestFunc(""One""); + x.TestFunc(""Two""); +})", + result, + ignoreLineEndingDifferences: true); + } + + [ConditionalFact] + public void Fragment_MethodCallCodeFragment_with_indent_works_when_chain_and_nested_closure() + { + var method = new MethodCallCodeFragment(_testFuncMethodInfo, "One") + .Chain( + new MethodCallCodeFragment( + _testFuncMethodInfo, + new NestedClosureCodeFragment( + "x", + new[] + { + new MethodCallCodeFragment(_testFuncMethodInfo, "Two"), + new MethodCallCodeFragment(_testFuncMethodInfo, "Three") + }))); + + var result = new CSharpHelper(TypeMappingSource).Fragment(method, indent: 1); + + Assert.Equal( + @" + .TestFunc(""One"") + .TestFunc(x => + { + x.TestFunc(""Two""); + x.TestFunc(""Three""); + })", + result, + ignoreLineEndingDifferences: true); + } + +#pragma warning disable CS0618 [ConditionalFact] public void Fragment_MethodCallCodeFragment_works_with_identifier() { var method = new MethodCallCodeFragment(_testFuncMethodInfo, true, 42); - var result = new CSharpHelper(TypeMappingSource).Fragment(method, instanceIdentifier: "builder"); + var result = new CSharpHelper(TypeMappingSource).Fragment(method, instanceIdentifier: "builder", typeQualified: false); Assert.Equal("builder.TestFunc(true, 42)", result); } @@ -397,7 +516,7 @@ public void Fragment_MethodCallCodeFragment_works_with_identifier_chained() { var method = new MethodCallCodeFragment(_testFuncMethodInfo, "One").Chain(new MethodCallCodeFragment(_testFuncMethodInfo)); - var result = new CSharpHelper(TypeMappingSource).Fragment(method, instanceIdentifier: "builder"); + var result = new CSharpHelper(TypeMappingSource).Fragment(method, instanceIdentifier: "builder", typeQualified: false); Assert.Equal($@"builder{EOL} .TestFunc(""One""){EOL} .TestFunc()", result); } @@ -407,10 +526,11 @@ public void Fragment_MethodCallCodeFragment_works_with_type_qualified() { var method = new MethodCallCodeFragment(_testFuncMethodInfo, true, 42); - var result = new CSharpHelper(TypeMappingSource).Fragment(method, instanceIdentifier: "builder"); + var result = new CSharpHelper(TypeMappingSource).Fragment(method, instanceIdentifier: "builder", typeQualified: false); Assert.Equal("builder.TestFunc(true, 42)", result); } +#pragma warning restore CS0618 [ConditionalFact] public void Really_unknown_literal_with_no_mapping_support() diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index 109664c73ec..5cba542dca9 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -329,7 +329,7 @@ public virtual void Model_annotations_are_stored_in_snapshot() .HasAnnotation(""AnnotationName"", ""AnnotationValue"") .HasAnnotation(""Relational:MaxIdentifierLength"", 128); - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); SqlServerModelBuilderExtensions.HasDatabaseMaxSize(modelBuilder, ""100 MB""); SqlServerModelBuilderExtensions.HasServiceTierSql(modelBuilder, ""'basic'""); SqlServerModelBuilderExtensions.HasPerformanceLevelSql(modelBuilder, ""'S0'"");"), @@ -392,7 +392,7 @@ public virtual void Model_default_schema_annotation_is_stored_in_snapshot_as_flu .HasAnnotation(""AnnotationName"", ""AnnotationValue"") .HasAnnotation(""Relational:MaxIdentifierLength"", 128); - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);"), + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);"), o => { Assert.Equal(6, o.GetAnnotations().Count()); @@ -417,7 +417,7 @@ public virtual void Entities_are_stored_in_model_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -430,7 +430,7 @@ public virtual void Entities_are_stored_in_model_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -469,7 +469,7 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -538,7 +538,7 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPT_with_one_exclu .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Discriminator"") .HasColumnType(""nvarchar(max)""); @@ -647,7 +647,7 @@ public virtual void Entities_are_stored_in_model_snapshot_for_TPC() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -756,7 +756,7 @@ public virtual void Entity_splitting_is_stored_in_snapshot_with_tables() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Shadow"") .HasColumnType(""int"") @@ -1102,7 +1102,7 @@ public void Unmapped_entity_types_are_stored_in_the_model_snapshot() .HasDefaultSchema(""default"") .HasAnnotation(""Relational:MaxIdentifierLength"", 128); - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithOneProperty"", b => { @@ -1258,7 +1258,7 @@ public virtual void CheckConstraint_is_stored_in_snapshot_as_fluent_api() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -1299,7 +1299,7 @@ public virtual void CheckConstraint_is_only_stored_in_snapshot_once_for_TPH() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Discriminator"") .IsRequired() @@ -1349,7 +1349,7 @@ public virtual void Trigger_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -1388,7 +1388,7 @@ public virtual void Triggers_and_ExcludeFromMigrations_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -1433,7 +1433,7 @@ public virtual void Model_use_identity_columns() @" modelBuilder.HasAnnotation(""Relational:MaxIdentifierLength"", 128); - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);"), + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);"), o => { Assert.Equal(4, o.GetAnnotations().Count()); @@ -1450,7 +1450,7 @@ public virtual void Model_use_identity_columns_custom_seed() @" modelBuilder.HasAnnotation(""Relational:MaxIdentifierLength"", 128); - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 5L, 1);"), + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 5L);"), o => { Assert.Equal(4, o.GetAnnotations().Count()); @@ -1544,7 +1544,7 @@ public virtual void EntityType_annotations_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -1575,7 +1575,7 @@ public virtual void EntityType_Fluent_APIs_are_properly_generated() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -1604,7 +1604,7 @@ public virtual void BaseType_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Discriminator"") .IsRequired() @@ -1673,7 +1673,7 @@ public virtual void Discriminator_annotations_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Discriminator"") .IsRequired() @@ -1748,7 +1748,7 @@ public virtual void Converted_discriminator_annotations_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Discriminator"") .IsRequired() @@ -1818,7 +1818,7 @@ public virtual void Properties_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -1911,7 +1911,7 @@ public virtual void Alternate_keys_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -1948,7 +1948,7 @@ public virtual void Indexes_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -1983,7 +1983,7 @@ public virtual void Indexes_are_stored_in_snapshot_including_composite_index() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -2023,7 +2023,7 @@ public virtual void Foreign_keys_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -2036,7 +2036,7 @@ public virtual void Foreign_keys_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -2111,7 +2111,7 @@ public virtual void Many_to_many_join_table_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Name"") .HasColumnType(""nvarchar(max)""); @@ -2127,7 +2127,7 @@ public virtual void Many_to_many_join_table_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Description"") .HasColumnType(""nvarchar(max)""); @@ -2255,7 +2255,7 @@ public virtual void Can_override_table_name_for_many_to_many_join_table_stored_i .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Name"") .HasColumnType(""nvarchar(max)""); @@ -2271,7 +2271,7 @@ public virtual void Can_override_table_name_for_many_to_many_join_table_stored_i .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Description"") .HasColumnType(""nvarchar(max)""); @@ -2428,7 +2428,7 @@ public virtual void Shared_columns_are_stored_in_the_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .ValueGeneratedOnUpdateSometimes() @@ -2536,7 +2536,7 @@ public virtual void AlternateKey_name_preserved_when_generic() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Property"") .HasColumnType(""uniqueidentifier""); @@ -2573,7 +2573,7 @@ public virtual void Discriminator_of_enum() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Day"") .HasColumnType(""bigint""); @@ -2604,7 +2604,7 @@ public virtual void Discriminator_of_enum_to_string() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Day"") .IsRequired() @@ -2643,7 +2643,7 @@ public virtual void Temporal_table_information_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""End"") .ValueGeneratedOnAddOrUpdate() @@ -2663,16 +2663,15 @@ public virtual void Temporal_table_information_is_stored_in_snapshot() b.ToTable(""EntityWithStringProperty""); b.ToTable(tb => tb.IsTemporal(ttb => - { - ttb.UseHistoryTable(""HistoryTable""); - ttb - .HasPeriodStart(""Start"") - .HasColumnName(""PeriodStart""); - ttb - .HasPeriodEnd(""End"") - .HasColumnName(""PeriodEnd""); - } - )); + { + ttb.UseHistoryTable(""HistoryTable""); + ttb + .HasPeriodStart(""Start"") + .HasColumnName(""PeriodStart""); + ttb + .HasPeriodEnd(""End"") + .HasColumnName(""PeriodEnd""); + })); });", usingSystem: true), o => { @@ -2705,7 +2704,7 @@ public virtual void Temporal_table_information_is_stored_in_snapshot_minimal_set .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Name"") .HasColumnType(""nvarchar(max)""); @@ -2725,16 +2724,15 @@ public virtual void Temporal_table_information_is_stored_in_snapshot_minimal_set b.ToTable(""EntityWithStringProperty""); b.ToTable(tb => tb.IsTemporal(ttb => - { - ttb.UseHistoryTable(""EntityWithStringPropertyHistory""); - ttb - .HasPeriodStart(""PeriodStart"") - .HasColumnName(""PeriodStart""); - ttb - .HasPeriodEnd(""PeriodEnd"") - .HasColumnName(""PeriodEnd""); - } - )); + { + ttb.UseHistoryTable(""EntityWithStringPropertyHistory""); + ttb + .HasPeriodStart(""PeriodStart"") + .HasColumnName(""PeriodStart""); + ttb + .HasPeriodEnd(""PeriodEnd"") + .HasColumnName(""PeriodEnd""); + })); });", usingSystem: true), o => { @@ -2803,7 +2801,7 @@ public virtual void Owned_types_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id"") .HasName(""PK_Custom""); @@ -2884,7 +2882,7 @@ public virtual void Owned_types_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property(""Id"")); b1.Property(""EntityWithOnePropertyId"") .HasColumnType(""int""); @@ -3031,7 +3029,7 @@ public virtual void Owned_types_are_stored_in_snapshot_when_excluded() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id"") .HasName(""PK_Custom""); @@ -3116,7 +3114,7 @@ public virtual void Owned_types_are_stored_in_snapshot_when_excluded() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property(""Id"")); b1.Property(""EntityWithOnePropertyId"") .HasColumnType(""int""); @@ -3228,7 +3226,7 @@ public virtual void Shared_owned_types_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -3392,7 +3390,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder.HasAnnotation(""Relational:MaxIdentifierLength"", 128); - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+TestOwner"", b => { @@ -3400,7 +3398,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -3475,7 +3473,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder.HasAnnotation(""Relational:MaxIdentifierLength"", 128); - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+TestOwner"", b => { @@ -3483,7 +3481,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -3501,7 +3499,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b1.Property(""Id"")); b1.Property(""TestEnum"") .HasColumnType(""int""); @@ -3581,7 +3579,7 @@ public virtual void Property_annotations_are_stored_in_snapshot() .HasColumnType(""int"") .HasAnnotation(""AnnotationName"", ""AnnotationValue""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -3607,7 +3605,7 @@ public virtual void Custom_value_generator_is_ignored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -3629,7 +3627,7 @@ public virtual void Property_isNullable_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Name"") .IsRequired() @@ -3658,7 +3656,7 @@ public virtual void Property_ValueGenerated_value_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .ValueGeneratedOnAdd() @@ -3723,7 +3721,7 @@ public virtual void Property_maxLength_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Name"") .HasMaxLength(100) @@ -3748,7 +3746,7 @@ public virtual void Property_unicodeness_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Name"") .IsUnicode(false) @@ -3773,7 +3771,7 @@ public virtual void Property_fixedlengthness_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Name"") .HasMaxLength(100) @@ -3802,7 +3800,7 @@ public virtual void Property_precision_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Price"") .HasPrecision(7) @@ -3835,7 +3833,7 @@ public virtual void Property_precision_and_scale_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Price"") .HasPrecision(7, 3) @@ -3872,7 +3870,7 @@ public virtual void Many_facets_chained_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Name"") .HasMaxLength(100) @@ -3909,7 +3907,7 @@ public virtual void Property_concurrencyToken_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .IsConcurrencyToken() @@ -3938,7 +3936,7 @@ public virtual void Property_column_name_annotation_is_stored_in_snapshot_as_flu .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int"") @@ -3967,7 +3965,7 @@ public virtual void Property_column_type_annotation_is_stored_in_snapshot_as_flu .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""CType""); @@ -3995,7 +3993,7 @@ public virtual void Property_default_value_annotation_is_stored_in_snapshot_as_f .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .ValueGeneratedOnAdd() @@ -4025,7 +4023,7 @@ public virtual void Property_default_value_annotation_is_stored_in_snapshot_as_f .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .ValueGeneratedOnAdd() @@ -4056,7 +4054,7 @@ public virtual void Property_default_value_sql_annotation_is_stored_in_snapshot_ .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .ValueGeneratedOnAdd() @@ -4086,7 +4084,7 @@ public virtual void Property_default_value_sql_annotation_is_stored_in_snapshot_ .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .ValueGeneratedOnAdd() @@ -4116,7 +4114,7 @@ public virtual void Property_computed_column_sql_annotation_is_stored_in_snapsho .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .ValueGeneratedOnAddOrUpdate() @@ -4146,7 +4144,7 @@ public virtual void Property_computed_column_sql_stored_annotation_is_stored_in_ .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .ValueGeneratedOnAddOrUpdate() @@ -4180,7 +4178,7 @@ public virtual void Property_computed_column_sql_annotation_is_stored_in_snapsho .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .ValueGeneratedOnAddOrUpdate() @@ -4206,7 +4204,7 @@ public virtual void Property_default_value_of_enum_type_is_stored_in_snapshot_wi .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Day"") .ValueGeneratedOnAdd() @@ -4239,7 +4237,7 @@ public virtual void Property_enum_type_is_stored_in_snapshot_with_custom_convers .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Day"") .IsRequired() @@ -4279,7 +4277,7 @@ public virtual void Property_of_nullable_enum() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Day"") .HasColumnType(""bigint""); @@ -4304,7 +4302,7 @@ public virtual void Property_of_enum_to_nullable() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Day"") .HasColumnType(""bigint""); @@ -4328,7 +4326,7 @@ public virtual void Property_of_nullable_enum_to_string() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Day"") .HasColumnType(""nvarchar(max)""); @@ -4357,7 +4355,7 @@ public virtual void Property_multiple_annotations_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int"") @@ -4407,7 +4405,7 @@ public virtual void Property_without_column_type() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -4443,7 +4441,7 @@ public virtual void Property_with_identity_column() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -4481,7 +4479,7 @@ public virtual void Property_with_identity_column_custom_seed() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 5L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 5L); b.HasKey(""Id""); @@ -4588,7 +4586,7 @@ public virtual void Property_column_order_annotation_is_stored_in_snapshot_as_fl .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int"") @@ -4608,7 +4606,7 @@ public virtual void SQLServer_model_legacy_identity_seed_int_annotation() @" modelBuilder.HasAnnotation(""Relational:MaxIdentifierLength"", 128); - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 8L, 1);"), + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 8L);"), o => Assert.Equal(8L, o.GetIdentitySeed())); [ConditionalFact] @@ -4629,7 +4627,7 @@ public virtual void SQLServer_property_legacy_identity_seed_int_annotation() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 8L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 8L); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -4662,7 +4660,7 @@ public virtual void Key_annotations_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -4694,7 +4692,7 @@ public virtual void Key_Fluent_APIs_are_properly_generated() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -4721,7 +4719,7 @@ public virtual void Key_name_annotation_is_stored_in_snapshot_as_fluent_api() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -4754,7 +4752,7 @@ public virtual void Key_multiple_annotations_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -4797,7 +4795,7 @@ public virtual void Index_annotations_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -4828,7 +4826,7 @@ public virtual void Index_Fluent_APIs_are_properly_generated() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -4860,7 +4858,7 @@ public virtual void Index_IsUnique_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -4900,7 +4898,7 @@ public virtual void Index_IsDescending_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""X"") .HasColumnType(""int""); @@ -4963,7 +4961,7 @@ public virtual void Index_database_name_annotation_is_stored_in_snapshot_as_flue .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -5000,7 +4998,7 @@ public virtual void Index_filter_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -5034,7 +5032,7 @@ public virtual void Index_multiple_annotations_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -5075,7 +5073,7 @@ public virtual void Index_with_default_constraint_name_exceeding_max() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Name"") .HasColumnType(""nvarchar(max)""); @@ -5104,7 +5102,7 @@ public virtual void IndexAttribute_causes_column_to_have_key_or_index_column_len .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""FirstName"") .HasColumnType(""nvarchar(450)""); @@ -5146,7 +5144,7 @@ public virtual void IndexAttribute_name_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""FirstName"") .HasColumnType(""nvarchar(450)""); @@ -5192,7 +5190,7 @@ public virtual void IndexAttribute_IsUnique_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""FirstName"") .HasColumnType(""nvarchar(450)""); @@ -5244,7 +5242,7 @@ public virtual void IndexAttribute_IncludeProperties_generated_without_fluent_ap .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Name"") .HasColumnType(""nvarchar(max)""); @@ -5287,7 +5285,7 @@ public virtual void ForeignKey_annotations_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -5300,7 +5298,7 @@ public virtual void ForeignKey_annotations_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -5363,7 +5361,7 @@ public virtual void ForeignKey_isRequired_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Name"") .IsRequired() @@ -5416,7 +5414,7 @@ public virtual void ForeignKey_isUnique_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Name"") .HasColumnType(""nvarchar(450)""); @@ -5461,7 +5459,7 @@ public virtual void ForeignKey_with_non_primary_principal_is_stored_in_snapshot( .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .IsRequired() @@ -5478,7 +5476,7 @@ public virtual void ForeignKey_with_non_primary_principal_is_stored_in_snapshot( .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Name"") .HasColumnType(""nvarchar(450)""); @@ -5534,7 +5532,7 @@ public virtual void ForeignKey_deleteBehavior_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -5586,7 +5584,7 @@ public virtual void ForeignKey_deleteBehavior_is_stored_in_snapshot_for_one_to_o .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -5647,7 +5645,7 @@ public virtual void ForeignKey_name_preserved_when_generic() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Property"") .HasColumnType(""uniqueidentifier""); @@ -5715,7 +5713,7 @@ public virtual void ForeignKey_constraint_name_is_stored_in_snapshot_as_fluent_a .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -5728,7 +5726,7 @@ public virtual void ForeignKey_constraint_name_is_stored_in_snapshot_as_fluent_a .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -5781,7 +5779,7 @@ public virtual void ForeignKey_multiple_annotations_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -5794,7 +5792,7 @@ public virtual void ForeignKey_multiple_annotations_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -5850,7 +5848,7 @@ public virtual void Do_not_generate_entity_type_builder_again_if_no_foreign_key_ .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Discriminator"") .IsRequired() @@ -5876,7 +5874,7 @@ public virtual void Do_not_generate_entity_type_builder_again_if_no_foreign_key_ .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -5930,7 +5928,7 @@ public virtual void ForeignKey_principal_key_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -5994,7 +5992,7 @@ public virtual void ForeignKey_principal_key_with_non_default_name_is_stored_in_ .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -6057,7 +6055,7 @@ public virtual void Navigation_annotations_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -6070,7 +6068,7 @@ public virtual void Navigation_annotations_are_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -6124,7 +6122,7 @@ public virtual void Navigation_isRequired_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.HasKey(""Id""); @@ -6137,7 +6135,7 @@ public virtual void Navigation_isRequired_is_stored_in_snapshot() .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""AlternateId"") .HasColumnType(""int""); @@ -6332,7 +6330,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) #pragma warning disable 612, 618 modelBuilder.HasAnnotation(""Relational:MaxIdentifierLength"", 128); - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1); + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithManyProperties"", b => { @@ -6340,7 +6338,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .ValueGeneratedOnAdd() .HasColumnType(""int""); - SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id""), 1L, 1); + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property(""Id"")); b.Property(""Boolean"") .HasColumnType(""bit""); @@ -6679,7 +6677,7 @@ protected virtual string GetHeading(bool empty = false) => @" modelBuilder.HasAnnotation(""Relational:MaxIdentifierLength"", 128); - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);" + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);" + (empty ? null : @" diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs index 0265be6861f..93f4b47f03d 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs @@ -1167,16 +1167,15 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity(entity => { entity.ToTable(tb => tb.IsTemporal(ttb => - { - ttb.UseHistoryTable(""CustomerHistory""); - ttb - .HasPeriodStart(""PeriodStart"") - .HasColumnName(""PeriodStart""); - ttb - .HasPeriodEnd(""PeriodEnd"") - .HasColumnName(""PeriodEnd""); - } -)); + { + ttb.UseHistoryTable(""CustomerHistory""); + ttb + .HasPeriodStart(""PeriodStart"") + .HasColumnName(""PeriodStart""); + ttb + .HasPeriodEnd(""PeriodEnd"") + .HasColumnName(""PeriodEnd""); + })); entity.Property(e => e.Id).UseIdentityColumn(); }); @@ -1195,6 +1194,37 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) // TODO })).Message); + [ConditionalFact] + public void Sequences_work() + => Test( + modelBuilder => modelBuilder.HasSequence("EvenNumbers", "dbo") + .StartsAt(2) + .IncrementsBy(2) + .HasMin(2) + .HasMax(100) + .IsCyclic(true), + new ModelCodeGenerationOptions(), + code => Assert.Contains( + @".HasSequence(""EvenNumbers"", ""dbo"")" + _nl + + " .StartsAt(2)" + _nl + + " .IncrementsBy(2)" + _nl + + " .HasMin(2)" + _nl + + " .HasMax(100)" + _nl + + " .IsCyclic();", + code.ContextFile.Code), + model => + { + var sequence = model.FindSequence("EvenNumbers", "dbo"); + Assert.NotNull(sequence); + + Assert.Equal(typeof(int), sequence.Type); + Assert.Equal(2, sequence.StartValue); + Assert.Equal(2, sequence.IncrementBy); + Assert.Equal(2, sequence.MinValue); + Assert.Equal(100, sequence.MaxValue); + Assert.True(sequence.IsCyclic); + }); + [ConditionalFact] public void Trigger_works() => Test( diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index ded382af523..eb9e5cc0938 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -840,10 +840,10 @@ public FullyQualifiedCSharpHelper(ITypeMappingSource typeMappingSource) { } - public override bool ShouldUseFullName(Type type) + protected override bool ShouldUseFullName(Type type) => base.ShouldUseFullName(type); - public override bool ShouldUseFullName(string shortTypeName) + protected override bool ShouldUseFullName(string shortTypeName) => base.ShouldUseFullName(shortTypeName) || shortTypeName == nameof(Index) || shortTypeName == nameof(Internal); diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs index bc35f0c1a74..70496fc7542 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/RelationalScaffoldingModelFactoryTest.cs @@ -109,7 +109,7 @@ public void Creates_entity_types() Assert.NotNull(view.FindAnnotation(RelationalAnnotationNames.ViewDefinitionSql)); } ); - Assert.Empty(model.GetEntityTypeErrors().Values); + Assert.Empty(model.GetReverseEngineeringErrors()); } [ConditionalFact] diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/ScaffoldingMetadataExtensionsTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/ScaffoldingMetadataExtensionsTest.cs index 8ee140252fb..54155395e9e 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/ScaffoldingMetadataExtensionsTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/ScaffoldingMetadataExtensionsTest.cs @@ -13,17 +13,17 @@ public void It_sets_gets_entity_type_errors() { IMutableModel model = new Model(); - Assert.Empty(model.GetEntityTypeErrors().Values); + Assert.Empty(model.GetReverseEngineeringErrors()); - model.GetOrCreateEntityTypeErrors().Add("ET", "FAIL!"); - Assert.Equal("FAIL!", model.GetEntityTypeErrors()["ET"]); + model.GetOrCreateReverseEngineeringErrors().Add("FAIL!"); + Assert.Equal("FAIL!", Assert.Single(model.GetReverseEngineeringErrors())); - model.SetEntityTypeErrors(new Dictionary()); - Assert.Empty(model.GetEntityTypeErrors().Values); + model.SetReverseEngineeringErrors(new List()); + Assert.Empty(model.GetReverseEngineeringErrors()); - model.GetOrCreateEntityTypeErrors()["ET"] = "FAIL 2!"; - model.GetOrCreateEntityTypeErrors().Clear(); - Assert.Empty(model.GetEntityTypeErrors().Values); + model.GetOrCreateReverseEngineeringErrors().Add("FAIL 2!"); + model.GetOrCreateReverseEngineeringErrors().Clear(); + Assert.Empty(model.GetReverseEngineeringErrors()); } [ConditionalFact] diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/TextTemplatingEngineHostTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/TextTemplatingEngineHostTest.cs new file mode 100644 index 00000000000..08aa4e24576 --- /dev/null +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/TextTemplatingEngineHostTest.cs @@ -0,0 +1,162 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.CodeDom.Compiler; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.TestUtilities.Xunit; +using Microsoft.VisualStudio.TextTemplating; +using Engine = Mono.TextTemplating.TemplatingEngine; + +namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +[PlatformSkipCondition(TestPlatform.Linux, SkipReason = "CI time out")] +public class TextTemplatingEngineHostTest +{ + public static readonly Engine _engine = new Engine(); + + [ConditionalFact] + public void Service_works() + { + var host = new TextTemplatingEngineHost( + new ServiceCollection() + .AddSingleton("Hello, Services!") + .BuildServiceProvider()); + + var result = _engine.ProcessTemplate( + @"<#@ template hostSpecific=""true"" #><#= ((IServiceProvider)Host).GetService(typeof(string)) #>", + host); + + Assert.Empty(host.Errors); + Assert.Equal("Hello, Services!", result); + } + + [ConditionalFact] + public void Session_works() + { + var host = new TextTemplatingEngineHost + { + Session = new TextTemplatingSession + { + ["Value"] = "Hello, Session!" + } + }; + + var result = _engine.ProcessTemplate( + @"<#= Session[""Value""] #>", + host); + + Assert.Empty(host.Errors); + Assert.Equal("Hello, Session!", result); + } + + [ConditionalFact] + public void Session_works_with_parameter() + { + var host = new TextTemplatingEngineHost + { + Session = new TextTemplatingSession + { + ["Value"] = "Hello, Session!" + } + }; + + var result = _engine.ProcessTemplate( + @"<#@ parameter name=""Value"" type=""System.String"" #><#= Value #>", + host); + + Assert.Empty(host.Errors); + Assert.Equal("Hello, Session!", result); + } + + [ConditionalFact] + public void Include_works() + { + using var dir = new TempDirectory(); + File.WriteAllText( + Path.Combine(dir, "test.ttinclude"), + "Hello, Include!"); + + var host = new TextTemplatingEngineHost + { + TemplateFile = Path.Combine(dir, "test.tt") + }; + + var result = _engine.ProcessTemplate( + @"<#@ include file=""test.ttinclude"" #>", + host); + + Assert.Empty(host.Errors); + Assert.Equal("Hello, Include!", result); + } + + [ConditionalFact] + public void Error_works() + { + var host = new TextTemplatingEngineHost(); + + _engine.ProcessTemplate( + @"<# Error(""Hello, Error!""); #>", + host); + + var error = Assert.Single(host.Errors.Cast()); + Assert.Equal("Hello, Error!", error.ErrorText); + } + + [ConditionalFact] + public void Directive_throws_when_processor_unknown() + { + var host = new TextTemplatingEngineHost(); + + var ex = Assert.Throws( + () => _engine.ProcessTemplate( + @"<#@ test processor=""TestDirectiveProcessor"" #>", + host)); + + Assert.Equal(DesignStrings.UnknownDirectiveProcessor("TestDirectiveProcessor"), ex.Message); + } + + [ConditionalFact] + public void ResolvePath_work() + { + using var dir = new TempDirectory(); + + var host = new TextTemplatingEngineHost + { + TemplateFile = Path.Combine(dir, "test.tt") + }; + + var result = _engine.ProcessTemplate( + @"<#@ template hostSpecific=""true"" #><#= Host.ResolvePath(""data.json"") #>", + host); + + Assert.Empty(host.Errors); + Assert.Equal(Path.Combine(dir, "data.json"), result); + } + + [ConditionalFact] + public void Output_works() + { + var host = new TextTemplatingEngineHost(); + + _engine.ProcessTemplate( + @"<#@ output extension="".txt"" encoding=""us-ascii"" #>", + host); + + Assert.Empty(host.Errors); + Assert.Equal(".txt", host.Extension); + Assert.Equal(Encoding.ASCII, host.OutputEncoding); + } + + [ConditionalFact] + public void Assembly_works() + { + var host = new TextTemplatingEngineHost(); + + var result = _engine.ProcessTemplate( + @"<#@ assembly name=""Microsoft.EntityFrameworkCore"" #><#= nameof(Microsoft.EntityFrameworkCore.DbContext) #>", + host); + + Assert.Empty(host.Errors); + Assert.Equal("DbContext", result); + } +} diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/TextTemplatingModelGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/TextTemplatingModelGeneratorTest.cs index 91c75986478..178699a07e2 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/TextTemplatingModelGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/TextTemplatingModelGeneratorTest.cs @@ -27,6 +27,22 @@ public void HasTemplates_works_when_templates() Assert.True(result); } + [ConditionalFact] + public void HasTemplates_throws_when_configuration_but_no_context() + { + using var projectDir = new TempDirectory(); + + var template = Path.Combine(projectDir, "CodeTemplates", "EFCore", "EntityTypeConfiguration.t4"); + Directory.CreateDirectory(Path.GetDirectoryName(template)); + File.Create(template).Close(); + + var generator = CreateGenerator(); + + var ex = Assert.Throws(() => generator.HasTemplates(projectDir)); + + Assert.Equal(DesignStrings.NoContextTemplateButConfiguration, ex.Message); + } + [ConditionalFact] public void HasTemplates_works_when_no_templates() { @@ -54,6 +70,10 @@ public void GenerateModel_uses_templates() Path.Combine(projectDir, "CodeTemplates", "EFCore", "EntityType.t4"), "My entity type template"); + File.WriteAllText( + Path.Combine(projectDir, "CodeTemplates", "EFCore", "EntityTypeConfiguration.t4"), + "My entity type configuration template"); + var generator = CreateGenerator(); var model = new ModelBuilder() .Entity("Entity1", b => { }) @@ -71,9 +91,13 @@ public void GenerateModel_uses_templates() Assert.Equal("Context.cs", result.ContextFile.Path); Assert.Equal("My DbContext template", result.ContextFile.Code); - var entityType = Assert.Single(result.AdditionalFiles); - Assert.Equal("Entity1.cs", entityType.Path); + Assert.Equal(2, result.AdditionalFiles.Count); + + var entityType = Assert.Single(result.AdditionalFiles, f => f.Path == "Entity1.cs"); Assert.Equal("My entity type template", entityType.Code); + + var entityTypeConfiguration = Assert.Single(result.AdditionalFiles, f => f.Path == "Entity1Configuration.cs"); + Assert.Equal("My entity type configuration template", entityTypeConfiguration.Code); } [ConditionalFact] @@ -107,6 +131,33 @@ public void GenerateModel_works_when_no_entity_type_template() Assert.Empty(result.AdditionalFiles); } + [ConditionalFact] + public void GenerateModel_throws_when_no_context_template() + { + using var projectDir = new TempDirectory(); + + var template = Path.Combine(projectDir, "CodeTemplates", "EFCore", "EntityType.t4"); + Directory.CreateDirectory(Path.GetDirectoryName(template)); + File.Create(template).Close(); + + var generator = CreateGenerator(); + var model = new ModelBuilder() + .Entity("Entity1", b => { }) + .FinalizeModel(); + + var ex = Assert.Throws( + () => generator.GenerateModel( + model, + new() + { + ContextName = "Context", + ConnectionString = @"Name=DefaultConnection", + ProjectDir = projectDir + })); + + Assert.Equal(DesignStrings.NoContextTemplate, ex.Message); + } + [ConditionalFact] public void GenerateModel_sets_session_variables() { @@ -126,6 +177,13 @@ public void GenerateModel_sets_session_variables() @"EntityType not null: <#= Session[""EntityType""] != null #> Options not null: <#= Session[""Options""] != null #> NamespaceHint: <#= Session[""NamespaceHint""] #> +ProjectDefaultNamespace: <#= Session[""ProjectDefaultNamespace""] #>"); + + File.WriteAllText( + Path.Combine(projectDir, "CodeTemplates", "EFCore", "EntityTypeConfiguration.t4"), + @"EntityType not null: <#= Session[""EntityType""] != null #> +Options not null: <#= Session[""Options""] != null #> +NamespaceHint: <#= Session[""NamespaceHint""] #> ProjectDefaultNamespace: <#= Session[""ProjectDefaultNamespace""] #>"); var generator = CreateGenerator(); @@ -152,13 +210,23 @@ public void GenerateModel_sets_session_variables() ProjectDefaultNamespace: RootNamespace", result.ContextFile.Code); - var entityType = Assert.Single(result.AdditionalFiles); + Assert.Equal(2, result.AdditionalFiles.Count); + + var entityType = Assert.Single(result.AdditionalFiles, f => f.Path == "Entity1.cs"); Assert.Equal( @"EntityType not null: True Options not null: True NamespaceHint: ModelNamespace ProjectDefaultNamespace: RootNamespace", entityType.Code); + + var entityTypeConfiguration = Assert.Single(result.AdditionalFiles, f => f.Path == "Entity1Configuration.cs"); + Assert.Equal( + @"EntityType not null: True +Options not null: True +NamespaceHint: ContextNamespace +ProjectDefaultNamespace: RootNamespace", + entityTypeConfiguration.Code); } [ConditionalFact] @@ -172,8 +240,13 @@ public void GenerateModel_defaults_to_model_namespace_when_no_context_namespace( contextTemplate, @"<#= Session[""NamespaceHint""] #>"); + File.WriteAllText( + Path.Combine(projectDir, "CodeTemplates", "EFCore", "EntityTypeConfiguration.t4"), + @"<#= Session[""NamespaceHint""] #>"); + var generator = CreateGenerator(); var model = new ModelBuilder() + .Entity("Entity1", b => { }) .FinalizeModel(); var result = generator.GenerateModel( @@ -189,6 +262,11 @@ public void GenerateModel_defaults_to_model_namespace_when_no_context_namespace( Assert.Equal( "ModelNamespace", result.ContextFile.Code); + + var entityTypeConfiguration = Assert.Single(result.AdditionalFiles); + Assert.Equal( + "ModelNamespace", + entityTypeConfiguration.Code); } [ConditionalFact] @@ -207,6 +285,11 @@ public void GenerateModel_uses_output_extension() @"<#@ output extension="".fs"" #> My entity type template"); + File.WriteAllText( + Path.Combine(projectDir, "CodeTemplates", "EFCore", "EntityTypeConfiguration.t4"), + @"<#@ output extension="".py"" #> +My entity type configuration template"); + var generator = CreateGenerator(); var model = new ModelBuilder() .Entity("Entity1", b => { }) @@ -223,8 +306,9 @@ public void GenerateModel_uses_output_extension() Assert.Equal("Context.vb", result.ContextFile.Path); - var entityType = Assert.Single(result.AdditionalFiles); - Assert.Equal("Entity1.fs", entityType.Path); + Assert.Equal(2, result.AdditionalFiles.Count); + Assert.Single(result.AdditionalFiles, f => f.Path == "Entity1.fs"); + Assert.Single(result.AdditionalFiles, f => f.Path == "Entity1Configuration.py"); } [ConditionalFact] @@ -278,14 +362,17 @@ public void GenerateModel_reports_errors() var model = new ModelBuilder() .FinalizeModel(); - var result = generator.GenerateModel( - model, - new() - { - ContextName = "Context", - ConnectionString = @"Name=DefaultConnection", - ProjectDir = projectDir - }); + var ex = Assert.Throws( + () => generator.GenerateModel( + model, + new() + { + ContextName = "Context", + ConnectionString = @"Name=DefaultConnection", + ProjectDir = projectDir + })); + + Assert.Equal(DesignStrings.ErrorGeneratingOutput(contextTemplate), ex.Message); Assert.Collection( reporter.Messages, @@ -301,6 +388,44 @@ public void GenerateModel_reports_errors() }); } + [ConditionalFact] + public void GenerateModel_reports_compiler_errors() + { + using var projectDir = new TempDirectory(); + + var contextTemplate = Path.Combine(projectDir, "CodeTemplates", "EFCore", "DbContext.t4"); + Directory.CreateDirectory(Path.GetDirectoryName(contextTemplate)); + File.WriteAllText( + contextTemplate, + "<# #error This is a compiler error #>"); + + + var reporter = new TestOperationReporter(); + var generator = CreateGenerator(reporter); + var model = new ModelBuilder() + .FinalizeModel(); + + var ex = Assert.Throws( + () => generator.GenerateModel( + model, + new() + { + ContextName = "Context", + ConnectionString = @"Name=DefaultConnection", + ProjectDir = projectDir + })); + + Assert.Equal(DesignStrings.ErrorGeneratingOutput(contextTemplate), ex.Message); + + Assert.Collection( + reporter.Messages, + x => + { + Assert.Equal(LogLevel.Error, x.Level); + Assert.Contains("DbContext.t4(1,9) : error CS1029: #error: 'This is a compiler error '", x.Message); + }); + } + private static TemplatedModelGenerator CreateGenerator(IOperationReporter reporter = null) { var serviceCollection = new ServiceCollection() @@ -310,7 +435,7 @@ private static TemplatedModelGenerator CreateGenerator(IOperationReporter report return serviceCollection .BuildServiceProvider() .GetServices() - .OfType() + .OfType() .Last(); } } diff --git a/test/EFCore.Design.Tests/TextTemplating/Internal/TextTemplatingServiceTest.cs b/test/EFCore.Design.Tests/TextTemplating/Internal/TextTemplatingServiceTest.cs deleted file mode 100644 index 64159cb9898..00000000000 --- a/test/EFCore.Design.Tests/TextTemplating/Internal/TextTemplatingServiceTest.cs +++ /dev/null @@ -1,183 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.CodeDom.Compiler; -using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.TestUtilities.Xunit; -using Microsoft.VisualStudio.TextTemplating; - -namespace Microsoft.EntityFrameworkCore.TextTemplating.Internal; - -[PlatformSkipCondition(TestPlatform.Linux, SkipReason = "CI time out")] -public class TextTemplatingServiceTest -{ - [ConditionalFact] - public void Service_works() - { - var host = new TextTemplatingService( - new ServiceCollection() - .AddSingleton("Hello, Services!") - .BuildServiceProvider()); - var callback = new TextTemplatingCallback(); - - var result = host.ProcessTemplate( - @"T:\test.tt", - @"<#@ template hostSpecific=""true"" #><#= ((IServiceProvider)Host).GetService(typeof(string)) #>", - callback); - - Assert.Empty(callback.Errors); - Assert.Equal("Hello, Services!", result); - } - - [ConditionalFact] - public void Session_works() - { - var host = new TextTemplatingService( - new ServiceCollection() - .BuildServiceProvider()); - host.Session = new TextTemplatingSession - { - ["Value"] = "Hello, Session!" - }; - var callback = new TextTemplatingCallback(); - - var result = host.ProcessTemplate( - @"T:\test.tt", - @"<#= Session[""Value""] #>", - callback); - - Assert.Empty(callback.Errors); - Assert.Equal("Hello, Session!", result); - } - - [ConditionalFact] - public void Session_works_with_parameter() - { - var host = new TextTemplatingService( - new ServiceCollection() - .BuildServiceProvider()); - host.Session = new TextTemplatingSession - { - ["Value"] = "Hello, Session!" - }; - var callback = new TextTemplatingCallback(); - - var result = host.ProcessTemplate( - @"T:\test.tt", - @"<#@ parameter name=""Value"" type=""System.String"" #><#= Value #>", - callback); - - Assert.Empty(callback.Errors); - Assert.Equal("Hello, Session!", result); - } - - [ConditionalFact] - public void Include_works() - { - using var dir = new TempDirectory(); - File.WriteAllText( - Path.Combine(dir, "test.ttinclude"), - "Hello, Include!"); - - var host = new TextTemplatingService( - new ServiceCollection() - .BuildServiceProvider()); - var callback = new TextTemplatingCallback(); - - var result = host.ProcessTemplate( - Path.Combine(dir, "test.tt"), - @"<#@ include file=""test.ttinclude"" #>", - callback); - - Assert.Empty(callback.Errors); - Assert.Equal("Hello, Include!", result); - } - - [ConditionalFact] - public void Error_works() - { - var host = new TextTemplatingService( - new ServiceCollection() - .BuildServiceProvider()); - var callback = new TextTemplatingCallback(); - - host.ProcessTemplate( - @"T:\test.tt", - @"<# Error(""Hello, Error!""); #>", - callback); - - var error = Assert.Single(callback.Errors.Cast()); - Assert.Equal("Hello, Error!", error.ErrorText); - } - - [ConditionalFact] - public void Directive_throws_when_processor_unknown() - { - var host = new TextTemplatingService( - new ServiceCollection() - .BuildServiceProvider()); - var callback = new TextTemplatingCallback(); - - var ex = Assert.Throws( - () => host.ProcessTemplate( - @"T:\test.tt", - @"<#@ test processor=""TestDirectiveProcessor"" #>", - callback)); - - Assert.Equal(DesignStrings.UnknownDirectiveProcessor("TestDirectiveProcessor"), ex.Message); - } - - [ConditionalFact] - public void ResolvePath_work() - { - using var dir = new TempDirectory(); - - var host = new TextTemplatingService( - new ServiceCollection() - .BuildServiceProvider()); - var callback = new TextTemplatingCallback(); - - var result = host.ProcessTemplate( - Path.Combine(dir, "test.tt"), - @"<#@ template hostSpecific=""true"" #><#= Host.ResolvePath(""data.json"") #>", - callback); - - Assert.Empty(callback.Errors); - Assert.Equal(Path.Combine(dir, "data.json"), result); - } - - [ConditionalFact] - public void Output_works() - { - var host = new TextTemplatingService( - new ServiceCollection() - .BuildServiceProvider()); - var callback = new TextTemplatingCallback(); - - host.ProcessTemplate( - @"T:\test.tt", - @"<#@ output extension="".txt"" encoding=""us-ascii"" #>", - callback); - - Assert.Empty(callback.Errors); - Assert.Equal(".txt", callback.Extension); - Assert.Equal(Encoding.ASCII, callback.OutputEncoding); - } - - [ConditionalFact] - public void Assembly_works() - { - var host = new TextTemplatingService( - new ServiceCollection() - .BuildServiceProvider()); - var callback = new TextTemplatingCallback(); - - var result = host.ProcessTemplate( - @"T:\test.tt", - @"<#@ assembly name=""Microsoft.EntityFrameworkCore"" #><#= nameof(Microsoft.EntityFrameworkCore.DbContext) #>", - callback); - - Assert.Empty(callback.Errors); - Assert.Equal("DbContext", result); - } -} diff --git a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs index 1f9521f13db..883cfe20799 100644 --- a/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs +++ b/test/EFCore.Specification.Tests/ApiConsistencyTestBase.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.CodeDom.Compiler; using System.Runtime.CompilerServices; // ReSharper disable InconsistentNaming @@ -841,6 +842,7 @@ var nonVirtualMethods = (from type in GetAllTypes(TargetAssembly.GetTypes()) where type.IsVisible && !type.IsSealed + && !type.GetCustomAttributes().Any() from method in type.GetMethods(AnyInstance) where method.DeclaringType == type && !Fixture.NonVirtualMethods.Contains(method) diff --git a/test/EFCore.Specification.Tests/TestUtilities/TestOperationReporter.cs b/test/EFCore.Specification.Tests/TestUtilities/TestOperationReporter.cs index 2d8fae0e658..76ae0cc7712 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/TestOperationReporter.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/TestOperationReporter.cs @@ -7,8 +7,12 @@ namespace Microsoft.EntityFrameworkCore.TestUtilities; public class TestOperationReporter : IOperationReporter { + private readonly ITestOutputHelper _output; private readonly List<(LogLevel, string)> _messages = new(); + public TestOperationReporter(ITestOutputHelper output = null) + => _output = output; + public IReadOnlyList<(LogLevel Level, string Message)> Messages => _messages; @@ -16,14 +20,26 @@ public void Clear() => _messages.Clear(); public void WriteInformation(string message) - => _messages.Add((LogLevel.Information, message)); + { + _output?.WriteLine("info: " + message); + _messages.Add((LogLevel.Information, message)); + } public void WriteVerbose(string message) - => _messages.Add((LogLevel.Debug, message)); + { + _output?.WriteLine("verbose: " + message); + _messages.Add((LogLevel.Debug, message)); + } public void WriteWarning(string message) - => _messages.Add((LogLevel.Warning, message)); + { + _output?.WriteLine("warn: " + message); + _messages.Add((LogLevel.Warning, message)); + } public void WriteError(string message) - => _messages.Add((LogLevel.Error, message)); + { + _output?.WriteLine("error: " + message); + _messages.Add((LogLevel.Error, message)); + } } diff --git a/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs b/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs index 4d45e99c49b..477909508d4 100644 --- a/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs +++ b/test/EFCore.SqlServer.Tests/Design/Internal/SqlServerAnnotationCodeGeneratorTest.cs @@ -205,10 +205,7 @@ public void GenerateFluentApi_IProperty_works_with_identity_default_seed_increme Assert.Equal("UseIdentityColumn", result.Method); Assert.Equal("SqlServerPropertyBuilderExtensions", result.DeclaringType); - Assert.Collection( - result.Arguments, - seed => Assert.Equal(1L, seed), - increment => Assert.Equal(1, increment)); + Assert.Empty(result.Arguments); } [ConditionalFact] diff --git a/test/EFCore.Tests/Design/MethodCallCodeFragmentTest.cs b/test/EFCore.Tests/Design/MethodCallCodeFragmentTest.cs index 4546ab9af8a..27c041e9e47 100644 --- a/test/EFCore.Tests/Design/MethodCallCodeFragmentTest.cs +++ b/test/EFCore.Tests/Design/MethodCallCodeFragmentTest.cs @@ -20,14 +20,17 @@ public virtual void Ctor_throw_when_too_many_parameters_instance() } private static readonly MethodInfo _extensionFuncMethodInfo - = typeof(MethodCallCodeFragmentTest).GetRuntimeMethod(nameof(ExtensionFunc), new[] { typeof(object), typeof(int) })!; + = typeof(MethodCallCodeFragmentTestExtensions).GetRuntimeMethod(nameof(MethodCallCodeFragmentTestExtensions.ExtensionFunc), new[] { typeof(MethodCallCodeFragmentTest), typeof(int) })!; private static readonly MethodInfo _instanceFuncMethodInfo = typeof(MethodCallCodeFragmentTest).GetRuntimeMethod(nameof(InstanceFunc), new[] { typeof(int) })!; - public static void ExtensionFunc(object thisParameter, int p) + public void InstanceFunc(int p) => throw new NotSupportedException(); +} - public void InstanceFunc(int p) +public static class MethodCallCodeFragmentTestExtensions +{ + public static void ExtensionFunc(this MethodCallCodeFragmentTest thisParameter, int p) => throw new NotSupportedException(); }