Skip to content

Commit 394d51c

Browse files
committed
Handle .NET 10 MemoryExtensions.Contains overload with comparer
Fixes #37176
1 parent cf59185 commit 394d51c

2 files changed

Lines changed: 50 additions & 2 deletions

File tree

src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ public class ParameterExtractingExpressionVisitor : ExpressionVisitor
3434
private static readonly bool UseOldBehavior35100 =
3535
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue35100", out var enabled35100) && enabled35100;
3636

37+
private static readonly bool UseOldBehavior37176 =
38+
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue37176", out var enabled37176) && enabled37176;
39+
3740
/// <summary>
3841
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
3942
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -210,15 +213,32 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
210213
switch (method.Name)
211214
{
212215
case nameof(MemoryExtensions.Contains)
213-
when methodCallExpression.Arguments is [var arg0, var arg1] &&
214-
TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0):
216+
when UseOldBehavior37176
217+
&& methodCallExpression.Arguments is [var arg0, var arg1]
218+
&& TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0):
215219
{
216220
return Visit(
217221
Expression.Call(
218222
EnumerableMethods.Contains.MakeGenericMethod(method.GetGenericArguments()[0]),
219223
unwrappedArg0, arg1));
220224
}
221225

226+
// In .NET 10, MemoryExtensions.Contains has an overload that accepts a third, optional comparer, in addition to the older
227+
// overload that accepts two parameters only.
228+
case nameof(MemoryExtensions.Contains)
229+
when !UseOldBehavior37176
230+
&& methodCallExpression.Arguments is [var spanArg, var valueArg, ..]
231+
&& (methodCallExpression.Arguments.Count is 2
232+
|| methodCallExpression.Arguments.Count is 3
233+
&& methodCallExpression.Arguments[2] is ConstantExpression { Value: null })
234+
&& TryUnwrapSpanImplicitCast(spanArg, out var unwrappedSpanArg):
235+
{
236+
return Visit(
237+
Expression.Call(
238+
EnumerableMethods.Contains.MakeGenericMethod(method.GetGenericArguments()[0]),
239+
unwrappedSpanArg, valueArg));
240+
}
241+
222242
case nameof(MemoryExtensions.SequenceEqual)
223243
when methodCallExpression.Arguments is [var arg0, var arg1]
224244
&& TryUnwrapSpanImplicitCast(arg0, out var unwrappedArg0)

test/EFCore.Specification.Tests/Query/PrimitiveCollectionsQueryTestBase.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,34 @@ public virtual Task Column_collection_of_bools_Contains(bool async)
371371
async,
372372
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => c.Bools.Contains(true)));
373373

374+
// C# 14 first-class spans caused MemoryExtensions.Contains to get resolved instead of Enumerable.Contains.
375+
// The following tests that the various overloads are all supported.
376+
[ConditionalTheory]
377+
[MemberData(nameof(IsAsyncData))]
378+
public virtual Task Contains_on_Enumerable(bool async)
379+
=> AssertQuery(
380+
async,
381+
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => Enumerable.Contains(new[] { 10, 999 }, c.Int)));
382+
383+
// C# 14 first-class spans caused MemoryExtensions.Contains to get resolved instead of Enumerable.Contains.
384+
// The following tests that the various overloads are all supported.
385+
[ConditionalTheory]
386+
[MemberData(nameof(IsAsyncData))]
387+
public virtual Task Contains_on_MemoryExtensions(bool async)
388+
=> AssertQuery(
389+
async,
390+
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => MemoryExtensions.Contains(new[] { 10, 999 }, c.Int)));
391+
392+
// Note that we don't test EF 8/9 with .NET 10; this test is here for completeness/documentation purposes.
393+
#if NET10_0_OR_GREATER
394+
[ConditionalTheory]
395+
[MemberData(nameof(IsAsyncData))]
396+
public virtual Task Contains_with_MemoryExtensions_with_null_comparer(bool async)
397+
=> AssertQuery(
398+
async,
399+
ss => ss.Set<PrimitiveCollectionsEntity>().Where(c => MemoryExtensions.Contains(new[] { 10, 999 }, c.Int, comparer: null)));
400+
#endif
401+
374402
[ConditionalTheory]
375403
[MemberData(nameof(IsAsyncData))]
376404
public virtual Task Column_collection_Count_method(bool async)

0 commit comments

Comments
 (0)