Skip to content

Commit 873d1a5

Browse files
authored
Merge branch 'release/9.0-staging' => 'release/9.0' (#35743)
2 parents a43de14 + 67f52d8 commit 873d1a5

File tree

18 files changed

+335
-82
lines changed

18 files changed

+335
-82
lines changed

Diff for: src/EFCore.Design/Design/Internal/AppServiceProviderFactory.cs

+26
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,30 @@ private IServiceProvider CreateEmptyServiceProvider()
8989

9090
return new ServiceCollection().BuildServiceProvider();
9191
}
92+
93+
/// <summary>
94+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
95+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
96+
/// any release. You should only use it directly in your code with extreme caution and knowing that
97+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
98+
/// </summary>
99+
public static void SetEnvironment(IOperationReporter reporter)
100+
{
101+
var aspnetCoreEnvironment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
102+
var dotnetEnvironment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");
103+
var environment = aspnetCoreEnvironment
104+
?? dotnetEnvironment
105+
?? "Development";
106+
if (aspnetCoreEnvironment == null)
107+
{
108+
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", environment);
109+
}
110+
111+
if (dotnetEnvironment == null)
112+
{
113+
Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", environment);
114+
}
115+
116+
reporter.WriteVerbose(DesignStrings.UsingEnvironment(environment));
117+
}
92118
}

Diff for: src/EFCore.Design/Design/Internal/DatabaseOperations.cs

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Design.Internal;
1111
/// </summary>
1212
public class DatabaseOperations
1313
{
14+
private readonly IOperationReporter _reporter;
1415
private readonly string _projectDir;
1516
private readonly string? _rootNamespace;
1617
private readonly string? _language;
@@ -34,6 +35,7 @@ public DatabaseOperations(
3435
bool nullable,
3536
string[]? args)
3637
{
38+
_reporter = reporter;
3739
_projectDir = projectDir;
3840
_rootNamespace = rootNamespace;
3941
_language = language;
@@ -73,6 +75,7 @@ public virtual SavedModelFiles ScaffoldContext(
7375
? Path.GetFullPath(Path.Combine(_projectDir, outputContextDir))
7476
: outputDir;
7577

78+
AppServiceProviderFactory.SetEnvironment(_reporter);
7679
var services = _servicesBuilder.Build(provider);
7780
using var scope = services.CreateScope();
7881

Diff for: src/EFCore.Design/Design/Internal/DbContextOperations.cs

+1-16
Original file line numberDiff line numberDiff line change
@@ -503,22 +503,7 @@ private IDictionary<Type, Func<DbContext>> FindContextTypes(string? name = null,
503503
{
504504
_reporter.WriteVerbose(DesignStrings.FindingContexts);
505505

506-
var aspnetCoreEnvironment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
507-
var dotnetEnvironment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");
508-
var environment = aspnetCoreEnvironment
509-
?? dotnetEnvironment
510-
?? "Development";
511-
if (aspnetCoreEnvironment == null)
512-
{
513-
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", environment);
514-
}
515-
516-
if (dotnetEnvironment == null)
517-
{
518-
Environment.SetEnvironmentVariable("DOTNET_ENVIRONMENT", environment);
519-
}
520-
521-
_reporter.WriteVerbose(DesignStrings.UsingEnvironment(environment));
506+
AppServiceProviderFactory.SetEnvironment(_reporter);
522507

523508
var contexts = new Dictionary<Type, Func<DbContext>?>();
524509

Diff for: src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs

+57-2
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ public class ExpressionTreeFuncletizer : ExpressionVisitor
109109
private static readonly bool UseOldBehavior35111 =
110110
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35111", out var enabled35111) && enabled35111;
111111

112+
private static readonly bool UseOldBehavior35656 =
113+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35656", out var enabled35656) && enabled35656;
114+
115+
private static readonly bool UseOldBehavior35100 =
116+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35100", out var enabled35100) && enabled35100;
117+
112118
private static readonly MethodInfo ReadOnlyCollectionIndexerGetter = typeof(ReadOnlyCollection<Expression>).GetProperties()
113119
.Single(p => p.GetIndexParameters() is { Length: 1 } indexParameters && indexParameters[0].ParameterType == typeof(int)).GetMethod!;
114120

@@ -971,6 +977,51 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCall)
971977
}
972978
}
973979

980+
// .NET 10 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans").
981+
// Unfortunately, the LINQ interpreter does not support ref structs, so we rewrite e.g. MemoryExtensions.Contains to
982+
// Enumerable.Contains here. See https://github.com/dotnet/runtime/issues/109757.
983+
if (method.DeclaringType == typeof(MemoryExtensions) && !UseOldBehavior35100)
984+
{
985+
switch (method.Name)
986+
{
987+
case nameof(MemoryExtensions.Contains)
988+
when methodCall.Arguments is [var arg0, var arg1] && TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0):
989+
{
990+
return Visit(
991+
Call(
992+
EnumerableMethods.Contains.MakeGenericMethod(methodCall.Method.GetGenericArguments()[0]),
993+
unwrappedArg0, arg1));
994+
}
995+
996+
case nameof(MemoryExtensions.SequenceEqual)
997+
when methodCall.Arguments is [var arg0, var arg1]
998+
&& TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0)
999+
&& TryUnwrapSpanImplicitCast(arg1, out var unwrappedArg1):
1000+
return Visit(
1001+
Call(
1002+
EnumerableMethods.SequenceEqual.MakeGenericMethod(methodCall.Method.GetGenericArguments()[0]),
1003+
unwrappedArg0, unwrappedArg1));
1004+
}
1005+
1006+
static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result)
1007+
{
1008+
if (expression is MethodCallExpression
1009+
{
1010+
Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType },
1011+
Arguments: [var unwrapped]
1012+
}
1013+
&& implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition
1014+
&& (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)))
1015+
{
1016+
result = unwrapped;
1017+
return true;
1018+
}
1019+
1020+
result = null;
1021+
return false;
1022+
}
1023+
}
1024+
9741025
// Regular/arbitrary method handling from here on
9751026

