From 78ec15a57c8320bc36a9610795754bb3127ba9c1 Mon Sep 17 00:00:00 2001 From: Andriy Svyryd Date: Wed, 26 Feb 2025 15:11:21 -0800 Subject: [PATCH] Set the environment when executing Scaffold-DBContext Fixes #35641 --- .../Internal/AppServiceProviderFactory.cs | 26 +++++++ .../Design/Internal/DatabaseOperations.cs | 3 + .../Design/Internal/DbContextOperations.cs | 17 +---- .../Design/Internal/DatabaseOperationsTest.cs | 70 ++++++++++++++++++- .../CSharpMigrationOperationGeneratorTest.cs | 4 +- .../ReverseEngineeringConfigurationTests.cs | 38 ---------- .../TestUtilities/TestDatabaseOperations.cs | 16 ----- .../Internal/EntityTypeTest.BaseType.cs | 2 +- .../BytesToStringConverterTest.cs | 2 +- .../StringToBytesConverterTest.cs | 2 +- 10 files changed, 103 insertions(+), 77 deletions(-) delete mode 100644 test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineeringConfigurationTests.cs delete mode 100644 test/EFCore.Design.Tests/TestUtilities/TestDatabaseOperations.cs diff --git a/src/EFCore.Design/Design/Internal/AppServiceProviderFactory.cs b/src/EFCore.Design/Design/Internal/AppServiceProviderFactory.cs index b797f8a4add..456184ec46a 100644 --- a/src/EFCore.Design/Design/Internal/AppServiceProviderFactory.cs +++ b/src/EFCore.Design/Design/Internal/AppServiceProviderFactory.cs @@ -89,4 +89,30 @@ private IServiceProvider CreateEmptyServiceProvider() return new ServiceCollection().BuildServiceProvider(); } + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static void SetEnvironment(IOperationReporter reporter) + { + var aspnetCoreEnvironment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); + var dotnetEnvironment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT"); + var environment = aspnetCoreEnvironment + ?? dotnetEnvironment + ?? "Development"; + if (aspnetCoreEnvironment == null) + { + Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", environment); + } + + if (dotnetEnvironment == null) + { + Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", environment); + } + + reporter.WriteVerbose(DesignStrings.UsingEnvironment(environment)); + } } diff --git a/src/EFCore.Design/Design/Internal/DatabaseOperations.cs b/src/EFCore.Design/Design/Internal/DatabaseOperations.cs index d27a2532b3c..f02f26bf4ed 100644 --- a/src/EFCore.Design/Design/Internal/DatabaseOperations.cs +++ b/src/EFCore.Design/Design/Internal/DatabaseOperations.cs @@ -11,6 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Design.Internal; /// public class DatabaseOperations { + private readonly IOperationReporter _reporter; private readonly string _projectDir; private readonly string? _rootNamespace; private readonly string? _language; @@ -34,6 +35,7 @@ public DatabaseOperations( bool nullable, string[]? args) { + _reporter = reporter; _projectDir = projectDir; _rootNamespace = rootNamespace; _language = language; @@ -73,6 +75,7 @@ public virtual SavedModelFiles ScaffoldContext( ? Path.GetFullPath(Path.Combine(_projectDir, outputContextDir)) : outputDir; + AppServiceProviderFactory.SetEnvironment(_reporter); var services = _servicesBuilder.Build(provider); using var scope = services.CreateScope(); diff --git a/src/EFCore.Design/Design/Internal/DbContextOperations.cs b/src/EFCore.Design/Design/Internal/DbContextOperations.cs index 761579c8bdb..f430f9892b0 100644 --- a/src/EFCore.Design/Design/Internal/DbContextOperations.cs +++ b/src/EFCore.Design/Design/Internal/DbContextOperations.cs @@ -503,22 +503,7 @@ private IDictionary> FindContextTypes(string? name = null, { _reporter.WriteVerbose(DesignStrings.FindingContexts); - var aspnetCoreEnvironment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"); - var dotnetEnvironment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT"); - var environment = aspnetCoreEnvironment - ?? dotnetEnvironment - ?? "Development"; - if (aspnetCoreEnvironment == null) - { - Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", environment); - } - - if (dotnetEnvironment == null) - { - Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", environment); - } - - _reporter.WriteVerbose(DesignStrings.UsingEnvironment(environment)); + AppServiceProviderFactory.SetEnvironment(_reporter); var contexts = new Dictionary?>(); diff --git a/test/EFCore.Design.Tests/Design/Internal/DatabaseOperationsTest.cs b/test/EFCore.Design.Tests/Design/Internal/DatabaseOperationsTest.cs index d5c7a100554..d49bac41616 100644 --- a/test/EFCore.Design.Tests/Design/Internal/DatabaseOperationsTest.cs +++ b/test/EFCore.Design.Tests/Design/Internal/DatabaseOperationsTest.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.EntityFrameworkCore.Internal; + namespace Microsoft.EntityFrameworkCore.Design.Internal; public class DatabaseOperationsTest @@ -10,8 +12,71 @@ public void Can_pass_null_args() { // Even though newer versions of the tools will pass an empty array // older versions of the tools can pass null args. + CreateOperations(null); + } + + [ConditionalFact] + public void ScaffoldContext_throws_exceptions_for_invalid_context_name() + { + ValidateContextNameInReverseEngineerGenerator("Invalid!CSharp*Class&Name"); + ValidateContextNameInReverseEngineerGenerator("1CSharpClassNameCannotStartWithNumber"); + ValidateContextNameInReverseEngineerGenerator("volatile"); + } + + private void ValidateContextNameInReverseEngineerGenerator(string contextName) + { + var operations = CreateOperations([]); + + Assert.Equal( + DesignStrings.ContextClassNotValidCSharpIdentifier(contextName), + Assert.Throws( + () => operations.ScaffoldContext( + "Microsoft.EntityFrameworkCore.SqlServer", + "connectionstring", + "", + "", + dbContextClassName: contextName, + null, + null, + "FakeNamespace", + contextNamespace: null, + useDataAnnotations: false, + overwriteFiles: true, + useDatabaseNames: false, + suppressOnConfiguring: true, + noPluralize: false)) + .Message); + } + + [ConditionalFact] + [SqlServerConfiguredCondition] + public void ScaffoldContext_sets_environment() + { + var operations = CreateOperations([]); + operations.ScaffoldContext( + "Microsoft.EntityFrameworkCore.SqlServer", + TestEnvironment.DefaultConnection, + "", + "", + dbContextClassName: nameof(TestContext), + schemas: ["Empty"], + null, + null, + contextNamespace: null, + useDataAnnotations: false, + overwriteFiles: true, + useDatabaseNames: false, + suppressOnConfiguring: true, + noPluralize: false); + + Assert.Equal("Development", Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")); + Assert.Equal("Development", Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")); + } + + private static DatabaseOperations CreateOperations(string[] args) + { var assembly = MockAssembly.Create(typeof(TestContext)); - _ = new TestDatabaseOperations( + var operations = new DatabaseOperations( new TestOperationReporter(), assembly, assembly, @@ -19,7 +84,8 @@ public void Can_pass_null_args() "RootNamespace", "C#", nullable: false, - args: null); + args: args); + return operations; } public class TestContext : DbContext; diff --git a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs index ba445da389c..15f7e341fcf 100644 --- a/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs +++ b/test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs @@ -2453,7 +2453,7 @@ public void InsertDataOperation_required_empty_array() Assert.Single(o.Columns); Assert.Equal(1, o.Values.GetLength(0)); Assert.Equal(1, o.Values.GetLength(1)); - Assert.Equal([], (string[])o.Values[0, 0]); + Assert.Equal(new string[0], (string[])o.Values[0, 0]); }); [ConditionalFact] @@ -2478,7 +2478,7 @@ public void InsertDataOperation_required_empty_array_composite() Assert.Equal(1, o.Values.GetLength(0)); Assert.Equal(3, o.Values.GetLength(1)); Assert.Null(o.Values[0, 1]); - Assert.Equal([], (string[])o.Values[0, 2]); + Assert.Equal(new string[0], (string[])o.Values[0, 2]); }); [ConditionalFact] diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineeringConfigurationTests.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineeringConfigurationTests.cs deleted file mode 100644 index e73a91f883a..00000000000 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/ReverseEngineeringConfigurationTests.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.EntityFrameworkCore.Design.Internal; -using Microsoft.EntityFrameworkCore.Internal; - -namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal; - -public class ReverseEngineeringConfigurationTests -{ - [ConditionalFact] - public void Throws_exceptions_for_invalid_context_name() - { - ValidateContextNameInReverseEngineerGenerator("Invalid!CSharp*Class&Name"); - ValidateContextNameInReverseEngineerGenerator("1CSharpClassNameCannotStartWithNumber"); - ValidateContextNameInReverseEngineerGenerator("volatile"); - } - - private void ValidateContextNameInReverseEngineerGenerator(string contextName) - { - var assembly = typeof(ReverseEngineeringConfigurationTests).Assembly; - var reverseEngineer = new DesignTimeServicesBuilder(assembly, assembly, new TestOperationReporter(), []) - .Build("Microsoft.EntityFrameworkCore.SqlServer") - .CreateScope() - .ServiceProvider - .GetRequiredService(); - - Assert.Equal( - DesignStrings.ContextClassNotValidCSharpIdentifier(contextName), - Assert.Throws( - () => reverseEngineer.ScaffoldModel( - "connectionstring", - new DatabaseModelFactoryOptions(), - new ModelReverseEngineerOptions(), - new ModelCodeGenerationOptions { ModelNamespace = "FakeNamespace", ContextName = contextName })) - .Message); - } -} diff --git a/test/EFCore.Design.Tests/TestUtilities/TestDatabaseOperations.cs b/test/EFCore.Design.Tests/TestUtilities/TestDatabaseOperations.cs deleted file mode 100644 index adf200ef857..00000000000 --- a/test/EFCore.Design.Tests/TestUtilities/TestDatabaseOperations.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.EntityFrameworkCore.Design.Internal; - -namespace Microsoft.EntityFrameworkCore.TestUtilities; - -public class TestDatabaseOperations( - IOperationReporter reporter, - Assembly assembly, - Assembly startupAssembly, - string projectDir, - string rootNamespace, - string language, - bool nullable, - string[] args) : DatabaseOperations(reporter, assembly, startupAssembly, projectDir, rootNamespace, language, nullable, args); diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.BaseType.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.BaseType.cs index 75dd606cd19..b118885ba85 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.BaseType.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.BaseType.cs @@ -677,7 +677,7 @@ public void Navigations_on_base_type_should_be_inherited() var specialCustomerType = model.AddEntityType(typeof(SpecialCustomer)); Assert.Equal(new[] { "Orders" }, customerType.GetNavigations().Select(p => p.Name).ToArray()); - Assert.Equal([], specialCustomerType.GetNavigations().Select(p => p.Name).ToArray()); + Assert.Equal(new string[0], specialCustomerType.GetNavigations().Select(p => p.Name).ToArray()); specialCustomerType.BaseType = customerType; diff --git a/test/EFCore.Tests/Storage/ValueConversion/BytesToStringConverterTest.cs b/test/EFCore.Tests/Storage/ValueConversion/BytesToStringConverterTest.cs index a29063dc5ab..c1a2694ba2f 100644 --- a/test/EFCore.Tests/Storage/ValueConversion/BytesToStringConverterTest.cs +++ b/test/EFCore.Tests/Storage/ValueConversion/BytesToStringConverterTest.cs @@ -23,7 +23,7 @@ public void Can_convert_bytes_to_strings() var converter = _bytesToStringConverter.ConvertFromProviderExpression.Compile(); Assert.Equal(new byte[] { 83, 112, 196, 177, 110, 204, 136, 97, 108, 32, 84, 97, 112 }, converter("U3DEsW7MiGFsIFRhcA==")); - Assert.Equal([], converter("")); + Assert.Equal(new byte[0], converter("")); } [ConditionalFact] diff --git a/test/EFCore.Tests/Storage/ValueConversion/StringToBytesConverterTest.cs b/test/EFCore.Tests/Storage/ValueConversion/StringToBytesConverterTest.cs index f2aa07d914c..6842b227c4a 100644 --- a/test/EFCore.Tests/Storage/ValueConversion/StringToBytesConverterTest.cs +++ b/test/EFCore.Tests/Storage/ValueConversion/StringToBytesConverterTest.cs @@ -13,7 +13,7 @@ public void Can_convert_strings_to_UTF8() var converter = _stringToUtf8Converter.ConvertToProviderExpression.Compile(); Assert.Equal(new byte[] { 83, 112, 196, 177, 110, 204, 136, 97, 108, 32, 84, 97, 112 }, converter("Spın̈al Tap")); - Assert.Equal([], converter("")); + Assert.Equal(new byte[0], converter("")); } [ConditionalFact]