Skip to content

Commit 5f9d0dc

Browse files
authored
Merge branch 'release/8.0-staging' => 'release/8.0' (#35742)
2 parents 51e50e9 + eeab918 commit 5f9d0dc

File tree

2 files changed

+132
-5
lines changed

2 files changed

+132
-5
lines changed

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

+53-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public class ParameterExtractingExpressionVisitor : ExpressionVisitor
3131
private static readonly bool UseOldBehavior31552 =
3232
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue31552", out var enabled31552) && enabled31552;
3333

34+
private static readonly bool UseOldBehavior35100 =
35+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35100", out var enabled35100) && enabled35100;
36+
3437
/// <summary>
3538
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
3639
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -181,9 +184,11 @@ protected override Expression VisitConditional(ConditionalExpression conditional
181184
/// </summary>
182185
protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
183186
{
187+
var method = methodCallExpression.Method;
188+
184189
if (!UseOldBehavior31552
185-
&& methodCallExpression.Method.DeclaringType == typeof(EF)
186-
&& methodCallExpression.Method.Name == nameof(EF.Constant))
190+
&& method.DeclaringType == typeof(EF)
191+
&& method.Name == nameof(EF.Constant))
187192
{
188193
// If this is a call to EF.Constant(), then examine its operand. If the operand isn't evaluatable (i.e. contains a reference
189194
// to a database table), throw immediately.
@@ -197,6 +202,52 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
197202
return Evaluate(operand, generateParameter: false);
198203
}
199204

205+
// .NET 10 made changes to overload resolution to prefer Span-based overloads when those exist ("first-class spans").
206+
// Unfortunately, the LINQ interpreter does not support ref structs, so we rewrite e.g. MemoryExtensions.Contains to
207+
// Enumerable.Contains here. See https://github.com/dotnet/runtime/issues/109757.
208+
if (method.DeclaringType == typeof(MemoryExtensions) && !UseOldBehavior35100)
209+
{
210+
switch (method.Name)
211+
{
212+
case nameof(MemoryExtensions.Contains)
213+
when methodCallExpression.Arguments is [var arg0, var arg1] &&
214+
TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0):
215+
{
216+
return Visit(
217+
Expression.Call(
218+
EnumerableMethods.Contains.MakeGenericMethod(method.GetGenericArguments()[0]),
219+
unwrappedArg0, arg1));
220+
}
221+
222+
case nameof(MemoryExtensions.SequenceEqual)
223+
when methodCallExpression.Arguments is [var arg0, var arg1]
224+
&& TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0)
225+
&& TryUnwrapSpanImplicitCast(arg1, out var unwrappedArg1):
226+
return Visit(
227+
Expression.Call(
228+
EnumerableMethods.SequenceEqual.MakeGenericMethod(method.GetGenericArguments()[0]),
229+
unwrappedArg0, unwrappedArg1));
230+
}
231+
232+
static bool TryUnwrapSpanImplicitCast(Expression expression, [NotNullWhen(true)] out Expression? result)
233+
{
234+
if (expression is MethodCallExpression
235+
{
236+
Method: { Name: "op_Implicit", DeclaringType: { IsGenericType: true } implicitCastDeclaringType },
237+
Arguments: [var unwrapped]
238+
}
239+
&& implicitCastDeclaringType.GetGenericTypeDefinition() is var genericTypeDefinition
240+
&& (genericTypeDefinition == typeof(Span<>) || genericTypeDefinition == typeof(ReadOnlySpan<>)))
241+
{
242+
result = unwrapped;
243+
return true;
244+
}
245+
246+
result = null;
247+
return false;
248+
}
249+
}
250+
200251
return base.VisitMethodCall(methodCallExpression);
201252
}
202253

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")
@@ -626,11 +631,82 @@ public virtual void LoadExtension(string file, string? proc = null)
626631

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

636712
/// <summary>

0 commit comments

Comments
 (0)