9761027
// First, visit the object and all arguments, saving states as well
@@ -2132,8 +2183,12 @@ static Expression RemoveConvert(Expression expression)
21322183
}
21332184
}
21342185

2135-
private static Expression ConvertIfNeeded(Expression expression, Type type)
2136-
=> expression.Type == type ? expression : Convert(expression, type);
2186+
private Expression ConvertIfNeeded(Expression expression, Type type)
2187+
=> expression.Type == type
2188+
? expression
2189+
: UseOldBehavior35656
2190+
? Convert(expression, type)
2191+
: Visit(Convert(expression, type));
21372192

21382193
private bool IsGenerallyEvaluatable(Expression expression)
21392194
=> _evaluatableExpressionFilter.IsEvaluatableExpression(expression, _model)

Diff for: src/Microsoft.Data.Sqlite.Core/SqliteConnection.cs

+79-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ namespace Microsoft.Data.Sqlite
2323
/// <seealso href="https://docs.microsoft.com/dotnet/standard/data/sqlite/async">Async Limitations</seealso>
2424
public partial class SqliteConnection : DbConnection
2525
{
26+
private static readonly bool UseOldBehavior35715 =
27+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35715", out var enabled35715) && enabled35715;
28+
2629
internal const string MainDatabaseName = "main";
2730

2831
private const int SQLITE_WIN32_DATA_DIRECTORY_TYPE = 1;
@@ -48,6 +51,8 @@ public partial class SqliteConnection : DbConnection
4851
private static readonly StateChangeEventArgs _fromClosedToOpenEventArgs = new StateChangeEventArgs(ConnectionState.Closed, ConnectionState.Open);
4952
private static readonly StateChangeEventArgs _fromOpenToClosedEventArgs = new StateChangeEventArgs(ConnectionState.Open, ConnectionState.Closed);
5053

54+
private static string[]? NativeDllSearchDirectories;
55+
5156
static SqliteConnection()
5257
{
5358
Type.GetType("SQLitePCL.Batteries_V2, SQLitePCLRaw.batteries_v2")
@@ -624,11 +629,82 @@ public virtual void LoadExtension(string file, string? proc = null)
624629

625630
private void LoadExtensionCore(string file, string? proc)
626631
{
627-
var rc = sqlite3_load_extension(Handle, utf8z.FromString(file), utf8z.FromString(proc), out var errmsg);
628-
if (rc != SQLITE_OK)
632+
if (UseOldBehavior35715)
633+
{
634+
var rc = sqlite3_load_extension(Handle, utf8z.FromString(file), utf8z.FromString(proc), out var errmsg);
635+
if (rc != SQLITE_OK)
636+
{
637+
throw new SqliteException(Resources.SqliteNativeError(rc, errmsg.utf8_to_string()), rc, rc);
638+
}
639+
}
640+
else
641+
{
642+
SqliteException? firstException = null;
643+
foreach (var path in GetLoadExtensionPaths(file))
644+
{
645+
var rc = sqlite3_load_extension(Handle, utf8z.FromString(path), utf8z.FromString(proc), out var errmsg);
646+
if (rc == SQLITE_OK)
647+
{
648+
return;
649+
}
650+
651+
if (firstException == null)
652+
{
653+
// We store the first exception so that error message looks more obvious if file appears in there
654+
firstException = new SqliteException(Resources.SqliteNativeError(rc, errmsg.utf8_to_string()), rc, rc);
655+
}
656+
}
657+
658+
if (firstException != null)
659+
{
660+
throw firstException;
661+
}
662+
}
663+
}
664+
665+
private static IEnumerable<string> GetLoadExtensionPaths(string file)
666+
{
667+
// we always try original input first
668+
yield return file;
669+
670+
string? dirName = Path.GetDirectoryName(file);
671+
672+
// we don't try to guess directories for user, if they pass a path either absolute or relative - they're on their own
673+
if (!string.IsNullOrEmpty(dirName))
629674
{
630-
throw new SqliteException(Resources.SqliteNativeError(rc, errmsg.utf8_to_string()), rc, rc);
675+
yield break;
631676
}
677+
678+
bool shouldTryAddingLibPrefix = !file.StartsWith("lib", StringComparison.Ordinal) && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
679+
680+
if (shouldTryAddingLibPrefix)
681+
{
682+
yield return $"lib{file}";
683+
}
684+
685+
NativeDllSearchDirectories ??= GetNativeDllSearchDirectories();
686+
687+
foreach (string dir in NativeDllSearchDirectories)
688+
{
689+
yield return Path.Combine(dir, file);
690+
691+
if (shouldTryAddingLibPrefix)
692+
{
693+
yield return Path.Combine(dir, $"lib{file}");
694+
}
695+
}
696+
}
697+
698+
private static string[] GetNativeDllSearchDirectories()
699+
{
700+
string? searchDirs = AppContext.GetData("NATIVE_DLL_SEARCH_DIRECTORIES") as string;
701+
702+
if (string.IsNullOrEmpty(searchDirs))
703+
{
704+
return [];
705+
}
706+
707+
return searchDirs!.Split([ Path.PathSeparator ], StringSplitOptions.RemoveEmptyEntries);
632708
}
633709

634710
/// <summary>

Diff for: test/EFCore.Design.Tests/Design/Internal/DatabaseOperationsTest.cs

+68-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Microsoft.EntityFrameworkCore.Internal;
5+
46
namespace Microsoft.EntityFrameworkCore.Design.Internal;
57

68
public class DatabaseOperationsTest
@@ -10,16 +12,80 @@ public void Can_pass_null_args()
1012
{
1113
// Even though newer versions of the tools will pass an empty array
1214
// older versions of the tools can pass null args.
15+
CreateOperations(null);
16+
}
17+
18+
[ConditionalFact]
19+
public void ScaffoldContext_throws_exceptions_for_invalid_context_name()
20+
{
21+
ValidateContextNameInReverseEngineerGenerator("Invalid!CSharp*Class&Name");
22+
ValidateContextNameInReverseEngineerGenerator("1CSharpClassNameCannotStartWithNumber");
23+
ValidateContextNameInReverseEngineerGenerator("volatile");
24+
}
25+
26+
private void ValidateContextNameInReverseEngineerGenerator(string contextName)
27+
{
28+
var operations = CreateOperations([]);
29+
30+
Assert.Equal(
31+
DesignStrings.ContextClassNotValidCSharpIdentifier(contextName),
32+
Assert.Throws<ArgumentException>(
33+
() => operations.ScaffoldContext(
34+
"Microsoft.EntityFrameworkCore.SqlServer",
35+
"connectionstring",
36+
"",
37+
"",
38+
dbContextClassName: contextName,
39+
null,
40+
null,
41+
"FakeNamespace",
42+
contextNamespace: null,
43+
useDataAnnotations: false,
44+
overwriteFiles: true,
45+
useDatabaseNames: false,
46+
suppressOnConfiguring: true,
47+
noPluralize: false))
48+
.Message);
49+
}
50+
51+
[ConditionalFact]
52+
[SqlServerConfiguredCondition]
53+
public void ScaffoldContext_sets_environment()
54+
{
55+
var operations = CreateOperations([]);
56+
operations.ScaffoldContext(
57+
"Microsoft.EntityFrameworkCore.SqlServer",
58+
TestEnvironment.DefaultConnection,
59+
"",
60+
"",
61+
dbContextClassName: nameof(TestContext),
62+
schemas: ["Empty"],
63+
null,
64+
null,
65+
contextNamespace: null,
66+
useDataAnnotations: false,
67+
overwriteFiles: true,
68+
useDatabaseNames: false,
69+
suppressOnConfiguring: true,
70+
noPluralize: false);
71+
72+
Assert.Equal("Development", Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT"));
73+
Assert.Equal("Development", Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT"));
74+
}
75+
76+
private static DatabaseOperations CreateOperations(string[] args)
77+
{
1378
var assembly = MockAssembly.Create(typeof(TestContext));
14-
_ = new TestDatabaseOperations(
79+
var operations = new DatabaseOperations(
1580
new TestOperationReporter(),
1681
assembly,
1782
assembly,
1883
"projectDir",
1984
"RootNamespace",
2085
"C#",
2186
nullable: false,
22-
args: null);
87+
args: args);
88+
return operations;
2389
}
2490

2591
public class TestContext : DbContext;

Diff for: test/EFCore.Design.Tests/Migrations/Design/CSharpMigrationOperationGeneratorTest.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2453,7 +2453,7 @@ public void InsertDataOperation_required_empty_array()
24532453
Assert.Single(o.Columns);
24542454
Assert.Equal(1, o.Values.GetLength(0));
24552455
Assert.Equal(1, o.Values.GetLength(1));
2456-
Assert.Equal([], (string[])o.Values[0, 0]);
2456+
Assert.Equal(new string[0], (string[])o.Values[0, 0]);
24572457
});
24582458

24592459
[ConditionalFact]
@@ -2478,7 +2478,7 @@ public void InsertDataOperation_required_empty_array_composite()
24782478
Assert.Equal(1, o.Values.GetLength(0));
24792479
Assert.Equal(3, o.Values.GetLength(1));
24802480
Assert.Null(o.Values[0, 1]);
2481-
Assert.Equal([], (string[])o.Values[0, 2]);
2481+
Assert.Equal(new string[0], (string[])o.Values[0, 2]);
24822482
});
24832483

24842484
[ConditionalFact]

0 commit comments

Comments
 (0)