diff --git a/src/EFCore.Design/Design/DbContextActivator.cs b/src/EFCore.Design/Design/DbContextActivator.cs index f676ad9e211..1ff0ba2f0c3 100644 --- a/src/EFCore.Design/Design/DbContextActivator.cs +++ b/src/EFCore.Design/Design/DbContextActivator.cs @@ -50,6 +50,10 @@ public static DbContext CreateInstance( new OperationReporter(reportHandler), contextType.Assembly, startupAssembly ?? contextType.Assembly, + projectDir: "", + rootNamespace: null, + language: "C#", + nullable: false, args: args ?? Array.Empty()) .CreateContext(contextType.FullName!); } diff --git a/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs b/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs index 940135a604c..f8831ffd121 100644 --- a/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs +++ b/src/EFCore.Design/Design/DesignTimeServiceCollectionExtensions.cs @@ -60,6 +60,7 @@ public static IServiceCollection AddEntityFrameworkDesignTimeServices( .TryAddSingleton() .TryAddSingleton() .TryAddSingleton() + .TryAddSingleton() .TryAddSingleton( new DesignTimeConnectionStringResolver(applicationServiceProviderAccessor)) .TryAddSingleton() diff --git a/src/EFCore.Design/Design/Internal/DbContextOperations.cs b/src/EFCore.Design/Design/Internal/DbContextOperations.cs index 002ad745a8d..86648e35e70 100644 --- a/src/EFCore.Design/Design/Internal/DbContextOperations.cs +++ b/src/EFCore.Design/Design/Internal/DbContextOperations.cs @@ -3,12 +3,15 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Scaffolding; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.DependencyInjection; @@ -27,8 +30,13 @@ public class DbContextOperations private readonly IOperationReporter _reporter; private readonly Assembly _assembly; private readonly Assembly _startupAssembly; + private readonly string _projectDir; + private readonly string? _rootNamespace; + private readonly string? _language; + private readonly bool _nullable; private readonly string[] _args; private readonly AppServiceProviderFactory _appServicesFactory; + private readonly DesignTimeServicesBuilder _servicesBuilder; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -40,8 +48,20 @@ public DbContextOperations( IOperationReporter reporter, Assembly assembly, Assembly startupAssembly, + string projectDir, + string? rootNamespace, + string? language, + bool nullable, string[]? args) - : this(reporter, assembly, startupAssembly, args, new AppServiceProviderFactory(startupAssembly, reporter)) + : this(reporter, + assembly, + startupAssembly, + projectDir, + rootNamespace, + language, + nullable, + args, + new AppServiceProviderFactory(startupAssembly, reporter)) { } @@ -55,18 +75,28 @@ protected DbContextOperations( IOperationReporter reporter, Assembly assembly, Assembly startupAssembly, + string projectDir, + string? rootNamespace, + string? language, + bool nullable, string[]? args, AppServiceProviderFactory appServicesFactory) { Check.NotNull(reporter, nameof(reporter)); Check.NotNull(assembly, nameof(assembly)); Check.NotNull(startupAssembly, nameof(startupAssembly)); + Check.NotNull(projectDir, nameof(projectDir)); _reporter = reporter; _assembly = assembly; _startupAssembly = startupAssembly; + _projectDir = projectDir; + _rootNamespace = rootNamespace; + _language = language; + _nullable = nullable; _args = args ?? Array.Empty(); _appServicesFactory = appServicesFactory; + _servicesBuilder = new DesignTimeServicesBuilder(assembly, startupAssembly, reporter, _args); } /// @@ -102,6 +132,83 @@ public virtual string ScriptDbContext(string? contextType) return context.Database.GenerateCreateScript(); } + /// + /// 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 IReadOnlyList Optimize(string? outputDir, string? modelNamespace, string? contextType) + { + using var context = CreateContext(contextType); + + var services = _servicesBuilder.Build(context); + var scaffolder = services.GetRequiredService(); + + outputDir = outputDir != null + ? Path.GetFullPath(Path.Combine(_projectDir, outputDir)) + : _projectDir; + + var finalModelNamespace = modelNamespace ?? GetNamespaceFromOutputPath(outputDir) ?? ""; + + var scaffoldedModel = scaffolder.ScaffoldModel( + context.GetService().Model, + outputDir, + new CompiledModelCodeGenerationOptions + { + ContextType = context.GetType(), + ModelNamespace = finalModelNamespace, + Language = _language, + UseNullableReferenceTypes = _nullable + }); + + var fullName = context.GetType().ShortDisplayName() + "Model"; + if (!string.IsNullOrEmpty(modelNamespace)) + { + fullName = modelNamespace + "." + fullName; + } + + _reporter.WriteInformation(DesignStrings.CompiledModelGenerated($"options.UseModel({fullName}.Instance)")); + + var cacheKeyFactory = context.GetService(); + if (!(cacheKeyFactory is ModelCacheKeyFactory)) + { + _reporter.WriteWarning(DesignStrings.CompiledModelCustomCacheKeyFactory(cacheKeyFactory.GetType().ShortDisplayName())); + } + + return scaffoldedModel; + } + + private string? GetNamespaceFromOutputPath(string directoryPath) + { + var subNamespace = SubnamespaceFromOutputPath(_projectDir, directoryPath); + return string.IsNullOrEmpty(subNamespace) + ? _rootNamespace + : string.IsNullOrEmpty(_rootNamespace) + ? subNamespace + : _rootNamespace + "." + subNamespace; + } + + // if outputDir is a subfolder of projectDir, then use each subfolder as a subnamespace + // --output-dir $(projectFolder)/A/B/C + // => "namespace $(rootnamespace).A.B.C" + private static string? SubnamespaceFromOutputPath(string projectDir, string outputDir) + { + if (!outputDir.StartsWith(projectDir, StringComparison.Ordinal)) + { + return null; + } + + var subPath = outputDir.Substring(projectDir.Length); + + return !string.IsNullOrWhiteSpace(subPath) + ? string.Join( + ".", + subPath.Split( + new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries)) + : 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 diff --git a/src/EFCore.Design/Design/Internal/MigrationsOperations.cs b/src/EFCore.Design/Design/Internal/MigrationsOperations.cs index 9479c00a1cb..9438b807a4f 100644 --- a/src/EFCore.Design/Design/Internal/MigrationsOperations.cs +++ b/src/EFCore.Design/Design/Internal/MigrationsOperations.cs @@ -66,6 +66,10 @@ public MigrationsOperations( reporter, assembly, startupAssembly, + projectDir, + rootNamespace, + language, + nullable, _args); _servicesBuilder = new DesignTimeServicesBuilder(assembly, startupAssembly, reporter, _args); diff --git a/src/EFCore.Design/Design/OperationExecutor.cs b/src/EFCore.Design/Design/OperationExecutor.cs index bca37aa2be7..54164859dae 100644 --- a/src/EFCore.Design/Design/OperationExecutor.cs +++ b/src/EFCore.Design/Design/OperationExecutor.cs @@ -120,6 +120,10 @@ private DbContextOperations ContextOperations _reporter, Assembly, StartupAssembly, + _projectDir, + _rootNamespace, + _language, + _nullable, _designArgs); private DatabaseOperations DatabaseOperations @@ -480,6 +484,41 @@ private IEnumerable GetMigrationsImpl( }); } + /// + /// Represents an operation to generate a compiled model from the DbContext. + /// + public class Optimize : OperationBase + { + /// + /// Initializes a new instance of the class. + /// The arguments supported by are: + /// outputDir--The directory to put files in. Paths are relative to the project directory. + /// modelNamespace--Specify to override the namespace of the generated model. + /// contextType--The to use. + /// + /// The operation executor. + /// The . + /// The operation arguments. + public Optimize( + OperationExecutor executor, + IOperationResultHandler resultHandler, + IDictionary args) + : base(resultHandler) + { + Check.NotNull(executor, nameof(executor)); + Check.NotNull(args, nameof(args)); + + var outputDir = (string?)args["outputDir"]; + var modelNamespace = (string?)args["modelNamespace"]; + var contextType = (string?)args["contextType"]; + + Execute(() => executor.OptimizeImpl(outputDir, modelNamespace, contextType)); + } + } + + private IReadOnlyList OptimizeImpl(string? outputDir, string? modelNamespace, string? contextType) + => ContextOperations.Optimize(outputDir, modelNamespace, contextType); + /// /// Represents an operation to scaffold a and entity types for a database. /// diff --git a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs index d139f1e464f..40d7e162e90 100644 --- a/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs +++ b/src/EFCore.Design/Migrations/Internal/SnapshotModelProcessor.cs @@ -79,11 +79,6 @@ public SnapshotModelProcessor( } } - if (model is IMutableModel mutableModel) - { - model = mutableModel.FinalizeModel(); - } - return _modelRuntimeInitializer.Initialize((IModel)model, designTime: true, validationLogger: null); } diff --git a/src/EFCore.Design/Properties/DesignStrings.Designer.cs b/src/EFCore.Design/Properties/DesignStrings.Designer.cs index b7258aa1b36..5f46167b672 100644 --- a/src/EFCore.Design/Properties/DesignStrings.Designer.cs +++ b/src/EFCore.Design/Properties/DesignStrings.Designer.cs @@ -67,6 +67,14 @@ public static string CompiledModelConstructorBinding(object? entityType, object? GetString("CompiledModelConstructorBinding", nameof(entityType), nameof(customize), nameof(className)), entityType, customize, className); + /// + /// The context is configured to use a custom model cache key factory '{factoryType}', this usually indicates that the produced model can change between context instances. To preserve this behavior manually modify the generated compiled model source code. + /// + public static string CompiledModelCustomCacheKeyFactory(object? factoryType) + => string.Format( + GetString("CompiledModelCustomCacheKeyFactory", nameof(factoryType)), + factoryType); + /// /// The entity type '{entityType}' has a defining query configured. Compiled model can't be generated, because defining queries are not supported. /// @@ -75,6 +83,14 @@ public static string CompiledModelDefiningQuery(object? entityType) GetString("CompiledModelDefiningQuery", nameof(entityType)), entityType); + /// + /// Successfully generated a compiled model, to use it call '{optionsCall}'. Run this command again when the model is modified. + /// + public static string CompiledModelGenerated(object? optionsCall) + => string.Format( + GetString("CompiledModelGenerated", nameof(optionsCall)), + optionsCall); + /// /// The entity type '{entityType}' has a query filter configured. Compiled model can't be generated, because query filters are not supported. /// diff --git a/src/EFCore.Design/Properties/DesignStrings.resx b/src/EFCore.Design/Properties/DesignStrings.resx index 962d0df69ed..13ee5457b01 100644 --- a/src/EFCore.Design/Properties/DesignStrings.resx +++ b/src/EFCore.Design/Properties/DesignStrings.resx @@ -135,9 +135,15 @@ The entity type '{entityType}' has a custom constructor binding. This is usually caused by using proxies. Compiled model can't be generated, because dynamic proxy types are not supported. If you are not using proxies configure the custom constructor binding in '{customize}' in a partial '{className}' class instead. + + The context is configured to use a custom model cache key factory '{factoryType}', this usually indicates that the produced model can change between context instances. To preserve this behavior manually modify the generated compiled model source code. + The entity type '{entityType}' has a defining query configured. Compiled model can't be generated, because defining queries are not supported. + + Successfully generated a compiled model, to use it call '{optionsCall}'. Run this command again when the model is modified. + The entity type '{entityType}' has a query filter configured. Compiled model can't be generated, because query filters are not supported. diff --git a/src/EFCore.Design/Scaffolding/CompiledModelCodeGenerationOptions.cs b/src/EFCore.Design/Scaffolding/CompiledModelCodeGenerationOptions.cs index a32bd930318..3a2f7478d90 100644 --- a/src/EFCore.Design/Scaffolding/CompiledModelCodeGenerationOptions.cs +++ b/src/EFCore.Design/Scaffolding/CompiledModelCodeGenerationOptions.cs @@ -27,5 +27,11 @@ public class CompiledModelCodeGenerationOptions /// /// The programming language to scaffold for. public virtual string? Language { get; set; } + + /// + /// Gets or sets a value indicating whether nullable reference types are enabled. + /// + /// A value indicating whether nullable reference types are enabled. + public virtual bool UseNullableReferenceTypes { get; set; } } } diff --git a/src/EFCore.Design/Scaffolding/ICompiledModelScaffolder.cs b/src/EFCore.Design/Scaffolding/ICompiledModelScaffolder.cs new file mode 100644 index 00000000000..c9545a8cc4a --- /dev/null +++ b/src/EFCore.Design/Scaffolding/ICompiledModelScaffolder.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Microsoft.EntityFrameworkCore.Scaffolding +{ + /// + /// Used to scaffold a compiled model from a model. + /// + public interface ICompiledModelScaffolder + { + /// + /// Scaffolds a compiled model from a model and saves it to disk. + /// + /// The model. + /// The output directory. + /// The options to use when generating code for the model. + /// The scaffolded model files. + IReadOnlyList ScaffoldModel( + IModel model, + string outputDir, + CompiledModelCodeGenerationOptions options); + } +} diff --git a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs index 91bb762b992..1f1dde68946 100644 --- a/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs +++ b/src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs @@ -13,7 +13,6 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Builders; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal @@ -31,6 +30,7 @@ public class CSharpRuntimeModelCodeGenerator : ICompiledModelCodeGenerator private const string FileExtension = ".cs"; private const string ModelSuffix = "Model"; + private const string ModelBuilderSuffix = "ModelBuilder"; private const string EntityTypeSuffix = "EntityType"; /// @@ -72,25 +72,41 @@ public virtual IReadOnlyCollection GenerateModel( Check.NotNull(model, nameof(model)); Check.NotNull(options, nameof(options)); - var resultingFiles = new List(); - var entityTypeIds = new Dictionary(); - var modelCode = CreateModel(model, options.ModelNamespace, options.ContextType, entityTypeIds); + var scaffoldedFiles = new List(); + var modelCode = CreateModel(model, options.ModelNamespace, options.ContextType, options.UseNullableReferenceTypes); var modelFileName = options.ContextType.ShortDisplayName() + ModelSuffix + FileExtension; - resultingFiles.Add(new ScaffoldedFile { Path = modelFileName, Code = modelCode }); + scaffoldedFiles.Add(new ScaffoldedFile { Path = modelFileName, Code = modelCode }); + + var entityTypeIds = new Dictionary(); + var modelBuilderCode = CreateModelBuilder(model, options.ModelNamespace, options.ContextType, entityTypeIds, options.UseNullableReferenceTypes); + var modelBuilderFileName = options.ContextType.ShortDisplayName() + ModelBuilderSuffix + FileExtension; + scaffoldedFiles.Add(new ScaffoldedFile { Path = modelBuilderFileName, Code = modelBuilderCode }); foreach (var (entityType, namePair) in entityTypeIds) { - var generatedCode = GenerateEntityType(entityType, options.ModelNamespace, namePair.Class); + var generatedCode = GenerateEntityType(entityType, options.ModelNamespace, namePair.Class, options.UseNullableReferenceTypes); var entityTypeFileName = namePair.Class + FileExtension; - resultingFiles.Add(new ScaffoldedFile { Path = entityTypeFileName, Code = generatedCode }); + scaffoldedFiles.Add(new ScaffoldedFile { Path = entityTypeFileName, Code = generatedCode }); } - return resultingFiles; + return scaffoldedFiles; } - private static string GenerateHeader(IEnumerable namespaces) + private static string GenerateHeader(SortedSet namespaces, string currentNamespace, bool nullable) { + for (var i = 0; i < currentNamespace.Length; i++) + { + if (currentNamespace[i] != '.') + { + continue; + } + + namespaces.Remove(currentNamespace[..i]); + } + + namespaces.Remove(currentNamespace); + var builder = new StringBuilder(); builder.AppendLine("// "); foreach (var @namespace in namespaces) @@ -101,9 +117,18 @@ private static string GenerateHeader(IEnumerable namespaces) .AppendLine(";"); } - builder.AppendLine(); - builder.AppendLine("#pragma warning disable 219, 612, 618"); - builder.AppendLine("#nullable disable"); + builder.AppendLine() + .AppendLine("#pragma warning disable 219, 612, 618"); + + if (nullable) + { + builder.AppendLine("#nullable enable"); + } + else + { + builder.AppendLine("#nullable disable"); + } + builder.AppendLine(); return builder.ToString(); @@ -113,10 +138,9 @@ private string CreateModel( IModel model, string @namespace, Type contextType, - Dictionary entityTypeIds) + bool nullable) { var mainBuilder = new IndentedStringBuilder(); - var methodBuilder = new IndentedStringBuilder(); var namespaces = new SortedSet(new NamespaceComparer()) { contextType.Namespace!, typeof(RuntimeModel).Namespace!, @@ -140,7 +164,7 @@ private string CreateModel( using (mainBuilder.Indent()) { mainBuilder - .Append("private static ").Append(className).AppendLine(" _instance;") + .Append("private static ").Append(className).AppendLine(nullable ? "? _instance;" : " _instance;") .Append("public static IModel Instance") .AppendLines(@" { @@ -150,6 +174,7 @@ private string CreateModel( { _instance = new " + className + @"(); _instance.Initialize(); + _instance.Customize(); } return _instance; @@ -158,7 +183,53 @@ private string CreateModel( mainBuilder .AppendLine() - .AppendLine("protected override void Initialize()") + .AppendLine("partial void Initialize();") + .AppendLine() + .AppendLine("partial void Customize();"); + } + + mainBuilder.AppendLine("}"); + + if (!string.IsNullOrEmpty(@namespace)) + { + mainBuilder.DecrementIndent(); + mainBuilder.AppendLine("}"); + } + + return GenerateHeader(namespaces, @namespace, nullable) + mainBuilder; + } + + private string CreateModelBuilder( + IModel model, + string @namespace, + Type contextType, + Dictionary entityTypeIds, + bool nullable) + { + var mainBuilder = new IndentedStringBuilder(); + var methodBuilder = new IndentedStringBuilder(); + var namespaces = new SortedSet(new NamespaceComparer()) { + typeof(RuntimeModel).Namespace!, + typeof(DbContextAttribute).Namespace! + }; + + if (!string.IsNullOrEmpty(@namespace)) + { + mainBuilder + .Append("namespace ").AppendLine(_code.Namespace(@namespace)) + .AppendLine("{"); + mainBuilder.Indent(); + } + + var className = _code.Identifier(contextType.ShortDisplayName()) + ModelSuffix; + mainBuilder + .Append("partial class ").AppendLine(className) + .AppendLine("{"); + + using (mainBuilder.Indent()) + { + mainBuilder + .AppendLine("partial void Initialize()") .AppendLine("{"); using (mainBuilder.Indent()) { @@ -176,17 +247,21 @@ private string CreateModel( entityTypeIds[entityType] = (variableName, entityClassName); - var baseTypeVariable = entityType.BaseType == null - ? _code.UnknownLiteral(null) - : entityTypeIds[entityType.BaseType].Variable; - mainBuilder .Append("var ") .Append(variableName) .Append(" = ") .Append(entityClassName) - .Append(".Create(this, ") - .Append(baseTypeVariable) + .Append(".Create(this"); + + if (entityType.BaseType != null) + { + mainBuilder + .Append(", ") + .Append(entityTypeIds[entityType.BaseType].Variable); + } + + mainBuilder .AppendLine(");"); } @@ -279,16 +354,10 @@ private string CreateModel( methodBuilder, namespaces, variables)); - - mainBuilder - .AppendLine() - .AppendLine("Customize();"); } mainBuilder - .AppendLine("}") - .AppendLine() - .AppendLine("partial void Customize();"); + .AppendLine("}"); var methods = methodBuilder.ToString(); if (!string.IsNullOrEmpty(methods)) @@ -306,19 +375,17 @@ private string CreateModel( mainBuilder.AppendLine("}"); } - namespaces.Remove(@namespace); - return GenerateHeader(namespaces) + mainBuilder; + return GenerateHeader(namespaces, @namespace, nullable) + mainBuilder; } - private string GenerateEntityType(IEntityType entityType, string @namespace, string className) + private string GenerateEntityType(IEntityType entityType, string @namespace, string className, bool nullable) { var mainBuilder = new IndentedStringBuilder(); var methodBuilder = new IndentedStringBuilder(); var namespaces = new SortedSet(new NamespaceComparer()) { - typeof(RuntimeEntityType).Namespace!, - typeof(PropertyAccessMode).Namespace!, - typeof(BindingFlags).Namespace! + typeof(BindingFlags).Namespace!, + typeof(RuntimeEntityType).Namespace! }; if (!string.IsNullOrEmpty(@namespace)) @@ -334,18 +401,18 @@ private string GenerateEntityType(IEntityType entityType, string @namespace, str .AppendLine("{"); using (mainBuilder.Indent()) { - CreateEntityType(entityType, mainBuilder, methodBuilder, namespaces, className); + CreateEntityType(entityType, mainBuilder, methodBuilder, namespaces, className, nullable); var foreignKeyNumber = 1; foreach (var foreignKey in entityType.GetDeclaredForeignKeys()) { - CreateForeignKey(foreignKey, foreignKeyNumber++, mainBuilder, methodBuilder, namespaces, className); + CreateForeignKey(foreignKey, foreignKeyNumber++, mainBuilder, methodBuilder, namespaces, className, nullable); } var navigationNumber = 1; foreach (var navigation in entityType.GetDeclaredSkipNavigations()) { - CreateSkipNavigation(navigation, navigationNumber++, mainBuilder, methodBuilder, namespaces, className); + CreateSkipNavigation(navigation, navigationNumber++, mainBuilder, methodBuilder, namespaces, className, nullable); } CreateAnnotations(entityType, mainBuilder, methodBuilder, namespaces, className); @@ -359,8 +426,7 @@ private string GenerateEntityType(IEntityType entityType, string @namespace, str mainBuilder.AppendLine("}"); } - namespaces.Remove(@namespace); - return GenerateHeader(namespaces) + mainBuilder + methodBuilder; + return GenerateHeader(namespaces, @namespace, nullable) + mainBuilder + methodBuilder; } private void CreateEntityType( @@ -368,11 +434,20 @@ private void CreateEntityType( IndentedStringBuilder mainBuilder, IndentedStringBuilder methodBuilder, SortedSet namespaces, - string className) + string className, + bool nullable) { mainBuilder .Append("public static RuntimeEntityType Create") - .AppendLine("(RuntimeModel model, RuntimeEntityType baseEntityType)") + .Append("(RuntimeModel model, RuntimeEntityType"); + + if (nullable) + { + mainBuilder + .Append("?"); + } + + mainBuilder.AppendLine(" baseEntityType = null)") .AppendLine("{"); using (mainBuilder.Indent()) @@ -408,12 +483,12 @@ private void CreateEntityType( foreach (var key in entityType.GetDeclaredKeys()) { - Create(key, propertyVariables, parameters); + Create(key, propertyVariables, parameters, nullable); } foreach (var index in entityType.GetDeclaredIndexes()) { - Create(index, propertyVariables, parameters); + Create(index, propertyVariables, parameters, nullable); } mainBuilder @@ -483,6 +558,8 @@ private void Create(IEntityType entityType, CSharpRuntimeAnnotationCodeGenerator var changeTrackingStrategy = entityType.GetChangeTrackingStrategy(); if (changeTrackingStrategy != ChangeTrackingStrategy.Snapshot) { + parameters.Namespaces.Add(typeof(ChangeTrackingStrategy).Namespace!); + mainBuilder.AppendLine(",") .Append("changeTrackingStrategy: ") .Append(_code.Literal(changeTrackingStrategy)); @@ -760,6 +837,8 @@ private void PropertyBaseParameters( var propertyAccessMode = property.GetPropertyAccessMode(); if (propertyAccessMode != Model.DefaultPropertyAccessMode) { + parameters.Namespaces.Add(typeof(PropertyAccessMode).Namespace!); + mainBuilder.AppendLine(",") .Append("propertyAccessMode: ") .Append(_code.Literal(propertyAccessMode)); @@ -770,6 +849,7 @@ private void FindProperties( string entityTypeVariable, IEnumerable properties, IndentedStringBuilder mainBuilder, + bool nullable, Dictionary? propertyVariables = null) { mainBuilder.Append("new[] { "); @@ -797,6 +877,12 @@ private void FindProperties( .Append(".FindProperty(") .Append(_code.Literal(property.Name)) .Append(")"); + + if (nullable) + { + mainBuilder + .Append("!"); + } } } @@ -832,7 +918,8 @@ private void Create( private void Create( IKey key, Dictionary propertyVariables, - CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + CSharpRuntimeAnnotationCodeGeneratorParameters parameters, + bool nullable) { var variableName = _code.Identifier("key", parameters.ScopeVariables); @@ -840,7 +927,7 @@ private void Create( mainBuilder .Append("var ").Append(variableName).Append(" = ").Append(parameters.TargetName).AppendLine(".AddKey(") .IncrementIndent(); - FindProperties(parameters.TargetName, key.Properties, mainBuilder, propertyVariables); + FindProperties(parameters.TargetName, key.Properties, mainBuilder, nullable, propertyVariables); mainBuilder .AppendLine(");") .DecrementIndent(); @@ -865,7 +952,8 @@ private void Create( private void Create( IIndex index, Dictionary propertyVariables, - CSharpRuntimeAnnotationCodeGeneratorParameters parameters) + CSharpRuntimeAnnotationCodeGeneratorParameters parameters, + bool nullable) { var variableName = _code.Identifier(index.Name ?? "index", parameters.ScopeVariables, capitalize: false); @@ -874,7 +962,7 @@ private void Create( .Append("var ").Append(variableName).Append(" = ").Append(parameters.TargetName).AppendLine(".AddIndex(") .IncrementIndent(); - FindProperties(parameters.TargetName, index.Properties, mainBuilder, propertyVariables); + FindProperties(parameters.TargetName, index.Properties, mainBuilder, nullable, propertyVariables); if (index.Name != null) { @@ -908,7 +996,8 @@ private void CreateForeignKey( IndentedStringBuilder mainBuilder, IndentedStringBuilder methodBuilder, SortedSet namespaces, - string className) + string className, + bool nullable) { var declaringEntityType = "declaringEntityType"; var principalEntityType = "principalEntityType"; @@ -931,16 +1020,23 @@ private void CreateForeignKey( mainBuilder .Append("var ").Append(foreignKeyVariable).Append(" = ") .Append(declaringEntityType).Append(".AddForeignKey(").IncrementIndent(); - FindProperties(declaringEntityType, foreignKey.Properties, mainBuilder); + FindProperties(declaringEntityType, foreignKey.Properties, mainBuilder, nullable); mainBuilder.AppendLine(",") .Append(principalEntityType).Append(".FindKey("); - FindProperties(principalEntityType, foreignKey.PrincipalKey.Properties, mainBuilder); - mainBuilder.AppendLine("),") + FindProperties(principalEntityType, foreignKey.PrincipalKey.Properties, mainBuilder, nullable); + mainBuilder.Append(")"); + if (nullable) + { + mainBuilder.Append("!"); + } + mainBuilder.AppendLine(",") .Append(principalEntityType); if (foreignKey.DeleteBehavior != ForeignKey.DefaultDeleteBehavior) { + namespaces.Add(typeof(DeleteBehavior).Namespace!); + mainBuilder.AppendLine(",") .Append("deleteBehavior: ") .Append(_code.Literal(foreignKey.DeleteBehavior)); @@ -1053,7 +1149,8 @@ private void CreateSkipNavigation( IndentedStringBuilder mainBuilder, IndentedStringBuilder methodBuilder, SortedSet namespaces, - string className) + string className, + bool nullable) { var declaringEntityType = "declaringEntityType"; var targetEntityType = "targetEntityType"; @@ -1093,12 +1190,22 @@ private void CreateSkipNavigation( .Append(joinEntityType).AppendLine(".FindForeignKey("); using (mainBuilder.Indent()) { - FindProperties(joinEntityType, navigation.ForeignKey.Properties, mainBuilder); + FindProperties(joinEntityType, navigation.ForeignKey.Properties, mainBuilder, nullable); mainBuilder.AppendLine(",") .Append(declaringEntityType).Append(".FindKey("); - FindProperties(declaringEntityType, navigation.ForeignKey.PrincipalKey.Properties, mainBuilder); - mainBuilder.AppendLine("),") + FindProperties(declaringEntityType, navigation.ForeignKey.PrincipalKey.Properties, mainBuilder, nullable); + mainBuilder.Append(")"); + if (nullable) + { + mainBuilder.Append("!"); + } + + mainBuilder.AppendLine(",") .Append(declaringEntityType).Append(")"); + if (nullable) + { + mainBuilder.Append("!"); + } } mainBuilder.AppendLine(",") diff --git a/src/EFCore.Design/Scaffolding/Internal/CompiledModelScaffolder.cs b/src/EFCore.Design/Scaffolding/Internal/CompiledModelScaffolder.cs new file mode 100644 index 00000000000..73257590dba --- /dev/null +++ b/src/EFCore.Design/Scaffolding/Internal/CompiledModelScaffolder.cs @@ -0,0 +1,118 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.EntityFrameworkCore.Design; +using Microsoft.EntityFrameworkCore.Design.Internal; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Utilities; + +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 class CompiledModelScaffolder : ICompiledModelScaffolder + { + private readonly IOperationReporter _reporter; + + /// + /// 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 CompiledModelScaffolder( + ICompiledModelCodeGeneratorSelector modelCodeGeneratorSelector, + IOperationReporter reporter) + { + Check.NotNull(modelCodeGeneratorSelector, nameof(modelCodeGeneratorSelector)); + Check.NotNull(reporter, nameof(reporter)); + + ModelCodeGeneratorSelector = modelCodeGeneratorSelector; + _reporter = reporter; + } + + /// + /// 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. + /// + private ICompiledModelCodeGeneratorSelector ModelCodeGeneratorSelector { 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 IReadOnlyList ScaffoldModel( + IModel model, + string outputDir, + CompiledModelCodeGenerationOptions options) + { + Check.NotNull(model, nameof(model)); + Check.NotEmpty(outputDir, nameof(outputDir)); + Check.NotNull(options, nameof(options)); + + var codeGenerator = ModelCodeGeneratorSelector.Select(options); + + var scaffoldedModel = codeGenerator.GenerateModel(model, options); + + CheckOutputFiles(scaffoldedModel, outputDir); + + Directory.CreateDirectory(outputDir); + + var savedFiles = new List(); + foreach (var file in scaffoldedModel) + { + var filePath = Path.Combine(outputDir, file.Path); + File.WriteAllText(filePath, file.Code, Encoding.UTF8); + savedFiles.Add(filePath); + } + + return savedFiles; + } + + private static void CheckOutputFiles( + IReadOnlyCollection scaffoldedModel, + string outputDir) + { + var paths = scaffoldedModel.Select(f => f.Path).ToList(); + + var existingFiles = new List(); + var readOnlyFiles = new List(); + foreach (var path in paths) + { + var fullPath = Path.Combine(outputDir, path); + + if (File.Exists(fullPath)) + { + existingFiles.Add(path!); + + if (File.GetAttributes(fullPath).HasFlag(FileAttributes.ReadOnly)) + { + readOnlyFiles.Add(path!); + } + } + } + + if (readOnlyFiles.Count != 0) + { + throw new OperationException( + DesignStrings.ReadOnlyFiles( + outputDir, + string.Join(CultureInfo.CurrentCulture.TextInfo.ListSeparator, readOnlyFiles))); + } + } + } +} diff --git a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs index 84134ac622e..1f1bedc7185 100644 --- a/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs +++ b/src/EFCore.Design/Scaffolding/Internal/RelationalScaffoldingModelFactory.cs @@ -93,26 +93,26 @@ public virtual IModel Create(DatabaseModel databaseModel, ModelReverseEngineerOp _tableNamer = new CSharpUniqueNamer( options.UseDatabaseNames - ? (Func)(t => t.Name) + ? (t => t.Name) : t => _candidateNamingService.GenerateCandidateIdentifier(t), _cSharpUtilities, options.NoPluralize - ? (Func?)null + ? null : _pluralizer.Singularize); _dbSetNamer = new CSharpUniqueNamer( options.UseDatabaseNames - ? (Func)(t => t.Name) + ? (t => t.Name) : t => _candidateNamingService.GenerateCandidateIdentifier(t), _cSharpUtilities, options.NoPluralize - ? (Func?)null + ? null : _pluralizer.Pluralize); _columnNamers = new Dictionary>(); _options = options; VisitDatabaseModel(modelBuilder, databaseModel); - return _modelRuntimeInitializer.Initialize(modelBuilder.FinalizeModel(), designTime: true, null); + return _modelRuntimeInitializer.Initialize((IModel)modelBuilder.Model, designTime: true, null); } /// diff --git a/src/EFCore.Relational/Migrations/HistoryRepository.cs b/src/EFCore.Relational/Migrations/HistoryRepository.cs index d0416ae69e1..b4be0751b22 100644 --- a/src/EFCore.Relational/Migrations/HistoryRepository.cs +++ b/src/EFCore.Relational/Migrations/HistoryRepository.cs @@ -108,7 +108,7 @@ private IModel EnsureModel() x.ToTable(TableName, TableSchema); }); - _model = Dependencies.ModelRuntimeInitializer.Initialize(modelBuilder.FinalizeModel(), designTime: true, validationLogger: null); + _model = Dependencies.ModelRuntimeInitializer.Initialize((IModel)modelBuilder.Model, designTime: true, validationLogger: null); } return _model; diff --git a/src/EFCore.Relational/Migrations/Internal/Migrator.cs b/src/EFCore.Relational/Migrations/Internal/Migrator.cs index 549a7a3151d..76c7ad94320 100644 --- a/src/EFCore.Relational/Migrations/Internal/Migrator.cs +++ b/src/EFCore.Relational/Migrations/Internal/Migrator.cs @@ -496,13 +496,6 @@ protected virtual IReadOnlyList GenerateDownSql( } private IModel FinalizeModel(IModel model) - { - if (model is IMutableModel mutableModel) - { - model = mutableModel.FinalizeModel(); - } - - return _modelRuntimeInitializer.Initialize(model, designTime: true, validationLogger: null); - } + => _modelRuntimeInitializer.Initialize(model, designTime: true, validationLogger: null); } } diff --git a/src/EFCore.Tools/EFCore.Tools.csproj b/src/EFCore.Tools/EFCore.Tools.csproj index 1d552e503bd..4152c3e81b5 100644 --- a/src/EFCore.Tools/EFCore.Tools.csproj +++ b/src/EFCore.Tools/EFCore.Tools.csproj @@ -18,6 +18,7 @@ Drop-Database Get-DbContext Get-Migration Remove-Migration +Optimize-DbContext Scaffold-DbContext Script-Migration Update-Database diff --git a/src/EFCore.Tools/EntityFrameworkCore.psd1.in b/src/EFCore.Tools/EntityFrameworkCore.psd1.in index d33c9b9c35f..202fbef5980 100644 --- a/src/EFCore.Tools/EntityFrameworkCore.psd1.in +++ b/src/EFCore.Tools/EntityFrameworkCore.psd1.in @@ -72,6 +72,7 @@ FunctionsToExport = ( 'Get-DbContext', 'Get-Migration', 'Remove-Migration', + 'Optimize-DbContext', 'Scaffold-DbContext', 'Script-DbContext', 'Script-Migration', diff --git a/src/EFCore.Tools/tools/EntityFrameworkCore.psm1 b/src/EFCore.Tools/tools/EntityFrameworkCore.psm1 index 0645f179a5b..fc626fd445c 100644 --- a/src/EFCore.Tools/tools/EntityFrameworkCore.psm1 +++ b/src/EFCore.Tools/tools/EntityFrameworkCore.psm1 @@ -365,6 +365,80 @@ function Remove-Migration } } +# +# Optimize-DbContext +# + +Register-TabExpansion Optimize-DbContext @{ + Provider = { param($x) GetProviders $x.Project } + Project = { GetProjects } + StartupProject = { GetProjects } + OutputDir = { <# Disabled. Otherwise, paths would be relative to the solution directory. #> } + ContextDir = { <# Disabled. Otherwise, paths would be relative to the solution directory. #> } +} + +<# +.SYNOPSIS + Generates a compiled version of the model used by the DbContext. + +.DESCRIPTION + Generates a compiled version of the model used by the DbContext. + +.PARAMETER OutputDir + The directory to put files in. Paths are relative to the project directory. + +.PARAMETER Namespace + The namespace to use. Matches the directory by default. + +.PARAMETER Context + The DbContext to use. + +.PARAMETER Project + The project to use. + +.PARAMETER StartupProject + The startup project to use. Defaults to the solution's startup project. + +.PARAMETER Args + Arguments passed to the application. + +.LINK + about_EntityFrameworkCore +#> +function Optimize-DbContext +{ + [CmdletBinding(PositionalBinding = $false)] + param( + [string] $OutputDir, + [string] $Namespace, + [string] $Context, + [string] $Project, + [string] $StartupProject, + [string] $Args) + + $dteProject = GetProject $Project + $dteStartupProject = GetStartupProject $StartupProject $dteProject + + $params = 'dbcontext', 'optimize' + + if ($OutputDir) + { + $params += '--output-dir', $OutputDir + } + + if ($Namespace) + { + $params += '--namespace', $Namespace + } + + if ($Context) + { + $params += '--context', $Context + } + + EF $dteProject $dteStartupProject $params $Args +} + # # Scaffold-DbContext # diff --git a/src/EFCore.Tools/tools/about_EntityFrameworkCore.help.txt b/src/EFCore.Tools/tools/about_EntityFrameworkCore.help.txt index faf0503e3ea..3c077436bc6 100644 --- a/src/EFCore.Tools/tools/about_EntityFrameworkCore.help.txt +++ b/src/EFCore.Tools/tools/about_EntityFrameworkCore.help.txt @@ -30,6 +30,8 @@ LONG DESCRIPTION Remove-Migration Removes the last migration. + Optimize-DbContext Generates a compiled version of the model used by the DbContext. + Scaffold-DbContext Scaffolds a DbContext and entity types for a database. Script-DbContext Generates a SQL script from the DbContext. Bypasses any migrations. @@ -44,6 +46,7 @@ SEE ALSO Get-DbContext Get-Migration Remove-Migration + Optimize-DbContext Scaffold-DbContext Script-DbContext Script-Migration diff --git a/src/EFCore/Diagnostics/CoreEventId.cs b/src/EFCore/Diagnostics/CoreEventId.cs index 488ef8f96d3..845344c7760 100644 --- a/src/EFCore/Diagnostics/CoreEventId.cs +++ b/src/EFCore/Diagnostics/CoreEventId.cs @@ -85,6 +85,7 @@ private enum Id DetachedLazyLoadingWarning, ServiceProviderDebugInfo, RedundantAddServicesCallWarning, + OldModelVersionWarning, // Model and ModelValidation events ShadowPropertyCreated = CoreBaseId + 600, @@ -435,6 +436,19 @@ private static EventId MakeInfraId(Id id) /// public static readonly EventId RedundantAddServicesCallWarning = MakeInfraId(Id.RedundantAddServicesCallWarning); + /// + /// + /// The model supplied in the context options was created with an older EF Core version. + /// + /// + /// This event is in the category. + /// + /// + /// This event uses the payload when used with a . + /// + /// + public static readonly EventId OldModelVersionWarning = MakeInfraId(Id.OldModelVersionWarning); + private static readonly string _modelPrefix = DbLoggerCategory.Model.Name + "."; private static EventId MakeModelId(Id id) diff --git a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs index babbdfa8d70..cd751fbafd0 100644 --- a/src/EFCore/Diagnostics/CoreLoggerExtensions.cs +++ b/src/EFCore/Diagnostics/CoreLoggerExtensions.cs @@ -153,6 +153,51 @@ public static void OptimisticConcurrencyException( } } + /// + /// Logs for the event. + /// + /// The diagnostics logger to use. + /// The context being used. + /// The context options being used. + public static void OldModelVersionWarning( + this IDiagnosticsLogger diagnostics, + DbContext context, + DbContextOptions contextOptions) + { + var definition = CoreResources.LogOldModelVersion(diagnostics); + + if (diagnostics.ShouldLog(definition)) + { + var modelVersion = contextOptions.FindExtension()?.Model?.GetProductVersion() ?? ""; + + definition.Log( + diagnostics, + modelVersion, + ProductInfo.GetVersion()); + } + + if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled)) + { + var eventData = new ContextInitializedEventData( + definition, + OldModelVersion, + context, + contextOptions); + + diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled); + } + } + + private static string OldModelVersion(EventDefinitionBase definition, EventData payload) + { + var d = (EventDefinition)definition; + var p = (ContextInitializedEventData)payload; + var modelVersion = p.ContextOptions.FindExtension()?.Model?.GetProductVersion() ?? ""; + return d.GenerateMessage( + modelVersion, + ProductInfo.GetVersion()); + } + /// /// Logs for the event. /// diff --git a/src/EFCore/Diagnostics/LoggingDefinitions.cs b/src/EFCore/Diagnostics/LoggingDefinitions.cs index e10b3314b07..1b73de8688b 100644 --- a/src/EFCore/Diagnostics/LoggingDefinitions.cs +++ b/src/EFCore/Diagnostics/LoggingDefinitions.cs @@ -53,6 +53,15 @@ public abstract class LoggingDefinitions [EntityFrameworkInternal] public EventDefinitionBase? LogContextInitialized; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + [EntityFrameworkInternal] + public EventDefinitionBase? LogOldModelVersion; + /// /// 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 diff --git a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs index 5b740a0459d..727afefe8f2 100644 --- a/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs +++ b/src/EFCore/Infrastructure/EntityFrameworkServicesBuilder.cs @@ -254,7 +254,7 @@ public virtual EntityFrameworkServicesBuilder TryAddCoreServices() TryAdd(p => GetContextServices(p).Model); TryAdd(p => new DesignTimeModel(GetContextServices(p))); TryAdd(p => GetContextServices(p).CurrentContext); - TryAdd(p => GetContextServices(p).ContextOptions); + TryAdd(p => GetContextServices(p).ContextOptions); TryAdd(p => p.GetRequiredService()); TryAdd(p => p.GetRequiredService()); TryAdd(); diff --git a/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs b/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs index 52a20f7153e..0fff77cecb4 100644 --- a/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs +++ b/src/EFCore/Infrastructure/ModelRuntimeInitializer.cs @@ -53,6 +53,12 @@ public virtual IModel Initialize( bool designTime = true, IDiagnosticsLogger? validationLogger = null) { + if (model is Model mutableModel + && !mutableModel.IsReadOnly) + { + model = mutableModel.FinalizeModel(); + } + if (model.ModelDependencies == null) { model = model.GetOrAddRuntimeAnnotationValue( diff --git a/src/EFCore/Infrastructure/ModelSource.cs b/src/EFCore/Infrastructure/ModelSource.cs index 7efbdcb3ab4..57a55cc6d2d 100644 --- a/src/EFCore/Infrastructure/ModelSource.cs +++ b/src/EFCore/Infrastructure/ModelSource.cs @@ -4,6 +4,7 @@ using System; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; @@ -67,6 +68,18 @@ public virtual IModel GetModel( if (!cache.TryGetValue(cacheKey, out model)) { model = CreateModel(context, conventionSetBuilder); + + if (model is Model mutableModel + && !mutableModel.IsReadOnly) + { + model = mutableModel.FinalizeModel(); + } + + model = model.GetOrAddRuntimeAnnotationValue( + CoreAnnotationNames.ReadOnlyModel, + static model => model!, + model); + model = cache.Set(cacheKey, model, new MemoryCacheEntryOptions { Size = 100, Priority = CacheItemPriority.High }); } } @@ -98,6 +111,18 @@ public virtual IModel GetModel( if (!cache.TryGetValue(cacheKey, out model)) { model = CreateModel(context, conventionSetBuilder, modelDependencies); + + if (model is Model mutableModel + && !mutableModel.IsReadOnly) + { + model = mutableModel.FinalizeModel(); + } + + model = model.GetOrAddRuntimeAnnotationValue( + CoreAnnotationNames.ReadOnlyModel, + static model => model!, + model); + model = cache.Set(cacheKey, model, new MemoryCacheEntryOptions { Size = 100, Priority = CacheItemPriority.High }); } } @@ -157,7 +182,7 @@ protected virtual IModel CreateModel( Dependencies.ModelCustomizer.Customize(modelBuilder, context); - return modelBuilder.FinalizeModel(); + return (IModel)modelBuilder.Model; } /// @@ -178,7 +203,7 @@ protected virtual IModel CreateModel( Dependencies.ModelCustomizer.Customize(modelBuilder, context); - return modelBuilder.FinalizeModel(); + return (IModel)modelBuilder.Model; } } } diff --git a/src/EFCore/Internal/DbContextServices.cs b/src/EFCore/Internal/DbContextServices.cs index b977ff7400b..c2dcb48e300 100644 --- a/src/EFCore/Internal/DbContextServices.cs +++ b/src/EFCore/Internal/DbContextServices.cs @@ -30,7 +30,7 @@ namespace Microsoft.EntityFrameworkCore.Internal public class DbContextServices : IDbContextServices { private IServiceProvider? _scopedProvider; - private IDbContextOptions? _contextOptions; + private DbContextOptions? _contextOptions; private ICurrentDbContext? _currentContext; private IModel? _model; private IModel? _designTimeModel; @@ -44,7 +44,7 @@ public class DbContextServices : IDbContextServices /// public virtual IDbContextServices Initialize( IServiceProvider scopedProvider, - IDbContextOptions contextOptions, + DbContextOptions contextOptions, DbContext context) { _scopedProvider = scopedProvider; @@ -84,6 +84,21 @@ private IModel CreateModel(bool designTime) var dependencies = _scopedProvider!.GetRequiredService(); var modelFromOptions = CoreOptions?.Model; + + var modelVersion = modelFromOptions?.GetProductVersion(); + if (modelVersion != null) + { + var modelMinorVersion = modelVersion[..modelVersion.LastIndexOf('.')]; + var productVersion = ProductInfo.GetVersion(); + var productMinorVersion = productVersion[..productVersion.LastIndexOf('.')]; + + if (modelMinorVersion != productMinorVersion) + { + var logger = _scopedProvider!.GetRequiredService>(); + logger.OldModelVersionWarning(_currentContext!.Context, _contextOptions!); + } + } + return modelFromOptions == null || (designTime && modelFromOptions is not Metadata.Internal.Model) ? dependencies.ModelSource.GetModel(_currentContext!.Context, dependencies, designTime) @@ -131,7 +146,7 @@ private CoreOptionsExtension? CoreOptions /// 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 IDbContextOptions ContextOptions + public virtual DbContextOptions ContextOptions => _contextOptions!; /// diff --git a/src/EFCore/Internal/IDbContextServices.cs b/src/EFCore/Internal/IDbContextServices.cs index 83c2289624c..e4d845a5ad5 100644 --- a/src/EFCore/Internal/IDbContextServices.cs +++ b/src/EFCore/Internal/IDbContextServices.cs @@ -32,7 +32,7 @@ public interface IDbContextServices /// IDbContextServices Initialize( IServiceProvider scopedProvider, - IDbContextOptions contextOptions, + DbContextOptions contextOptions, DbContext context); /// @@ -65,7 +65,7 @@ IDbContextServices Initialize( /// 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. /// - IDbContextOptions ContextOptions { get; } + DbContextOptions ContextOptions { get; } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index 94591ffcb61..ae6aef950af 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -15,7 +15,6 @@ using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.EntityFrameworkCore.ValueGeneration; -using Microsoft.EntityFrameworkCore.ValueGeneration.Internal; namespace Microsoft.EntityFrameworkCore.Metadata.Internal { diff --git a/src/EFCore/Metadata/RuntimeModel.cs b/src/EFCore/Metadata/RuntimeModel.cs index c3d35a6b342..b6b1c3d5916 100644 --- a/src/EFCore/Metadata/RuntimeModel.cs +++ b/src/EFCore/Metadata/RuntimeModel.cs @@ -41,13 +41,6 @@ public RuntimeModel() { } - /// - /// Initializes this model instance. - /// - protected virtual void Initialize() - { - } - /// /// Sets a value indicating whether should be called. /// diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index e56ce6d0e83..2c9c141ffaa 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -3878,6 +3878,31 @@ public static EventDefinition LogNonNullableReferenceOnDependent return (EventDefinition)definition; } + /// + /// The model supplied in the context options was created with EF Core version '{oldVersion}', but the context is from version '{newVersion}'. Update the externally built model. + /// + public static EventDefinition LogOldModelVersion(IDiagnosticsLogger logger) + { + var definition = ((LoggingDefinitions)logger.Definitions).LogOldModelVersion; + if (definition == null) + { + definition = NonCapturingLazyInitializer.EnsureInitialized( + ref ((LoggingDefinitions)logger.Definitions).LogOldModelVersion, + logger, + static logger => new EventDefinition( + logger.Options, + CoreEventId.OldModelVersionWarning, + LogLevel.Warning, + "CoreEventId.OldModelVersionWarning", + level => LoggerMessage.Define( + level, + CoreEventId.OldModelVersionWarning, + _resourceManager.GetString("LogOldModelVersion")!))); + } + + return (EventDefinition)definition; + } + /// /// {error} /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 2023e55ea9e..6a703d51ccb 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -855,6 +855,10 @@ The navigation '{targetEntityType}.{inverseNavigation}' specified in the [InverseProperty] attribute cannot be used as the inverse of '{ownedEntityType}.{navigation}' because it's not the ownership navigation '{ownershipNavigation}'. Warning CoreEventId.NonOwnershipInverseNavigationWarning string string? string string? string? + + The model supplied in the context options was created with EF Core version '{oldVersion}', but the context is from version '{newVersion}'. Update the externally built model. + Warning CoreEventId.OldModelVersionWarning string string + {error} Debug CoreEventId.OptimisticConcurrencyException Exception diff --git a/src/dotnet-ef/Properties/Resources.Designer.cs b/src/dotnet-ef/Properties/Resources.Designer.cs index 74c5612c43a..1396d84387c 100644 --- a/src/dotnet-ef/Properties/Resources.Designer.cs +++ b/src/dotnet-ef/Properties/Resources.Designer.cs @@ -3,7 +3,6 @@ using System; using System.Reflection; using System.Resources; -using JetBrains.Annotations; #nullable enable @@ -134,6 +133,12 @@ public static string DbContextInfoDescription public static string DbContextListDescription => GetString("DbContextListDescription"); + /// + /// Generates a compiled version of the model used by the DbContext. + /// + public static string DbContextOptimizeDescription + => GetString("DbContextOptimizeDescription"); + /// /// Scaffolds a DbContext and entity types for a database. /// diff --git a/src/dotnet-ef/Properties/Resources.resx b/src/dotnet-ef/Properties/Resources.resx index b8299f07e9f..981f976c06a 100644 --- a/src/dotnet-ef/Properties/Resources.resx +++ b/src/dotnet-ef/Properties/Resources.resx @@ -174,6 +174,9 @@ Lists available DbContext types. + + Generates a compiled version of the model used by the DbContext. + Scaffolds a DbContext and entity types for a database. diff --git a/src/ef/Commands/DbContextCommand.cs b/src/ef/Commands/DbContextCommand.cs index e94048bcbc9..94a92a88235 100644 --- a/src/ef/Commands/DbContextCommand.cs +++ b/src/ef/Commands/DbContextCommand.cs @@ -14,6 +14,7 @@ public override void Configure(CommandLineApplication command) command.Command("info", new DbContextInfoCommand().Configure); command.Command("list", new DbContextListCommand().Configure); + command.Command("optimize", new DbContextOptimizeCommand().Configure); command.Command("scaffold", new DbContextScaffoldCommand().Configure); command.Command("script", new DbContextScriptCommand().Configure); diff --git a/src/ef/Commands/DbContextOptimizeCommand.Configure.cs b/src/ef/Commands/DbContextOptimizeCommand.Configure.cs new file mode 100644 index 00000000000..61d9b287cd4 --- /dev/null +++ b/src/ef/Commands/DbContextOptimizeCommand.Configure.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.DotNet.Cli.CommandLine; +using Microsoft.EntityFrameworkCore.Tools.Properties; + +namespace Microsoft.EntityFrameworkCore.Tools.Commands +{ + internal partial class DbContextOptimizeCommand : ContextCommandBase + { + private CommandOption? _outputDir; + private CommandOption? _namespace; + private CommandOption? _json; + + public override void Configure(CommandLineApplication command) + { + command.Description = Resources.DbContextOptimizeDescription; + + _outputDir = command.Option("-o|--output-dir ", Resources.OutputDirDescription); + _namespace = command.Option("-n|--namespace ", Resources.NamespaceDescription); + _json = Json.ConfigureOption(command); + + base.Configure(command); + } + } +} diff --git a/src/ef/Commands/DbContextOptimizeCommand.cs b/src/ef/Commands/DbContextOptimizeCommand.cs new file mode 100644 index 00000000000..89385c1f870 --- /dev/null +++ b/src/ef/Commands/DbContextOptimizeCommand.cs @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Microsoft.EntityFrameworkCore.Tools.Commands +{ + // ReSharper disable once ArrangeTypeModifiers + internal partial class DbContextOptimizeCommand + { + protected override int Execute(string[] args) + { + using var executor = CreateExecutor(args); + var result = executor.Optimize( + _outputDir!.Value(), + _namespace!.Value(), + Context!.Value()); + + if (_json!.HasValue()) + { + ReportJsonResults(result); + } + + return base.Execute(args); + } + private static void ReportJsonResults(IReadOnlyList result) + { + Reporter.WriteData("{"); + Reporter.WriteData(" \"files\": ["); + + for (var i = 0; i < result.Count; i++) + { + var line = " " + Json.Literal(result[i]); + if (i != result.Count - 1) + { + line += ","; + } + + Reporter.WriteData(line); + } + + Reporter.WriteData(" ]"); + Reporter.WriteData("}"); + } + } +} diff --git a/src/ef/IOperationExecutor.cs b/src/ef/IOperationExecutor.cs index 0715d0a3108..2838642c750 100644 --- a/src/ef/IOperationExecutor.cs +++ b/src/ef/IOperationExecutor.cs @@ -16,6 +16,7 @@ internal interface IOperationExecutor : IDisposable IDictionary GetContextInfo(string? name); void UpdateDatabase(string? migration, string? connectionString, string? contextType); IEnumerable GetContextTypes(); + IReadOnlyList Optimize(string? outputDir, string? modelNamespace, string? contextType); IDictionary ScaffoldContext( string provider, diff --git a/src/ef/OperationExecutorBase.cs b/src/ef/OperationExecutorBase.cs index b177fc52ae1..b96f6afae75 100644 --- a/src/ef/OperationExecutorBase.cs +++ b/src/ef/OperationExecutorBase.cs @@ -140,6 +140,16 @@ public void UpdateDatabase(string? migration, string? connectionString, string? public IEnumerable GetContextTypes() => InvokeOperation>("GetContextTypes"); + public IReadOnlyList Optimize(string? outputDir, string? modelNamespace, string? contextType) + => InvokeOperation>( + "Optimize", + new Dictionary + { + ["outputDir"] = outputDir, + ["modelNamespace"] = modelNamespace, + ["contextType"] = contextType + }); + public IDictionary ScaffoldContext( string provider, string connectionString, diff --git a/src/ef/Properties/Resources.Designer.cs b/src/ef/Properties/Resources.Designer.cs index c4686c743cc..4fbe4aadf44 100644 --- a/src/ef/Properties/Resources.Designer.cs +++ b/src/ef/Properties/Resources.Designer.cs @@ -153,6 +153,12 @@ public static string DbContextInfoDescription public static string DbContextListDescription => GetString("DbContextListDescription"); + /// + /// Generates a compiled version of the model used by the DbContext. + /// + public static string DbContextOptimizeDescription + => GetString("DbContextOptimizeDescription"); + /// /// Scaffolds a DbContext and entity types for a database. /// diff --git a/src/ef/Properties/Resources.resx b/src/ef/Properties/Resources.resx index 1fb7af61d17..0312a9ddc38 100644 --- a/src/ef/Properties/Resources.resx +++ b/src/ef/Properties/Resources.resx @@ -180,6 +180,9 @@ Lists available DbContext types. + + Generates a compiled version of the model used by the DbContext. + Scaffolds a DbContext and entity types for a database. diff --git a/test/EFCore.Design.Tests/Design/DesignTimeServicesTest.cs b/test/EFCore.Design.Tests/Design/DesignTimeServicesTest.cs index 22e425bfa6c..71169896e98 100644 --- a/test/EFCore.Design.Tests/Design/DesignTimeServicesTest.cs +++ b/test/EFCore.Design.Tests/Design/DesignTimeServicesTest.cs @@ -47,6 +47,8 @@ public void Services_are_registered_using_correct_priority(bool useContext) using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; +#nullable disable + [assembly: DesignTimeServicesReference(""Microsoft.EntityFrameworkCore.SqlServer.Design.Internal.SqlServerNetTopologySuiteDesignTimeServices, Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite"")] public class UserDesignTimeServices : IDesignTimeServices diff --git a/test/EFCore.Design.Tests/Design/Internal/DbContextOperationsTest.cs b/test/EFCore.Design.Tests/Design/Internal/DbContextOperationsTest.cs index 6c278f8b12e..b93164df582 100644 --- a/test/EFCore.Design.Tests/Design/Internal/DbContextOperationsTest.cs +++ b/test/EFCore.Design.Tests/Design/Internal/DbContextOperationsTest.cs @@ -40,6 +40,10 @@ public void Can_pass_null_args() new TestOperationReporter(), assembly, assembly, + projectDir: "", + rootNamespace: null, + language: "C#", + nullable: false, args: null, new TestAppServiceProviderFactory(assembly)); } @@ -52,6 +56,10 @@ public void CreateContext_uses_exact_factory_method() new TestOperationReporter(), assembly, assembly, + projectDir: "", + rootNamespace: null, + language: "C#", + nullable: false, args: Array.Empty(), new TestAppServiceProviderFactory(assembly)); @@ -153,6 +161,10 @@ private static TestDbContextOperations CreateOperations(Type testProgramType) new TestOperationReporter(), assembly, assembly, + projectDir: "", + rootNamespace: null, + language: "C#", + nullable: false, /* args: */ Array.Empty(), new TestAppServiceProviderFactory(assembly)); } diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs index 42751afabb3..842372a0d20 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs @@ -3659,6 +3659,8 @@ private void Test(T operation, string expectedCode, Action assert) using Microsoft.EntityFrameworkCore.Migrations; using NetTopologySuite.Geometries; + #nullable disable + public static class OperationsFactory { public static void Create(MigrationBuilder mb) diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs index 5d4d953be4e..2bb9f47587e 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpDbContextGeneratorTest.cs @@ -8,7 +8,6 @@ using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -268,7 +267,7 @@ public void IsRequired_is_generated_for_ref_property_without_nrt() x.Property("NonRequiredInt"); }); }, - new ModelCodeGenerationOptions(), + new ModelCodeGenerationOptions { UseNullableReferenceTypes = false }, code => { Assert.Contains("Property(e => e.RequiredString).IsRequired()", code.ContextFile.Code); Assert.DoesNotContain("NotRequiredString", code.ContextFile.Code); diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeAnnotationCodeGeneratorTest.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs similarity index 89% rename from test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeAnnotationCodeGeneratorTest.cs rename to test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs index 2ae6ca3efb9..fa1c072dab4 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeAnnotationCodeGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/CSharpRuntimeModelCodeGeneratorTest.cs @@ -33,7 +33,7 @@ namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal { - public class CSharpRuntimeAnnotationCodeGeneratorTest + public class CSharpRuntimeModelCodeGeneratorTest { [ConditionalFact] public void Empty_model() @@ -41,7 +41,9 @@ public void Empty_model() Test( new EmptyContext(), new CompiledModelCodeGenerationOptions(), - code => AssertFileContents( + code => + Assert.Collection(code, + c => AssertFileContents( "EmptyContextModel.cs", @"// using Microsoft.EntityFrameworkCore.Infrastructure; @@ -53,7 +55,7 @@ public void Empty_model() namespace TestNamespace { - [DbContext(typeof(CSharpRuntimeAnnotationCodeGeneratorTest.EmptyContext))] + [DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.EmptyContext))] partial class EmptyContextModel : RuntimeModel { private static EmptyContextModel _instance; @@ -65,23 +67,40 @@ public static IModel Instance { _instance = new EmptyContextModel(); _instance.Initialize(); + _instance.Customize(); } return _instance; } } - protected override void Initialize() - { - - Customize(); - } + partial void Initialize(); partial void Customize(); } } ", - code.Single()), + c), + c => AssertFileContents( + "EmptyContextModelBuilder.cs", + @"// +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class EmptyContextModel + { + partial void Initialize() + { + } + } +} +", + c)), model => { Assert.Empty(model.GetEntityTypes()); Assert.Same(model, model.FindRuntimeAnnotationValue("ReadOnlyModel")); @@ -443,25 +462,24 @@ public void BigModel() { Test( new BigContext(), - new CompiledModelCodeGenerationOptions(), + new CompiledModelCodeGenerationOptions { UseNullableReferenceTypes = true }, code => Assert.Collection(code, c => AssertFileContents("BigContextModel.cs", @"// -using System; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; #pragma warning disable 219, 612, 618 -#nullable disable +#nullable enable namespace TestNamespace { - [DbContext(typeof(CSharpRuntimeAnnotationCodeGeneratorTest.BigContext))] + [DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.BigContext))] partial class BigContextModel : RuntimeModel { - private static BigContextModel _instance; + private static BigContextModel? _instance; public static IModel Instance { get @@ -470,19 +488,39 @@ public static IModel Instance { _instance = new BigContextModel(); _instance.Initialize(); + _instance.Customize(); } return _instance; } } - protected override void Initialize() + partial void Initialize(); + + partial void Customize(); + } +} +", c), + c => AssertFileContents("BigContextModelBuilder.cs", + @"// +using System; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +#pragma warning disable 219, 612, 618 +#nullable enable + +namespace TestNamespace +{ + partial class BigContextModel + { + partial void Initialize() { - var dependentBasebyte = DependentBasebyteEntityType.Create(this, null); - var principalBase = PrincipalBaseEntityType.Create(this, null); - var ownedType = OwnedTypeEntityType.Create(this, null); - var ownedType0 = OwnedType0EntityType.Create(this, null); - var principalBasePrincipalDerivedDependentBasebyte = PrincipalBasePrincipalDerivedDependentBasebyteEntityType.Create(this, null); + var dependentBasebyte = DependentBasebyteEntityType.Create(this); + var principalBase = PrincipalBaseEntityType.Create(this); + var ownedType = OwnedTypeEntityType.Create(this); + var ownedType0 = OwnedType0EntityType.Create(this); + var principalBasePrincipalDerivedDependentBasebyte = PrincipalBasePrincipalDerivedDependentBasebyteEntityType.Create(this); var dependentDerivedbyte = DependentDerivedbyteEntityType.Create(this, dependentBasebyte); var principalDerivedDependentBasebyte = PrincipalDerivedDependentBasebyteEntityType.Create(this, principalBase); @@ -507,11 +545,7 @@ protected override void Initialize() AddAnnotation(""Relational:MaxIdentifierLength"", 128); AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); - - Customize(); } - - partial void Customize(); } } ", c), @@ -525,17 +559,17 @@ protected override void Initialize() using NetTopologySuite.Geometries; #pragma warning disable 219, 612, 618 -#nullable disable +#nullable enable namespace TestNamespace { partial class DependentBasebyteEntityType { - public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) { var runtimeEntityType = model.AddEntityType( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DependentBase"", - typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentBase), + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DependentBase"", + typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase), baseEntityType, discriminatorProperty: ""EnumDiscriminator""); @@ -554,7 +588,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var enumDiscriminator = runtimeEntityType.AddProperty( ""EnumDiscriminator"", - typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Discriminator), + typeof(CSharpRuntimeModelCodeGeneratorTest.Discriminator), afterSaveBehavior: PropertySaveBehavior.Throw, valueGeneratorFactory: new DiscriminatorValueGeneratorFactory().Create); enumDiscriminator.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); @@ -562,8 +596,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var id = runtimeEntityType.AddProperty( ""Id"", typeof(byte?), - propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentBase).GetProperty(""Id"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentBase).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase).GetProperty(""Id"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), nullable: true); id.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); @@ -580,8 +614,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) { - var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalId"") }, - principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id"") }), + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalId"")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id"")! })!, principalEntityType, deleteBehavior: DeleteBehavior.Cascade, unique: true, @@ -592,8 +626,8 @@ public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEnt public static RuntimeForeignKey CreateForeignKey2(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) { - var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalId""), declaringEntityType.FindProperty(""PrincipalAlternateId"") }, - principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id""), principalEntityType.FindProperty(""AlternateId"") }), + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalId"")!, declaringEntityType.FindProperty(""PrincipalAlternateId"")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id"")!, principalEntityType.FindProperty(""AlternateId"")! })!, principalEntityType, deleteBehavior: DeleteBehavior.ClientNoAction, unique: true, @@ -602,16 +636,16 @@ public static RuntimeForeignKey CreateForeignKey2(RuntimeEntityType declaringEnt var principal = declaringEntityType.AddNavigation(""Principal"", runtimeForeignKey, onDependent: true, - typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalDerived>), - propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentBase).GetProperty(""Principal"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentBase).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase).GetProperty(""Principal"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); var dependent = principalEntityType.AddNavigation(""Dependent"", runtimeForeignKey, onDependent: false, - typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentBase), - propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalDerived>).GetProperty(""Dependent"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalDerived>).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + typeof(CSharpRuntimeModelCodeGeneratorTest.DependentBase), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetProperty(""Dependent"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), eagerLoaded: true); return runtimeForeignKey; @@ -619,7 +653,7 @@ public static RuntimeForeignKey CreateForeignKey2(RuntimeEntityType declaringEnt public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) { - runtimeEntityType.AddAnnotation(""DiscriminatorValue"", CSharpRuntimeAnnotationCodeGeneratorTest.Discriminator.Base); + runtimeEntityType.AddAnnotation(""DiscriminatorValue"", CSharpRuntimeModelCodeGeneratorTest.Discriminator.Base); runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); runtimeEntityType.AddAnnotation(""Relational:Schema"", null); runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); @@ -645,24 +679,24 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) using NetTopologySuite.Geometries; #pragma warning disable 219, 612, 618 -#nullable disable +#nullable enable namespace TestNamespace { partial class PrincipalBaseEntityType { - public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) { var runtimeEntityType = model.AddEntityType( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+PrincipalBase"", - typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalBase), + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase"", + typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase), baseEntityType); var id = runtimeEntityType.AddProperty( ""Id"", typeof(long?), - propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalBase).GetProperty(""Id"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalBase).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty(""Id"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), valueGenerated: ValueGenerated.OnAdd, afterSaveBehavior: PropertySaveBehavior.Throw); var overrides = new SortedDictionary(); @@ -677,12 +711,12 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var alternateId = runtimeEntityType.AddProperty( ""AlternateId"", typeof(Point), - fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalBase).GetField(""AlternateId"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField(""AlternateId"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), propertyAccessMode: PropertyAccessMode.FieldDuringConstruction, valueGenerated: ValueGenerated.OnAdd, afterSaveBehavior: PropertySaveBehavior.Throw, valueConverter: new CastingConverter(), - valueComparer: new CSharpRuntimeAnnotationCodeGeneratorTest.CustomValueComparer()); + valueComparer: new CSharpRuntimeModelCodeGeneratorTest.CustomValueComparer()); alternateId.AddAnnotation(""Relational:ColumnType"", ""geometry""); alternateId.AddAnnotation(""Relational:DefaultValue"", (NetTopologySuite.Geometries.Point)new NetTopologySuite.IO.WKTReader().Read(""SRID=0;POINT Z(0 0 0)"")); alternateId.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); @@ -713,14 +747,14 @@ public static RuntimeSkipNavigation CreateSkipNavigation1(RuntimeEntityType decl ""Deriveds"", targetEntityType, joinEntityType.FindForeignKey( - new[] { joinEntityType.FindProperty(""PrincipalsId""), joinEntityType.FindProperty(""PrincipalsAlternateId"") }, - declaringEntityType.FindKey(new[] { declaringEntityType.FindProperty(""Id""), declaringEntityType.FindProperty(""AlternateId"") }), - declaringEntityType), + new[] { joinEntityType.FindProperty(""PrincipalsId"")!, joinEntityType.FindProperty(""PrincipalsAlternateId"")! }, + declaringEntityType.FindKey(new[] { declaringEntityType.FindProperty(""Id"")!, declaringEntityType.FindProperty(""AlternateId"")! })!, + declaringEntityType)!, true, false, - typeof(ICollection), - propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalBase).GetProperty(""Deriveds"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalBase).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + typeof(ICollection), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty(""Deriveds"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly)); var inverse = targetEntityType.FindSkipNavigation(""Principals""); if (inverse != null) @@ -757,17 +791,17 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) using NetTopologySuite.Geometries; #pragma warning disable 219, 612, 618 -#nullable disable +#nullable enable namespace TestNamespace { partial class OwnedTypeEntityType { - public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) { var runtimeEntityType = model.AddEntityType( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+PrincipalBase.Owned#OwnedType"", - typeof(CSharpRuntimeAnnotationCodeGeneratorTest.OwnedType), + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalBase.Owned#OwnedType"", + typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), baseEntityType, sharedClrType: true, changeTrackingStrategy: ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); @@ -789,7 +823,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var context = runtimeEntityType.AddServiceProperty( ""Context"", - propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.OwnedType).GetProperty(""Context"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty(""Context"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)); var key = runtimeEntityType.AddKey( new[] { principalBaseId, principalBaseAlternateId }); @@ -800,8 +834,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) { - var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalBaseId""), declaringEntityType.FindProperty(""PrincipalBaseAlternateId"") }, - principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id""), principalEntityType.FindProperty(""AlternateId"") }), + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalBaseId"")!, declaringEntityType.FindProperty(""PrincipalBaseAlternateId"")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id"")!, principalEntityType.FindProperty(""AlternateId"")! })!, principalEntityType, deleteBehavior: DeleteBehavior.Cascade, unique: true, @@ -812,9 +846,9 @@ public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEnt var owned = principalEntityType.AddNavigation(""Owned"", runtimeForeignKey, onDependent: false, - typeof(CSharpRuntimeAnnotationCodeGeneratorTest.OwnedType), - propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalBase).GetProperty(""Owned"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalBase).GetField(""_ownedField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetProperty(""Owned"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalBase).GetField(""_ownedField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), propertyAccessMode: PropertyAccessMode.Field, eagerLoaded: true); @@ -847,17 +881,17 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) using NetTopologySuite.Geometries; #pragma warning disable 219, 612, 618 -#nullable disable +#nullable enable namespace TestNamespace { partial class OwnedType0EntityType { - public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) { var runtimeEntityType = model.AddEntityType( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+PrincipalDerived>.ManyOwned#OwnedType"", - typeof(CSharpRuntimeAnnotationCodeGeneratorTest.OwnedType), + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>.ManyOwned#OwnedType"", + typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType), baseEntityType, sharedClrType: true); @@ -882,7 +916,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var context = runtimeEntityType.AddServiceProperty( ""Context"", - propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.OwnedType).GetProperty(""Context"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)); + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.OwnedType).GetProperty(""Context"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)); var key = runtimeEntityType.AddKey( new[] { principalDerivedDependentBasebyteId, principalDerivedDependentBasebyteAlternateId, id }); @@ -893,8 +927,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) { - var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalDerived>Id""), declaringEntityType.FindProperty(""PrincipalDerived>AlternateId"") }, - principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id""), principalEntityType.FindProperty(""AlternateId"") }), + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalDerived>Id"")!, declaringEntityType.FindProperty(""PrincipalDerived>AlternateId"")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id"")!, principalEntityType.FindProperty(""AlternateId"")! })!, principalEntityType, deleteBehavior: DeleteBehavior.Cascade, required: true, @@ -903,8 +937,8 @@ public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEnt var manyOwned = principalEntityType.AddNavigation(""ManyOwned"", runtimeForeignKey, onDependent: false, - typeof(ICollection), - fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalDerived>).GetField(""ManyOwned"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + typeof(ICollection), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetField(""ManyOwned"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), eagerLoaded: true); return runtimeForeignKey; @@ -936,13 +970,13 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) using NetTopologySuite.Geometries; #pragma warning disable 219, 612, 618 -#nullable disable +#nullable enable namespace TestNamespace { partial class PrincipalBasePrincipalDerivedDependentBasebyteEntityType { - public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) { var runtimeEntityType = model.AddEntityType( ""PrincipalBasePrincipalDerived>"", @@ -1003,8 +1037,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) { - var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""DerivedsId""), declaringEntityType.FindProperty(""DerivedsAlternateId"") }, - principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id""), principalEntityType.FindProperty(""AlternateId"") }), + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""DerivedsId"")!, declaringEntityType.FindProperty(""DerivedsAlternateId"")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id"")!, principalEntityType.FindProperty(""AlternateId"")! })!, principalEntityType, deleteBehavior: DeleteBehavior.Cascade, required: true); @@ -1014,8 +1048,8 @@ public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEnt public static RuntimeForeignKey CreateForeignKey2(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) { - var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalsId""), declaringEntityType.FindProperty(""PrincipalsAlternateId"") }, - principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id""), principalEntityType.FindProperty(""AlternateId"") }), + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""PrincipalsId"")!, declaringEntityType.FindProperty(""PrincipalsAlternateId"")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id"")!, principalEntityType.FindProperty(""AlternateId"")! })!, principalEntityType, deleteBehavior: DeleteBehavior.Cascade, required: true); @@ -1042,30 +1076,29 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) c => AssertFileContents("DependentDerivedbyteEntityType.cs", @"// using System; using System.Reflection; -using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; #pragma warning disable 219, 612, 618 -#nullable disable +#nullable enable namespace TestNamespace { partial class DependentDerivedbyteEntityType { - public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) { var runtimeEntityType = model.AddEntityType( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DependentDerived"", - typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentDerived), + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DependentDerived"", + typeof(CSharpRuntimeModelCodeGeneratorTest.DependentDerived), baseEntityType, discriminatorProperty: ""EnumDiscriminator""); var data = runtimeEntityType.AddProperty( ""Data"", typeof(string), - propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentDerived).GetProperty(""Data"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DependentDerived).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DependentDerived).GetProperty(""Data"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DependentDerived).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), nullable: true, maxLength: 20, unicode: false); @@ -1084,7 +1117,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) { - runtimeEntityType.AddAnnotation(""DiscriminatorValue"", CSharpRuntimeAnnotationCodeGeneratorTest.Discriminator.Derived); + runtimeEntityType.AddAnnotation(""DiscriminatorValue"", CSharpRuntimeModelCodeGeneratorTest.Discriminator.Derived); runtimeEntityType.AddAnnotation(""Relational:FunctionName"", null); runtimeEntityType.AddAnnotation(""Relational:Schema"", null); runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); @@ -1108,17 +1141,17 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) using Microsoft.EntityFrameworkCore.Scaffolding.Internal; #pragma warning disable 219, 612, 618 -#nullable disable +#nullable enable namespace TestNamespace { partial class PrincipalDerivedDependentBasebyteEntityType { - public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType? baseEntityType = null) { var runtimeEntityType = model.AddEntityType( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+PrincipalDerived>"", - typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalDerived>), + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+PrincipalDerived>"", + typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>), baseEntityType); return runtimeEntityType; @@ -1126,8 +1159,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas public static RuntimeForeignKey CreateForeignKey1(RuntimeEntityType declaringEntityType, RuntimeEntityType principalEntityType) { - var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""Id""), declaringEntityType.FindProperty(""AlternateId"") }, - principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id""), principalEntityType.FindProperty(""AlternateId"") }), + var runtimeForeignKey = declaringEntityType.AddForeignKey(new[] { declaringEntityType.FindProperty(""Id"")!, declaringEntityType.FindProperty(""AlternateId"")! }, + principalEntityType.FindKey(new[] { principalEntityType.FindProperty(""Id"")!, principalEntityType.FindProperty(""AlternateId"")! })!, principalEntityType, deleteBehavior: DeleteBehavior.ClientCascade, unique: true, @@ -1142,14 +1175,14 @@ public static RuntimeSkipNavigation CreateSkipNavigation1(RuntimeEntityType decl ""Principals"", targetEntityType, joinEntityType.FindForeignKey( - new[] { joinEntityType.FindProperty(""DerivedsId""), joinEntityType.FindProperty(""DerivedsAlternateId"") }, - declaringEntityType.FindKey(new[] { declaringEntityType.FindProperty(""Id""), declaringEntityType.FindProperty(""AlternateId"") }), - declaringEntityType), + new[] { joinEntityType.FindProperty(""DerivedsId"")!, joinEntityType.FindProperty(""DerivedsAlternateId"")! }, + declaringEntityType.FindKey(new[] { declaringEntityType.FindProperty(""Id"")!, declaringEntityType.FindProperty(""AlternateId"")! })!, + declaringEntityType)!, true, false, - typeof(ICollection), - propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalDerived>).GetProperty(""Principals"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.PrincipalDerived>).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + typeof(ICollection), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetProperty(""Principals"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.PrincipalDerived>).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), eagerLoaded: true); var inverse = targetEntityType.FindSkipNavigation(""Deriveds""); @@ -1388,8 +1421,8 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) var principalDerived = model.FindEntityType(typeof(PrincipalDerived>)); Assert.Equal(principalBase, principalDerived.BaseType); - Assert.Equal("Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+"+ - "PrincipalDerived>", + Assert.Equal("Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+"+ + "PrincipalDerived>", principalDerived.Name); Assert.False(principalDerived.IsOwned()); Assert.IsType(principalDerived.ConstructorBinding); @@ -1811,10 +1844,6 @@ public void DbFunctions() code => Assert.Collection(code, c => AssertFileContents("DbFunctionContextModel.cs", @"// -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; @@ -1824,7 +1853,7 @@ public void DbFunctions() namespace TestNamespace { - [DbContext(typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DbFunctionContext))] + [DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.DbFunctionContext))] partial class DbFunctionContextModel : RuntimeModel { private static DbFunctionContextModel _instance; @@ -1836,16 +1865,41 @@ public static IModel Instance { _instance = new DbFunctionContextModel(); _instance.Initialize(); + _instance.Customize(); } return _instance; } } - protected override void Initialize() + partial void Initialize(); + + partial void Customize(); + } +} +", + c), + c => AssertFileContents("DbFunctionContextModelBuilder.cs", + @"// +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Scaffolding.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class DbFunctionContextModel + { + partial void Initialize() { - var data = DataEntityType.Create(this, null); - var @object = ObjectEntityType.Create(this, null); + var data = DataEntityType.Create(this); + var @object = ObjectEntityType.Create(this); DataEntityType.CreateAnnotations(data); ObjectEntityType.CreateAnnotations(@object); @@ -1861,13 +1915,13 @@ protected override void Initialize() functions[""GetBlobs()""] = getBlobs; var getCount = new RuntimeDbFunction( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.GetCount(System.Guid?,string)"", + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DbFunctionContext.GetCount(System.Guid?,string)"", this, typeof(int), ""CustomerOrderCount"", schema: ""dbf"", storeType: ""int"", - methodInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DbFunctionContext).GetMethod( + methodInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DbFunctionContext).GetMethod( ""GetCount"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly, null, @@ -1888,30 +1942,30 @@ protected override void Initialize() false, ""nvarchar(max)""); - functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.GetCount(System.Guid?,string)""] = getCount; + functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DbFunctionContext.GetCount(System.Guid?,string)""] = getCount; var getData = new RuntimeDbFunction( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.GetData()"", + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DbFunctionContext.GetData()"", this, - typeof(IQueryable), + typeof(IQueryable), ""GetAllData"", schema: ""dbo"", - methodInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DbFunctionContext).GetMethod( + methodInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DbFunctionContext).GetMethod( ""GetData"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, null, new Type[] { }, null)); - functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.GetData()""] = getData; + functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DbFunctionContext.GetData()""] = getData; var getData0 = new RuntimeDbFunction( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.GetData(int)"", + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DbFunctionContext.GetData(int)"", this, - typeof(IQueryable), + typeof(IQueryable), ""GetData"", schema: ""dbo"", - methodInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DbFunctionContext).GetMethod( + methodInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DbFunctionContext).GetMethod( ""GetData"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, null, @@ -1924,15 +1978,15 @@ protected override void Initialize() false, ""int""); - functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.GetData(int)""] = getData0; + functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DbFunctionContext.GetData(int)""] = getData0; var isDateStatic = new RuntimeDbFunction( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.IsDateStatic(string)"", + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DbFunctionContext.IsDateStatic(string)"", this, typeof(bool), ""IsDate"", storeType: ""bit"", - methodInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.DbFunctionContext).GetMethod( + methodInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.DbFunctionContext).GetMethod( ""IsDateStatic"", BindingFlags.Public | BindingFlags.Static | BindingFlags.DeclaredOnly, null, @@ -1949,16 +2003,12 @@ protected override void Initialize() ""nvarchar(max)""); isDateStatic.AddAnnotation(""MyGuid"", new Guid(""00000000-0000-0000-0000-000000000000"")); - functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.IsDateStatic(string)""] = isDateStatic; + functions[""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DbFunctionContext.IsDateStatic(string)""] = isDateStatic; AddAnnotation(""Relational:DbFunctions"", functions); AddAnnotation(""Relational:MaxIdentifierLength"", 128); AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); - - Customize(); } - - partial void Customize(); } } ", @@ -1967,7 +2017,6 @@ protected override void Initialize() @"// using System; using System.Reflection; -using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; @@ -1978,18 +2027,18 @@ namespace TestNamespace { partial class DataEntityType { - public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null) { var runtimeEntityType = model.AddEntityType( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+Data"", - typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data), + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+Data"", + typeof(CSharpRuntimeModelCodeGeneratorTest.Data), baseEntityType); var blob = runtimeEntityType.AddProperty( ""Blob"", typeof(byte[]), - propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetProperty(""Blob"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.Data).GetProperty(""Blob"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.Data).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), nullable: true); blob.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); @@ -1998,7 +2047,7 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) { - runtimeEntityType.AddAnnotation(""Relational:FunctionName"", ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+DbFunctionContext.GetData()""); + runtimeEntityType.AddAnnotation(""Relational:FunctionName"", ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+DbFunctionContext.GetData()""); runtimeEntityType.AddAnnotation(""Relational:Schema"", null); runtimeEntityType.AddAnnotation(""Relational:SqlQuery"", null); runtimeEntityType.AddAnnotation(""Relational:TableName"", null); @@ -2017,7 +2066,6 @@ public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) @"// using System; using System.Reflection; -using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; #pragma warning disable 219, 612, 618 @@ -2027,7 +2075,7 @@ namespace TestNamespace { partial class ObjectEntityType { - public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null) { var runtimeEntityType = model.AddEntityType( ""object"", @@ -2278,10 +2326,8 @@ public void Sequences() new SequencesContext(), new CompiledModelCodeGenerationOptions(), code => Assert.Collection(code, - c => AssertFileContents("SequencesContextModel.cs", + c => AssertFileContents("SequencesContextModel.cs", @"// -using System; -using System.Collections.Generic; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; @@ -2291,7 +2337,7 @@ public void Sequences() namespace TestNamespace { - [DbContext(typeof(CSharpRuntimeAnnotationCodeGeneratorTest.SequencesContext))] + [DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.SequencesContext))] partial class SequencesContextModel : RuntimeModel { private static SequencesContextModel _instance; @@ -2303,15 +2349,37 @@ public static IModel Instance { _instance = new SequencesContextModel(); _instance.Initialize(); + _instance.Customize(); } return _instance; } } - protected override void Initialize() + partial void Initialize(); + + partial void Customize(); + } +} +", + c), + c => AssertFileContents("SequencesContextModelBuilder.cs", + @"// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class SequencesContextModel + { + partial void Initialize() { - var data = DataEntityType.Create(this, null); + var data = DataEntityType.Create(this); DataEntityType.CreateAnnotations(data); @@ -2340,11 +2408,7 @@ protected override void Initialize() AddAnnotation(""Relational:Sequences"", sequences); AddAnnotation(""Relational:MaxIdentifierLength"", 128); AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); - - Customize(); } - - partial void Customize(); } } ", @@ -2353,7 +2417,6 @@ protected override void Initialize() @"// using System; using System.Reflection; -using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; @@ -2364,11 +2427,11 @@ namespace TestNamespace { partial class DataEntityType { - public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null) { var runtimeEntityType = model.AddEntityType( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+Data"", - typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data), + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+Data"", + typeof(CSharpRuntimeModelCodeGeneratorTest.Data), baseEntityType); var id = runtimeEntityType.AddProperty( @@ -2383,8 +2446,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var blob = runtimeEntityType.AddProperty( ""Blob"", typeof(byte[]), - propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetProperty(""Blob"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.Data).GetProperty(""Blob"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.Data).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), nullable: true); blob.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); @@ -2473,7 +2536,6 @@ public void CheckConstraints() code => Assert.Collection(code, c => AssertFileContents("ConstraintsContextModel.cs", @"// -using System; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; @@ -2483,7 +2545,7 @@ public void CheckConstraints() namespace TestNamespace { - [DbContext(typeof(CSharpRuntimeAnnotationCodeGeneratorTest.ConstraintsContext))] + [DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.ConstraintsContext))] partial class ConstraintsContextModel : RuntimeModel { private static ConstraintsContextModel _instance; @@ -2495,25 +2557,42 @@ public static IModel Instance { _instance = new ConstraintsContextModel(); _instance.Initialize(); + _instance.Customize(); } return _instance; } } - protected override void Initialize() + partial void Initialize(); + + partial void Customize(); + } +} +", + c), + c => AssertFileContents("ConstraintsContextModelBuilder.cs", + @"// +using System; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class ConstraintsContextModel + { + partial void Initialize() { - var data = DataEntityType.Create(this, null); + var data = DataEntityType.Create(this); DataEntityType.CreateAnnotations(data); AddAnnotation(""Relational:MaxIdentifierLength"", 128); AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.IdentityColumn); - - Customize(); } - - partial void Customize(); } } ", @@ -2523,7 +2602,6 @@ protected override void Initialize() using System; using System.Collections.Generic; using System.Reflection; -using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; @@ -2534,11 +2612,11 @@ namespace TestNamespace { partial class DataEntityType { - public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null) { var runtimeEntityType = model.AddEntityType( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+Data"", - typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data), + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+Data"", + typeof(CSharpRuntimeModelCodeGeneratorTest.Data), baseEntityType); var id = runtimeEntityType.AddProperty( @@ -2551,8 +2629,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var blob = runtimeEntityType.AddProperty( ""Blob"", typeof(byte[]), - propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetProperty(""Blob"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.Data).GetProperty(""Blob"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.Data).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), nullable: true); blob.AddAnnotation(""SqlServer:ValueGenerationStrategy"", SqlServerValueGenerationStrategy.None); @@ -2638,20 +2716,19 @@ public void Sqlite() { Test( new SqliteContext(), - new CompiledModelCodeGenerationOptions(), + new CompiledModelCodeGenerationOptions { ModelNamespace = "Microsoft.EntityFrameworkCore.Metadata" }, code => Assert.Collection(code, c => AssertFileContents("SqliteContextModel.cs", @"// using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; #pragma warning disable 219, 612, 618 #nullable disable -namespace TestNamespace +namespace Microsoft.EntityFrameworkCore.Metadata { - [DbContext(typeof(CSharpRuntimeAnnotationCodeGeneratorTest.SqliteContext))] + [DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.SqliteContext))] partial class SqliteContextModel : RuntimeModel { private static SqliteContextModel _instance; @@ -2663,23 +2740,38 @@ public static IModel Instance { _instance = new SqliteContextModel(); _instance.Initialize(); + _instance.Customize(); } return _instance; } } - protected override void Initialize() + partial void Initialize(); + + partial void Customize(); + } +} +", + c), + c => AssertFileContents("SqliteContextModelBuilder.cs", + @"// +using Microsoft.EntityFrameworkCore.Infrastructure; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace Microsoft.EntityFrameworkCore.Metadata +{ + partial class SqliteContextModel + { + partial void Initialize() { - var data = DataEntityType.Create(this, null); + var data = DataEntityType.Create(this); DataEntityType.CreateAnnotations(data); - - Customize(); } - - partial void Customize(); } } ", @@ -2688,23 +2780,21 @@ protected override void Initialize() @"// using System; using System.Reflection; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; using NetTopologySuite.Geometries; #pragma warning disable 219, 612, 618 #nullable disable -namespace TestNamespace +namespace Microsoft.EntityFrameworkCore.Metadata { partial class DataEntityType { - public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null) { var runtimeEntityType = model.AddEntityType( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+Data"", - typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data), + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+Data"", + typeof(CSharpRuntimeModelCodeGeneratorTest.Data), baseEntityType); var id = runtimeEntityType.AddProperty( @@ -2716,8 +2806,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var blob = runtimeEntityType.AddProperty( ""Blob"", typeof(byte[]), - propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetProperty(""Blob"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.Data).GetProperty(""Blob"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.Data).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), nullable: true); var point = runtimeEntityType.AddProperty( @@ -2809,7 +2899,6 @@ public void Cosmos() code => Assert.Collection(code, c => AssertFileContents("CosmosContextModel.cs", @"// -using System; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; @@ -2819,7 +2908,7 @@ public void Cosmos() namespace TestNamespace { - [DbContext(typeof(CSharpRuntimeAnnotationCodeGeneratorTest.CosmosContext))] + [DbContext(typeof(CSharpRuntimeModelCodeGeneratorTest.CosmosContext))] partial class CosmosContextModel : RuntimeModel { private static CosmosContextModel _instance; @@ -2831,24 +2920,41 @@ public static IModel Instance { _instance = new CosmosContextModel(); _instance.Initialize(); + _instance.Customize(); } return _instance; } } - protected override void Initialize() + partial void Initialize(); + + partial void Customize(); + } +} +", + c), + c => AssertFileContents("CosmosContextModelBuilder.cs", + @"// +using System; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace TestNamespace +{ + partial class CosmosContextModel + { + partial void Initialize() { - var data = DataEntityType.Create(this, null); + var data = DataEntityType.Create(this); DataEntityType.CreateAnnotations(data); AddAnnotation(""Cosmos:ContainerName"", ""Default""); - - Customize(); } - - partial void Customize(); } } ", @@ -2857,7 +2963,6 @@ protected override void Initialize() @"// using System; using System.Reflection; -using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Cosmos.ValueGeneration; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Scaffolding.Internal; @@ -2870,11 +2975,11 @@ namespace TestNamespace { partial class DataEntityType { - public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType) + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null) { var runtimeEntityType = model.AddEntityType( - ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeAnnotationCodeGeneratorTest+Data"", - typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data), + ""Microsoft.EntityFrameworkCore.Scaffolding.Internal.CSharpRuntimeModelCodeGeneratorTest+Data"", + typeof(CSharpRuntimeModelCodeGeneratorTest.Data), baseEntityType); var id = runtimeEntityType.AddProperty( @@ -2891,8 +2996,8 @@ public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType bas var blob = runtimeEntityType.AddProperty( ""Blob"", typeof(byte[]), - propertyInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetProperty(""Blob"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), - fieldInfo: typeof(CSharpRuntimeAnnotationCodeGeneratorTest.Data).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + propertyInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.Data).GetProperty(""Blob"", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(CSharpRuntimeModelCodeGeneratorTest.Data).GetField(""k__BackingField"", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), nullable: true); blob.AddAnnotation(""Cosmos:PropertyName"", ""JsonBlob""); @@ -3100,7 +3205,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnConfiguring(DbContextOptionsBuilder options) => options .EnableServiceProviderCaching(false) - .UseInMemoryDatabase(nameof(CSharpRuntimeAnnotationCodeGeneratorTest)); + .UseInMemoryDatabase(nameof(CSharpRuntimeModelCodeGeneratorTest)); } public abstract class SqlServerContextBase : DbContext @@ -3174,7 +3279,7 @@ protected void Test( BuildReference.ByName("Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite"), BuildReference.ByName("NetTopologySuite"), BuildReference.ByName("Newtonsoft.Json"), - BuildReference.ByName(typeof(CSharpRuntimeAnnotationCodeGeneratorTest).Assembly.GetName().Name) + BuildReference.ByName(typeof(CSharpRuntimeModelCodeGeneratorTest).Assembly.GetName().Name) }, Sources = scaffoldedFiles.ToDictionary(f => f.Path, f => f.Code) }; diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/ModelCodeGeneratorTestBase.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/ModelCodeGeneratorTestBase.cs index 3f1dc3cec58..0c4e62bc580 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/ModelCodeGeneratorTestBase.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/ModelCodeGeneratorTestBase.cs @@ -2,14 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Linq; -using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Design.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; -using Microsoft.EntityFrameworkCore.SqlServer.Design.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Xunit; diff --git a/test/EFCore.Design.Tests/TestUtilities/BuildSource.cs b/test/EFCore.Design.Tests/TestUtilities/BuildSource.cs index c1e5ee4bea3..e40db557002 100644 --- a/test/EFCore.Design.Tests/TestUtilities/BuildSource.cs +++ b/test/EFCore.Design.Tests/TestUtilities/BuildSource.cs @@ -109,11 +109,14 @@ private CSharpCompilationOptions CreateOptions() OutputKind.DynamicallyLinkedLibrary, nullableContextOptions: NullableReferenceTypes ? NullableContextOptions.Enable : NullableContextOptions.Disable, reportSuppressedDiagnostics: false, + // TODO: Uncomment when #18950 is fixed + //generalDiagnosticOption: ReportDiagnostic.Error, specificDiagnosticOptions: new Dictionary() { - { "CS1701", ReportDiagnostic.Suppress }, - { "CS1702", ReportDiagnostic.Suppress }, - { "CS1705", ReportDiagnostic.Suppress } + { "CS1030", ReportDiagnostic.Suppress }, + { "CS1701", ReportDiagnostic.Suppress }, { "CS1702", ReportDiagnostic.Suppress }, // Always thrown for .NET Core + { "CS1705", ReportDiagnostic.Suppress }, // Assembly 'AssemblyName1' uses 'TypeName' which has a higher version than referenced assembly 'AssemblyName2' + { "CS8019", ReportDiagnostic.Suppress } // Unnecessary using directive. }); } } diff --git a/test/EFCore.Design.Tests/TestUtilities/TestDbContextOperations.cs b/test/EFCore.Design.Tests/TestUtilities/TestDbContextOperations.cs index 49e50b64bc8..ab4569d934d 100644 --- a/test/EFCore.Design.Tests/TestUtilities/TestDbContextOperations.cs +++ b/test/EFCore.Design.Tests/TestUtilities/TestDbContextOperations.cs @@ -12,9 +12,13 @@ public TestDbContextOperations( IOperationReporter reporter, Assembly assembly, Assembly startupAssembly, + string projectDir, + string rootNamespace, + string language, + bool nullable, string[] args, AppServiceProviderFactory appServicesFactory) - : base(reporter, assembly, startupAssembly, args, appServicesFactory) + : base(reporter, assembly, startupAssembly, projectDir, rootNamespace, language, nullable, args, appServicesFactory) { } } diff --git a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs index d93943837a9..5579307dda9 100644 --- a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsInfrastructureTestBase.cs @@ -307,7 +307,7 @@ public virtual void Can_get_active_provider() protected virtual void DiffSnapshot(ModelSnapshot snapshot, DbContext context) { var sourceModel = context.GetService().Initialize( - ((IMutableModel)snapshot.Model).FinalizeModel(), designTime: true, validationLogger: null); + snapshot.Model, designTime: true, validationLogger: null); var modelDiffer = context.GetService(); var operations = modelDiffer.GetDifferences( diff --git a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsSqlGeneratorTestBase.cs b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsSqlGeneratorTestBase.cs index 7e1eed8cf9a..2a396a03c64 100644 --- a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsSqlGeneratorTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsSqlGeneratorTestBase.cs @@ -770,7 +770,7 @@ protected virtual void Generate( buildAction(modelBuilder); model = services.GetService().Initialize( - modelBuilder.FinalizeModel(), designTime: true, validationLogger: null); + (IModel)modelBuilder.Model, designTime: true, validationLogger: null); } var batch = services.GetRequiredService().Generate(operation, model, options); diff --git a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs index 608b4448666..c7420cfeb02 100644 --- a/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Migrations/MigrationsTestBase.cs @@ -1610,13 +1610,13 @@ protected virtual Task Test( var sourceModelBuilder = CreateConventionlessModelBuilder(); buildCommonAction(sourceModelBuilder); buildSourceAction(sourceModelBuilder); - var sourceModel = modelRuntimeInitializer.Initialize(sourceModelBuilder.FinalizeModel(), designTime: true, validationLogger: null); + var sourceModel = modelRuntimeInitializer.Initialize((IModel)sourceModelBuilder.Model, designTime: true, validationLogger: null); var targetModelBuilder = CreateConventionlessModelBuilder(); buildCommonAction(targetModelBuilder); buildTargetAction(targetModelBuilder); - var targetModel = modelRuntimeInitializer.Initialize(targetModelBuilder.FinalizeModel(), designTime: true, validationLogger: null); + var targetModel = modelRuntimeInitializer.Initialize((IModel)targetModelBuilder.Model, designTime: true, validationLogger: null); var operations = modelDiffer.GetDifferences(sourceModel.GetRelationalModel(), targetModel.GetRelationalModel()); @@ -1655,7 +1655,7 @@ protected virtual Task Test( var context = CreateContext(); var modelRuntimeInitializer = context.GetService(); - var sourceModel = modelRuntimeInitializer.Initialize(sourceModelBuilder.FinalizeModel(), designTime: true, validationLogger: null); + var sourceModel = modelRuntimeInitializer.Initialize((IModel)sourceModelBuilder.Model, designTime: true, validationLogger: null); return Test(sourceModel, targetModel: null, operations, asserter); } diff --git a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs index 8829673a395..01643163d55 100644 --- a/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs +++ b/test/EFCore.Relational.Tests/Migrations/Internal/MigrationsModelDifferTestBase.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations.Operations; @@ -57,7 +58,7 @@ protected void Execute( var sourceModelBuilder = CreateModelBuilder(skipSourceConventions); buildCommonAction(sourceModelBuilder); buildSourceAction(sourceModelBuilder); - var sourceModel = sourceModelBuilder.FinalizeModel(); + var sourceModel = (IModel)sourceModelBuilder.Model; var sourceOptionsBuilder = TestHelpers .AddProviderOptions(new DbContextOptionsBuilder()) .UseModel(sourceModel) @@ -66,7 +67,7 @@ protected void Execute( var targetModelBuilder = CreateModelBuilder(skipConventions: false); buildCommonAction(targetModelBuilder); buildTargetAction(targetModelBuilder); - var targetModel = targetModelBuilder.FinalizeModel(); + var targetModel = (IModel)targetModelBuilder.Model; var targetOptionsBuilder = TestHelpers .AddProviderOptions(new DbContextOptionsBuilder()) .UseModel(targetModel) @@ -89,8 +90,6 @@ protected void Execute( if (assertActionDown != null) { - modelDiffer = CreateModelDiffer(sourceOptionsBuilder.Options); - var operationsDown = modelDiffer.GetDifferences(targetModel.GetRelationalModel(), sourceModel.GetRelationalModel()); assertActionDown(operationsDown); } diff --git a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs index 3c8f21917b2..41e5c734439 100644 --- a/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs +++ b/test/EFCore.Specification.Tests/DataAnnotationTestBase.cs @@ -59,7 +59,7 @@ protected virtual IModel Validate(ModelBuilder modelBuilder) var context = CreateContext(); var modelRuntimeInitializer = context.GetService(); var logger = context.GetService>(); - return modelRuntimeInitializer.Initialize(modelBuilder.FinalizeModel(), designTime: false, logger); + return modelRuntimeInitializer.Initialize((IModel)modelBuilder.Model, designTime: true, logger); } protected class Person diff --git a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs index a48cc65f528..23f74e814a9 100644 --- a/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs +++ b/test/EFCore.Specification.Tests/TestUtilities/TestHelpers.cs @@ -132,7 +132,7 @@ public IModel Finalize(ModelBuilder modelBuilder, bool designTime = false, bool var contextServices = CreateContextServices(); var modelRuntimeInitializer = contextServices.GetRequiredService(); - return modelRuntimeInitializer.Initialize(modelBuilder.FinalizeModel(), designTime, skipValidation + return modelRuntimeInitializer.Initialize((IModel)modelBuilder.Model, designTime, skipValidation ? null : new TestLogger(LoggingDefinitions)); } diff --git a/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs b/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs index 4ca0bfaaf4a..3426d019ee1 100644 --- a/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs +++ b/test/EFCore.Tests/Infrastructure/ModelValidatorTestBase.cs @@ -298,7 +298,7 @@ protected virtual IModel Validate(IMutableModel model, bool sensitiveDataLogging var modelRuntimeInitializer = serviceProvider.GetRequiredService(); var validationLogger = CreateValidationLogger(sensitiveDataLoggingEnabled); - return modelRuntimeInitializer.Initialize(model.FinalizeModel(), designTime: true, validationLogger); + return modelRuntimeInitializer.Initialize((IModel)model, designTime: true, validationLogger); } protected DiagnosticsLogger CreateValidationLogger(bool sensitiveDataLoggingEnabled = false) diff --git a/test/EFCore.Tests/Metadata/Conventions/PropertyMappingValidationConventionTest.cs b/test/EFCore.Tests/Metadata/Conventions/PropertyMappingValidationConventionTest.cs index 292e2a997f6..1473e1dabc5 100644 --- a/test/EFCore.Tests/Metadata/Conventions/PropertyMappingValidationConventionTest.cs +++ b/test/EFCore.Tests/Metadata/Conventions/PropertyMappingValidationConventionTest.cs @@ -203,7 +203,6 @@ protected virtual Action CreatePropertyMappingValidator() { try { - m.FinalizeModel(); modelRuntimeInitializer.Initialize(m, designTime: false, validationLogger: null); validatePropertyMappingMethod.Invoke( validator, new object[] { m, logger }); diff --git a/test/EFCore.Tests/Metadata/Internal/PropertyBaseTest.cs b/test/EFCore.Tests/Metadata/Internal/PropertyBaseTest.cs index 025c4167ea7..0d9ded1efca 100644 --- a/test/EFCore.Tests/Metadata/Internal/PropertyBaseTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/PropertyBaseTest.cs @@ -866,7 +866,7 @@ private void MemberInfoTestCommon( try { - var model = ((Model)propertyBase.DeclaringType.Model).FinalizeModel(); + var model = propertyBase.DeclaringType.Model; var contextServices = InMemoryTestHelpers.Instance.CreateContextServices(); var modelRuntimeInitializer = contextServices.GetRequiredService(); diff --git a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs index a9d2f61cd2e..a433360395e 100644 --- a/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ModelBuilderTestBase.cs @@ -178,7 +178,7 @@ public virtual IModel FinalizeModel() var serviceProvider = TestHelpers.CreateContextServices(); var modelRuntimeInitializer = serviceProvider.GetRequiredService(); - return modelRuntimeInitializer.Initialize(ModelBuilder.FinalizeModel(), designTime: true, ValidationLogger); + return modelRuntimeInitializer.Initialize((IModel)ModelBuilder.Model, designTime: true, ValidationLogger); } public virtual string GetDisplayName(Type entityType) diff --git a/test/EFCore.Tests/ModelSourceTest.cs b/test/EFCore.Tests/ModelSourceTest.cs index 278dc4911ae..eb61d82d190 100644 --- a/test/EFCore.Tests/ModelSourceTest.cs +++ b/test/EFCore.Tests/ModelSourceTest.cs @@ -8,10 +8,12 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Diagnostics.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -22,6 +24,9 @@ namespace Microsoft.EntityFrameworkCore { public class ModelSourceTest { + private readonly IServiceProvider _serviceProvider = new ServiceCollection() + .AddEntityFrameworkInMemoryDatabase().BuildServiceProvider(); + [ConditionalFact] public void OnModelCreating_is_only_called_once() { @@ -33,7 +38,7 @@ public void OnModelCreating_is_only_called_once() 0, threadCount, i => { - using var context = new SlowContext(); + using var context = new SlowContext(_serviceProvider); models[i] = context.Model; }); @@ -49,6 +54,13 @@ public void OnModelCreating_is_only_called_once() private class SlowContext : DbContext { + private readonly IServiceProvider _serviceProvider; + + public SlowContext(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + public static int CallCount { get; private set; } protected internal override void OnModelCreating(ModelBuilder modelBuilder) @@ -59,7 +71,7 @@ protected internal override void OnModelCreating(ModelBuilder modelBuilder) protected internal override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder - .UseInternalServiceProvider(InMemoryFixture.DefaultServiceProvider) + .UseInternalServiceProvider(_serviceProvider) .UseInMemoryDatabase(nameof(SlowContext)); } @@ -153,33 +165,67 @@ public void Model_from_options_is_preserved() Assert.NotSame(model, designTimeModel); - var context = new ModelContext(model); + var context = new ModelContext(model, _serviceProvider); Assert.NotSame(context.Model, context.GetService().Model); Assert.Same(model, context.Model); Assert.NotSame(model, context.GetService().Model); - var designTimeContext = new ModelContext(designTimeModel); + var designTimeContext = new ModelContext(designTimeModel, _serviceProvider); Assert.NotSame(context.Model, designTimeContext.GetService().Model); Assert.NotSame(model, designTimeContext.Model); Assert.Same(designTimeModel, designTimeContext.GetService().Model); } + [ConditionalFact] + public void Throws_for_model_from_options_with_different_version() + { + var model = (Model)InMemoryTestHelpers.Instance.CreateConventionBuilder().Model; + model.SetProductVersion("1.0.0"); + + var context = new ModelContext(model, _serviceProvider); + var warning = CoreStrings.WarningAsErrorTemplate( + CoreEventId.OldModelVersionWarning, + CoreResources.LogOldModelVersion( + new TestLogger()).GenerateMessage("1.0.0", ProductInfo.GetVersion()), + "CoreEventId.OldModelVersionWarning"); + + Assert.Equal(warning, + Assert.Throws(() => context.Model).Message); + } + + [ConditionalFact] + public void Does_not_throw_for_model_from_options_with_different_patch_version() + { + var productVersion = ProductInfo.GetVersion(); + var productMinorVersion = productVersion[..productVersion.LastIndexOf('.')]; + + var model = (Model)InMemoryTestHelpers.Instance.CreateConventionBuilder().Model; + model.SetProductVersion(productMinorVersion + ".new"); + + var context = new ModelContext(model, _serviceProvider); + + Assert.NotNull(context.Model); + } + private class ModelContext : DbContext { private readonly IModel _model; + private readonly IServiceProvider _serviceProvider; - public ModelContext(IModel model) + public ModelContext(IModel model, IServiceProvider serviceProvider) { _model = model; + _serviceProvider = serviceProvider; } protected internal override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder = optionsBuilder - .UseInternalServiceProvider(InMemoryFixture.DefaultServiceProvider) - .UseInMemoryDatabase(nameof(ModelContext)); + .UseInternalServiceProvider(_serviceProvider) + .UseInMemoryDatabase(nameof(ModelContext)) + .ConfigureWarnings(w => w.Default(WarningBehavior.Throw)); if (_model != null) {