diff --git a/NuGet.config b/NuGet.config index bd2ac2630b0..e6046bdda5d 100644 --- a/NuGet.config +++ b/NuGet.config @@ -4,7 +4,7 @@ - + @@ -21,7 +21,7 @@ - + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 7d47952faa3..1dc45f4c790 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,83 +1,83 @@ - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 831d23e56149cd59c40fc00c7feb7c5334bd19c4 + f57e6dc747158ab7ade4e62a75a6750d16b771e8 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 831d23e56149cd59c40fc00c7feb7c5334bd19c4 + f57e6dc747158ab7ade4e62a75a6750d16b771e8 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 831d23e56149cd59c40fc00c7feb7c5334bd19c4 + f57e6dc747158ab7ade4e62a75a6750d16b771e8 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 831d23e56149cd59c40fc00c7feb7c5334bd19c4 + f57e6dc747158ab7ade4e62a75a6750d16b771e8 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 831d23e56149cd59c40fc00c7feb7c5334bd19c4 + f57e6dc747158ab7ade4e62a75a6750d16b771e8 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 831d23e56149cd59c40fc00c7feb7c5334bd19c4 + f57e6dc747158ab7ade4e62a75a6750d16b771e8 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 831d23e56149cd59c40fc00c7feb7c5334bd19c4 + f57e6dc747158ab7ade4e62a75a6750d16b771e8 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 831d23e56149cd59c40fc00c7feb7c5334bd19c4 + f57e6dc747158ab7ade4e62a75a6750d16b771e8 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 831d23e56149cd59c40fc00c7feb7c5334bd19c4 + f57e6dc747158ab7ade4e62a75a6750d16b771e8 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 831d23e56149cd59c40fc00c7feb7c5334bd19c4 + f57e6dc747158ab7ade4e62a75a6750d16b771e8 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 831d23e56149cd59c40fc00c7feb7c5334bd19c4 + f57e6dc747158ab7ade4e62a75a6750d16b771e8 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 831d23e56149cd59c40fc00c7feb7c5334bd19c4 + f57e6dc747158ab7ade4e62a75a6750d16b771e8 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 831d23e56149cd59c40fc00c7feb7c5334bd19c4 + f57e6dc747158ab7ade4e62a75a6750d16b771e8 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 831d23e56149cd59c40fc00c7feb7c5334bd19c4 + f57e6dc747158ab7ade4e62a75a6750d16b771e8 - + https://dev.azure.com/dnceng/internal/_git/dotnet-runtime - 831d23e56149cd59c40fc00c7feb7c5334bd19c4 + f57e6dc747158ab7ade4e62a75a6750d16b771e8 - + https://github.com/dotnet/arcade - bac7e1caea791275b7c3ccb4cb75fd6a04a26618 + f33d9e642f0e68a61312164cd9e0baf4e142a999 - + https://github.com/dotnet/arcade - bac7e1caea791275b7c3ccb4cb75fd6a04a26618 + f33d9e642f0e68a61312164cd9e0baf4e142a999 - + https://github.com/dotnet/arcade - bac7e1caea791275b7c3ccb4cb75fd6a04a26618 + f33d9e642f0e68a61312164cd9e0baf4e142a999 diff --git a/eng/Versions.props b/eng/Versions.props index 8a42bd48a5d..a9458f9340d 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -1,6 +1,6 @@ - 9.0.3 + 9.0.4 rtm @@ -17,24 +17,24 @@ False - 9.0.3 - 9.0.3 - 9.0.3 - 9.0.3 - 9.0.3 - 9.0.3 - 9.0.3 - 9.0.3-servicing.25111.13 - 9.0.3 - 9.0.3 - 9.0.3 - 9.0.3-servicing.25111.13 - 9.0.3 - 9.0.3 - 9.0.3 + 9.0.4 + 9.0.4 + 9.0.4 + 9.0.4 + 9.0.4 + 9.0.4 + 9.0.4 + 9.0.4-servicing.25163.5 + 9.0.4 + 9.0.4 + 9.0.4 + 9.0.4-servicing.25163.5 + 9.0.4 + 9.0.4 + 9.0.4 - 9.0.0-beta.25077.4 + 9.0.0-beta.25161.4 17.8.3 diff --git a/eng/common/core-templates/steps/generate-sbom.yml b/eng/common/core-templates/steps/generate-sbom.yml index d938b60e1bb..56a09009482 100644 --- a/eng/common/core-templates/steps/generate-sbom.yml +++ b/eng/common/core-templates/steps/generate-sbom.yml @@ -38,7 +38,7 @@ steps: PackageName: ${{ parameters.packageName }} BuildDropPath: ${{ parameters.buildDropPath }} PackageVersion: ${{ parameters.packageVersion }} - ManifestDirPath: ${{ parameters.manifestDirPath }} + ManifestDirPath: ${{ parameters.manifestDirPath }}/$(ARTIFACT_NAME) ${{ if ne(parameters.IgnoreDirectories, '') }}: AdditionalComponentDetectorArgs: '--IgnoreDirectories ${{ parameters.IgnoreDirectories }}' diff --git a/eng/common/generate-sbom-prep.ps1 b/eng/common/generate-sbom-prep.ps1 index 3e5c1c74a1c..a0c7d792a76 100644 --- a/eng/common/generate-sbom-prep.ps1 +++ b/eng/common/generate-sbom-prep.ps1 @@ -4,18 +4,26 @@ Param( . $PSScriptRoot\pipeline-logging-functions.ps1 +# Normally - we'd listen to the manifest path given, but 1ES templates will overwrite if this level gets uploaded directly +# with their own overwriting ours. So we create it as a sub directory of the requested manifest path. +$ArtifactName = "${env:SYSTEM_STAGENAME}_${env:AGENT_JOBNAME}_SBOM" +$SafeArtifactName = $ArtifactName -replace '["/:<>\\|?@*"() ]', '_' +$SbomGenerationDir = Join-Path $ManifestDirPath $SafeArtifactName + +Write-Host "Artifact name before : $ArtifactName" +Write-Host "Artifact name after : $SafeArtifactName" + Write-Host "Creating dir $ManifestDirPath" + # create directory for sbom manifest to be placed -if (!(Test-Path -path $ManifestDirPath)) +if (!(Test-Path -path $SbomGenerationDir)) { - New-Item -ItemType Directory -path $ManifestDirPath - Write-Host "Successfully created directory $ManifestDirPath" + New-Item -ItemType Directory -path $SbomGenerationDir + Write-Host "Successfully created directory $SbomGenerationDir" } else{ Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder." } Write-Host "Updating artifact name" -$artifact_name = "${env:SYSTEM_STAGENAME}_${env:AGENT_JOBNAME}_SBOM" -replace '["/:<>\\|?@*"() ]', '_' -Write-Host "Artifact name $artifact_name" -Write-Host "##vso[task.setvariable variable=ARTIFACT_NAME]$artifact_name" +Write-Host "##vso[task.setvariable variable=ARTIFACT_NAME]$SafeArtifactName" diff --git a/eng/common/generate-sbom-prep.sh b/eng/common/generate-sbom-prep.sh index d5c76dc827b..b8ecca72bbf 100644 --- a/eng/common/generate-sbom-prep.sh +++ b/eng/common/generate-sbom-prep.sh @@ -14,19 +14,24 @@ done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . $scriptroot/pipeline-logging-functions.sh + +# replace all special characters with _, some builds use special characters like : in Agent.Jobname, that is not a permissible name while uploading artifacts. +artifact_name=$SYSTEM_STAGENAME"_"$AGENT_JOBNAME"_SBOM" +safe_artifact_name="${artifact_name//["/:<>\\|?@*$" ]/_}" manifest_dir=$1 -if [ ! -d "$manifest_dir" ] ; then - mkdir -p "$manifest_dir" - echo "Sbom directory created." $manifest_dir +# Normally - we'd listen to the manifest path given, but 1ES templates will overwrite if this level gets uploaded directly +# with their own overwriting ours. So we create it as a sub directory of the requested manifest path. +sbom_generation_dir="$manifest_dir/$safe_artifact_name" + +if [ ! -d "$sbom_generation_dir" ] ; then + mkdir -p "$sbom_generation_dir" + echo "Sbom directory created." $sbom_generation_dir else Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder." fi -artifact_name=$SYSTEM_STAGENAME"_"$AGENT_JOBNAME"_SBOM" echo "Artifact name before : "$artifact_name -# replace all special characters with _, some builds use special characters like : in Agent.Jobname, that is not a permissible name while uploading artifacts. -safe_artifact_name="${artifact_name//["/:<>\\|?@*$" ]/_}" echo "Artifact name after : "$safe_artifact_name export ARTIFACT_NAME=$safe_artifact_name echo "##vso[task.setvariable variable=ARTIFACT_NAME]$safe_artifact_name" diff --git a/eng/common/templates-official/job/job.yml b/eng/common/templates-official/job/job.yml index 605692d2fb7..817555505aa 100644 --- a/eng/common/templates-official/job/job.yml +++ b/eng/common/templates-official/job/job.yml @@ -16,6 +16,7 @@ jobs: parameters: PackageVersion: ${{ parameters.packageVersion }} BuildDropPath: ${{ parameters.buildDropPath }} + ManifestDirPath: $(Build.ArtifactStagingDirectory)/sbom publishArtifacts: false # publish artifacts diff --git a/eng/common/templates-official/post-build/common-variables.yml b/eng/common/templates-official/post-build/common-variables.yml index fbeca86bd4c..c32fc49233f 100644 --- a/eng/common/templates-official/post-build/common-variables.yml +++ b/eng/common/templates-official/post-build/common-variables.yml @@ -5,4 +5,4 @@ variables: is1ESPipeline: true ${{ each parameter in parameters }}: - ${{ parameter.key }}: ${{ parameter.value }} + ${{ parameter.key }}: ${{ parameter.value }} \ No newline at end of file diff --git a/global.json b/global.json index cc2268259a4..61faa20a7a5 100644 --- a/global.json +++ b/global.json @@ -1,11 +1,11 @@ { "sdk": { - "version": "9.0.102", + "version": "9.0.104", "allowPrerelease": true, "rollForward": "latestMajor" }, "tools": { - "dotnet": "9.0.102", + "dotnet": "9.0.104", "runtimes": { "dotnet": [ "$(MicrosoftNETCoreBrowserDebugHostTransportVersion)" @@ -13,7 +13,7 @@ } }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25077.4", - "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.25077.4" + "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25161.4", + "Microsoft.DotNet.Helix.Sdk": "9.0.0-beta.25161.4" } } 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/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs index 3d70a71491f..eb02e70da83 100644 --- a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs +++ b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs @@ -109,6 +109,12 @@ public class ExpressionTreeFuncletizer : ExpressionVisitor private static readonly bool UseOldBehavior35111 = AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35111", out var enabled35111) && enabled35111; + private static readonly bool UseOldBehavior35656 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35656", out var enabled35656) && enabled35656; + + private static readonly bool UseOldBehavior35100 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35100", out var enabled35100) && enabled35100; + private static readonly MethodInfo ReadOnlyCollectionIndexerGetter = typeof(ReadOnlyCollection).GetProperties() .Single(p => p.GetIndexParameters() is { Length: 1 } indexParameters && indexParameters[0].ParameterType == typeof(int)).GetMethod!; @@ -971,6 +977,51 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall) } } + // .NET 10 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans"). + // Unfortunately, the LINQ interpreter does not support ref structs, so we rewrite e.g. MemoryExtensions.Contains to + // Enumerable.Contains here. See https://github.com/dotnet/runtime/issues/109757. + if (method.DeclaringType == typeof(MemoryExtensions) && !UseOldBehavior35100) + { + switch (method.Name) + { + case nameof(MemoryExtensions.Contains) + when methodCall.Arguments is [var arg0, var arg1] && TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0): + { + return Visit( + Call( + EnumerableMethods.Contains.MakeGenericMethod(methodCall.Method.GetGenericArguments()[0]), + unwrappedArg0, arg1)); + } + + case nameof(MemoryExtensions.SequenceEqual) + when methodCall.Arguments is [var arg0, var arg1] + && TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0) + && TryUnwrapSpanImplicitCast(arg1, out var unwrappedArg1): + return Visit( + Call( + EnumerableMethods.SequenceEqual.MakeGenericMethod(methodCall.Method.GetGenericArguments()[0]), + unwrappedArg0, unwrappedArg1)); + } + + static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result) + { + if (expression is MethodCallExpression + { + Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType }, + Arguments: [var unwrapped] + } + && implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition + && (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>))) + { + result = unwrapped; + return true; + } + + result = null; + return false; + } + } + // Regular/arbitrary method handling from here on // First, visit the object and all arguments, saving states as well @@ -2132,8 +2183,12 @@ static Expression RemoveConvert(Expression expression) } } - private static Expression ConvertIfNeeded(Expression expression, Type type) - => expression.Type == type ? expression : Convert(expression, type); + private Expression ConvertIfNeeded(Expression expression, Type type) + => expression.Type == type + ? expression + : UseOldBehavior35656 + ? Convert(expression, type) + : Visit(Convert(expression, type)); private bool IsGenerallyEvaluatable(Expression expression) => _evaluatableExpressionFilter.IsEvaluatableExpression(expression, _model) diff --git a/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs b/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs index 427e8c56e94..5d315daa6eb 100644 --- a/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs +++ b/src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs @@ -23,6 +23,9 @@ namespace Microsoft.Data.Sqlite /// Async Limitations public partial class SqliteConnection : DbConnection { + private static readonly bool UseOldBehavior35715 = + AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35715", out var enabled35715) && enabled35715; + internal const string MainDatabaseName = "main"; private const int SQLITE_WIN32_DATA_DIRECTORY_TYPE = 1; @@ -48,6 +51,8 @@ public partial class SqliteConnection : DbConnection private static readonly StateChangeEventArgs _fromClosedToOpenEventArgs = new StateChangeEventArgs(ConnectionState.Closed, ConnectionState.Open); private static readonly StateChangeEventArgs _fromOpenToClosedEventArgs = new StateChangeEventArgs(ConnectionState.Open, ConnectionState.Closed); + private static string[]? NativeDllSearchDirectories; + static SqliteConnection() { Type.GetType("SQLitePCL.Batteries_V2, SQLitePCLRaw.batteries_v2") @@ -624,11 +629,82 @@ public virtual void LoadExtension(string file, string? proc = null) private void LoadExtensionCore(string file, string? proc) { - var rc = sqlite3_load_extension(Handle, utf8z.FromString(file), utf8z.FromString(proc), out var errmsg); - if (rc != SQLITE_OK) + if (UseOldBehavior35715) + { + var rc = sqlite3_load_extension(Handle, utf8z.FromString(file), utf8z.FromString(proc), out var errmsg); + if (rc != SQLITE_OK) + { + throw new SqliteException(Resources.SqliteNativeError(rc, errmsg.utf8_to_string()), rc, rc); + } + } + else + { + SqliteException? firstException = null; + foreach (var path in GetLoadExtensionPaths(file)) + { + var rc = sqlite3_load_extension(Handle, utf8z.FromString(path), utf8z.FromString(proc), out var errmsg); + if (rc == SQLITE_OK) + { + return; + } + + if (firstException == null) + { + // We store the first exception so that error message looks more obvious if file appears in there + firstException = new SqliteException(Resources.SqliteNativeError(rc, errmsg.utf8_to_string()), rc, rc); + } + } + + if (firstException != null) + { + throw firstException; + } + } + } + + private static IEnumerable GetLoadExtensionPaths(string file) + { + // we always try original input first + yield return file; + + string? dirName = Path.GetDirectoryName(file); + + // we don't try to guess directories for user, if they pass a path either absolute or relative - they're on their own + if (!string.IsNullOrEmpty(dirName)) { - throw new SqliteException(Resources.SqliteNativeError(rc, errmsg.utf8_to_string()), rc, rc); + yield break; } + + bool shouldTryAddingLibPrefix = !file.StartsWith("lib", StringComparison.Ordinal) && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + if (shouldTryAddingLibPrefix) + { + yield return $"lib{file}"; + } + + NativeDllSearchDirectories ??= GetNativeDllSearchDirectories(); + + foreach (string dir in NativeDllSearchDirectories) + { + yield return Path.Combine(dir, file); + + if (shouldTryAddingLibPrefix) + { + yield return Path.Combine(dir, $"lib{file}"); + } + } + } + + private static string[] GetNativeDllSearchDirectories() + { + string? searchDirs = AppContext.GetData("NATIVE_DLL_SEARCH_DIRECTORIES") as string; + + if (string.IsNullOrEmpty(searchDirs)) + { + return []; + } + + return searchDirs!.Split([ Path.PathSeparator ], StringSplitOptions.RemoveEmptyEntries); } /// 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.Specification.Tests/Query/GearsOfWarQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs index 93ffe9f0cfb..b65b15d2d0c 100644 --- a/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/GearsOfWarQueryTestBase.cs @@ -6603,6 +6603,23 @@ public virtual Task Enum_array_contains(bool async) .Where(w => w.SynergyWith != null && types.Contains(w.SynergyWith.AmmunitionType))); } + [ConditionalTheory] // #35656 + [MemberData(nameof(IsAsyncData))] + public virtual Task Coalesce_with_non_root_evaluatable_Convert(bool async) + { + MilitaryRank? rank = MilitaryRank.Private; + + // The coalesce is simplified away in the funcletizer (since rank is non-null), but a Convert node is added + // to convert from MilitaryRank? (the type of rank) to the type of the coalesce expression (non-nullable + // MilitaryRank). + // This resulting Convert node isn't evaluatable as root (enum convert), and so the NotEvaluatableAsRootHandler + // is invoked. + return AssertQuery( + async, + // ReSharper disable once ConstantNullCoalescingCondition + ss => ss.Set().Where(g => (rank ?? g.Rank) == g.Rank)); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public virtual async Task Client_eval_followed_by_aggregate_operation(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs index cae4816b1ae..9429aab66b8 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs @@ -8162,6 +8162,20 @@ FROM OPENJSON(@__types_0_without_nulls) AS [t] """); } + public override async Task Coalesce_with_non_root_evaluatable_Convert(bool async) + { + await base.Coalesce_with_non_root_evaluatable_Convert(async); + + AssertSql( + """ +@__rank_0='1' (Nullable = true) + +SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] +FROM [Gears] AS [g] +WHERE @__rank_0 = [g].[Rank] +"""); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public async Task DataLength_function_for_string_parameter(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs index ee461d32d9c..c54d2675c03 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs @@ -10883,6 +10883,26 @@ FROM OPENJSON(@__types_0_without_nulls) AS [t] """); } + public override async Task Coalesce_with_non_root_evaluatable_Convert(bool async) + { + await base.Coalesce_with_non_root_evaluatable_Convert(async); + + AssertSql( + """ +@__rank_0='1' (Nullable = true) + +SELECT [u].[Nickname], [u].[SquadId], [u].[AssignedCityName], [u].[CityOfBirthName], [u].[FullName], [u].[HasSoulPatch], [u].[LeaderNickname], [u].[LeaderSquadId], [u].[Rank], [u].[Discriminator] +FROM ( + SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], N'Gear' AS [Discriminator] + FROM [Gears] AS [g] + UNION ALL + SELECT [o].[Nickname], [o].[SquadId], [o].[AssignedCityName], [o].[CityOfBirthName], [o].[FullName], [o].[HasSoulPatch], [o].[LeaderNickname], [o].[LeaderSquadId], [o].[Rank], N'Officer' AS [Discriminator] + FROM [Officers] AS [o] +) AS [u] +WHERE @__rank_0 = [u].[Rank] +"""); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public async Task DataLength_function_for_string_parameter(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs index 076e6c19d97..d5fa8b8562d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs @@ -9220,6 +9220,23 @@ FROM OPENJSON(@__types_0_without_nulls) AS [t] """); } + public override async Task Coalesce_with_non_root_evaluatable_Convert(bool async) + { + await base.Coalesce_with_non_root_evaluatable_Convert(async); + + AssertSql( + """ +@__rank_0='1' (Nullable = true) + +SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], CASE + WHEN [o].[Nickname] IS NOT NULL THEN N'Officer' +END AS [Discriminator] +FROM [Gears] AS [g] +LEFT JOIN [Officers] AS [o] ON [g].[Nickname] = [o].[Nickname] AND [g].[SquadId] = [o].[SquadId] +WHERE @__rank_0 = [g].[Rank] +"""); + } + [ConditionalTheory] [MemberData(nameof(IsAsyncData))] public async Task DataLength_function_for_string_parameter(bool async) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs index ca649d80e12..5d82e72fff3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs @@ -6987,6 +6987,20 @@ SELECT TOP(1) ~CAST(([g].[Rank] & 2) ^ 2 AS bit) AS [BitwiseTrue], ~CAST(([g].[R """); } + public override async Task Coalesce_with_non_root_evaluatable_Convert(bool async) + { + await base.Coalesce_with_non_root_evaluatable_Convert(async); + + AssertSql( + """ +@__rank_0='1' (Nullable = true) + +SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[PeriodEnd], [g].[PeriodStart], [g].[Rank] +FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g] +WHERE @__rank_0 = [g].[Rank] +"""); + } + public override async Task Comparison_with_value_converted_subclass(bool async) { await base.Comparison_with_value_converted_subclass(async); diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs index 6e69f0b1ce3..77293ffbe86 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs @@ -6383,6 +6383,20 @@ LIMIT @__p_0 """); } + public override async Task Coalesce_with_non_root_evaluatable_Convert(bool async) + { + await base.Coalesce_with_non_root_evaluatable_Convert(async); + + AssertSql( + """ +@__rank_0='1' (Nullable = true) + +SELECT "g"."Nickname", "g"."SquadId", "g"."AssignedCityName", "g"."CityOfBirthName", "g"."Discriminator", "g"."FullName", "g"."HasSoulPatch", "g"."LeaderNickname", "g"."LeaderSquadId", "g"."Rank" +FROM "Gears" AS "g" +WHERE @__rank_0 = "g"."Rank" +"""); + } + public override async Task Correlated_collections_with_Take(bool async) { await base.Correlated_collections_with_Take(async); 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]