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]