5

After upgrading from .NET 9 to .NET 10, a MongoDB query that used to work now throws a System.NotSupportedException during query execution. I'm looking for pointers whether this is a runtime change in .NET 10, a MongoDB driver bug, or something I'm doing wrong?

Using: .NET 10, MongoDB.Driver 3.5.0

Code:

public static async Task<List<User>> GetUsers(Guid[] ids)
{
    return await Collection.Find(p => ids.Contains(p.UserId)).ToListAsync();
}

Exception:

System.NotSupportedException
HResult=0x80131515
Message=Specified method is not supported.
Source=System.Private.CoreLib

StackTrace:
at System.Reflection.RuntimeMethodInfo.ThrowNoInvokeException()
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Delegate.DynamicInvokeImpl(Object[] args)
at MongoDB.Driver.Linq.Linq3Implementation.Misc.PartialEvaluator.SubtreeEvaluator.Evaluate(Expression expression)
at MongoDB.Driver.Linq.Linq3Implementation.Misc.PartialEvaluator.SubtreeEvaluator.Visit(Expression expression)
at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at MongoDB.Driver.Linq.Linq3Implementation.Misc.PartialEvaluator.SubtreeEvaluator.Visit(Expression expression)
at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression1 node) at MongoDB.Driver.Linq.Linq3Implementation.Misc.PartialEvaluator.SubtreeEvaluator.Visit(Expression expression) at MongoDB.Driver.Linq.Linq3Implementation.Misc.PartialEvaluator.EvaluatePartially(Expression expression) at MongoDB.Driver.Linq.LinqProviderAdapter.TranslateExpressionToFilter[TDocument](Expression1 expression, IBsonSerializer1 documentSerializer, IBsonSerializerRegistry serializerRegistry, ExpressionTranslationOptions translationOptions) at MongoDB.Driver.ExpressionFilterDefinition1.Render(RenderArgs1 args) at MongoDB.Driver.MongoCollectionImpl1.CreateFindOperation[TProjection](FilterDefinition1 filter, FindOptions2 options)
at MongoDB.Driver.MongoCollectionImpl1.FindAsync[TProjection](IClientSessionHandle session, FilterDefinition1 filter, FindOptions2 options, CancellationToken cancellationToken) at MongoDB.Driver.MongoCollectionImpl1.d__551.MoveNext() at MongoDB.Driver.IAsyncCursorSourceExtensions.<ToListAsync>d__171.MoveNext()

Working:

var filter = Builders<User>.Filter.In(p => p.UserId, ids);
  1. Is this a behavioral change in .NET 10 that prevents using a captured array's Contains method inside MongoDB LINQ expressions? The same code worked on .NET 9 but now throws System.NotSupportedException.

  2. Are LINQ methods that operate on captured collections (for example Contains, Any, Count) no longer safe to use inside expressions passed to IMongoCollection<T>.Find(...) with the MongoDB.Driver? If so, which methods are affected?

  3. Is Builders<T>.Filter.In(...) the recommended long-term solution for this scenario, or should the LINQ provider accept array.Contains(x) again? Are there performance or correctness differences I should be aware of when switching to Filter.In?

0

1 Answer 1

5

C# 14 introduces first-class support for System.Span<T> and System.ReadOnlySpan<T>:

C# 14 recognizes the relationship and supports some conversions between ReadOnlySpan<T>, Span<T>, and T[]. The span types can be extension method receivers, compose with other conversions, and help with generic type inference scenarios.

Which results in Contains in ids.Contains(p.UserId) treated as MemoryExtensions.Contains<T>(this ReadOnlySpan<T> span, T value) which seems not to be supported by the driver for now.

As a workaround you can try using Enumerable.Contains explicitly:

await Collection.Find(p => Enumerable.Contains(ids, p.UserId))
    .ToListAsync()

Or change GetUsers signature to accept IEnumerable (or IReadOnlyCollection, etc.):

public static async Task<List<User>> GetUsers(IEnumerable<Guid> ids)
{
    return await Collection.Find(p => ids.Contains(p.UserId)).ToListAsync();
}

Or change the language version used by the application:

<PropertyGroup>
    <LangVersion>13</LangVersion>
</PropertyGroup>

UPD

To address at least some questions by Ivan Petrov

But why are arrays getting the extension methods of spans?

This is how the feature was designed - from the feature spec:

An implicit span conversion permits array_types, System.Span<T>, System.ReadOnlySpan<T>, and string to be converted between each other as follows ... We also add implicit span conversion to the list of acceptable implicit conversions on the first parameter of an extension method when determining applicability

And get them first before IEnumerable methods?

This will require a bit more investigation on my side, but as far as I understand one of them should be first and it should be the span one otherwise feature would quite less useful (or completely useless).

Also found this issue @github, which basically covers the question and is resolved "by design".

Notes

  1. Rider provides diagnostics for this:

    The resolution for this invocation has changed in C# 14 due to a breaking change in overload resolution with spans. This can cause runtime exceptions when compiled with interpretation.

    Rider/Resharper: the resolution for this invocation has changed in C# 14 due to a breaking change in overload resolution with spans. This can cause runtime exceptions when compiled with interpretation

  2. Entity Framework Core in versions earlier than 10 has the same problem - see for example First class span's break EF Core issue @github

Sign up to request clarification or add additional context in comments.

7 Comments

sounds plausible, but why are arrays getting the extension methods of spans? and get them first before IEnumerable methods?
Updated answer a bit.
others find this breaking change not that great too - see this issue
I'm surprised that thay have not backported this to EF 9 but for EF 10 it should work AFAIK
I think this should have been opt-in csproj option. Can't really believe they did that so carelessly. Makes me wonder what other decisions like that were made in .NET 10
Probably yes. Those having a lot of settings is not a good thing. As for decisions - I would argue that this is better than default interface implementations or primary constructors)
this is performance-oriented feature shouldn't really break things imo. It's like "Tiered Compilation" for me. DII are not as bad - although if one looks at the IL and the metadata + maybe. Primary constructors was the first just bad feature that I wondered how it could have made its way in the actual language.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.