[CSHARP-4505] The LINQ3 provider does not support projection to a class taking a BsonDocument in its constructor Created: 30/Jan/23  Updated: 27/Oct/23  Resolved: 06/Feb/23

Status: Closed
Project: C# Driver
Component/s: LINQ3
Affects Version/s: 2.19.0
Fix Version/s: None

Type: Bug Priority: Unknown
Reporter: Cédric Luthi Assignee: Robert Stam
Resolution: Works as Designed Votes: 1
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Case:
Documentation Changes Summary:

1. What would you like to communicate to the user about this feature?
2. Would you like the user to see examples of the syntax and/or executable code and its output?
3. Which versions of the driver/connector does this apply to?


 Description   

Summary

The LINQ3 provider does not support projection to a class taking a BsonDocument in its constructor (the V2 provider supports it).

How to Reproduce

Linq3Bug.csproj

<Project Sdk="Microsoft.NET.Sdk">
 
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>
 
  <ItemGroup>
    <PackageReference Include="EphemeralMongo6" Version="0.1.3" />
    <PackageReference Include="MongoDB.Driver" Version="2.19.0" />
  </ItemGroup>
 
</Project>

 

Program.cs

using System;
using System.Linq;
using EphemeralMongo;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Linq;
 
Console.Write("Running MongoDB");
using var runner = MongoRunner.Run();
Console.WriteLine($" on {runner.ConnectionString}");
var settings = MongoClientSettings.FromConnectionString(runner.ConnectionString);
if (Enum.TryParse<LinqProvider>(args.FirstOrDefault(), out var provider))
{
    settings.LinqProvider = provider;
}
Console.WriteLine($"Using LinqProvider {settings.LinqProvider}");
 
var client = new MongoClient(settings);
var collection = client.GetDatabase("my-db").GetCollection<BsonDocument>("my-collection");
var document = new BsonDocument
{
    ["_id"] = ObjectId.GenerateNewId(),
    ["Name"] = "Me",
};
collection.InsertOne(document);
var me = collection.Find(new BsonDocument()).Project(d => new Person(d)).Single();
Console.WriteLine($"[{me.Id}] {me.Name}");
 
public class Person
{
    public Person(BsonDocument document)
    {
        Id = document["_id"].AsObjectId;
        Name = document["Name"].AsString;
    }
 
    public ObjectId Id { get; set; }
 
    public string Name { get; set; }
}

Run with the V2 driver (dotnet run V2): ✅ works as expected

Running MongoDB on mongodb://127.0.0.1:60766
Using LinqProvider V2
[63d846b35c677f6f0e813308] Me

Run with the V3 driver (dotnet run V3): ❌ ExpressionNotSupportedException

Running MongoDB on mongodb://127.0.0.1:60866
Using LinqProvider V3
Unhandled exception. MongoDB.Driver.Linq.ExpressionNotSupportedException: Expression not supported: new Person(d) because constructor parameter document does not match any property.
   at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.NewExpressionToAggregationExpressionTranslator.GetMatchingPropertyName(NewExpression expression, String constructorParameterName)
   at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.NewExpressionToAggregationExpressionTranslator.<>c__DisplayClass0_0.<Translate>b__1(ParameterInfo p)
   at System.Linq.Enumerable.SelectArrayIterator`2.ToArray()
   at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.NewExpressionToAggregationExpressionTranslator.Translate(TranslationContext context, NewExpression expression)
   at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.ExpressionToAggregationExpressionTranslator.Translate(TranslationContext context, Expression expression)
   at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(TranslationContext context, LambdaExpression lambdaExpression, IBsonSerializer parameterSerializer, Boolean asRoot)
   at MongoDB.Driver.Linq.Linq3Implementation.LinqProviderAdapterV3.TranslateExpressionToProjection[TInput,TOutput](Expression`1 expression, IBsonSerializer`1 inputSerializer, IBsonSerializerRegistry serializerRegistry, ExpressionTranslationOptions translationOptions)
   at MongoDB.Driver.Linq.Linq3Implementation.LinqProviderAdapterV3.TranslateExpressionToFindProjection[TSource,TProjection](Expression`1 expression, IBsonSerializer`1 sourceSerializer, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.FindExpressionProjectionDefinition`2.Render(IBsonSerializer`1 sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
   at MongoDB.Driver.MongoCollectionImpl`1.CreateFindOperation[TProjection](FilterDefinition`1 filter, FindOptions`2 options)
   at MongoDB.Driver.MongoCollectionImpl`1.FindSync[TProjection](IClientSessionHandle session, FilterDefinition`1 filter, FindOptions`2 options, CancellationToken cancellationToken)
   at MongoDB.Driver.MongoCollectionImpl`1.<>c__DisplayClass46_0`1.<FindSync>b__0(IClientSessionHandle session)
   at MongoDB.Driver.MongoCollectionImpl`1.UsingImplicitSession[TResult](Func`2 func, CancellationToken cancellationToken)
   at MongoDB.Driver.MongoCollectionImpl`1.FindSync[TProjection](FilterDefinition`1 filter, FindOptions`2 options, CancellationToken cancellationToken)
   at MongoDB.Driver.FindFluent`2.ToCursor(CancellationToken cancellationToken)
   at MongoDB.Driver.IAsyncCursorSourceExtensions.Single[TDocument](IAsyncCursorSource`1 source, CancellationToken cancellationToken)
   at MongoDB.Driver.IFindFluentExtensions.Single[TDocument,TProjection](IFindFluent`2 find, CancellationToken cancellationToken)

Additional Background

The Hangfire.Mongo project uses this pattern (Find + Project) a lot so it's impossible to migrate to the LINQ3 provider.



 Comments   
Comment by Robert Stam [ 06/Feb/23 ]

For the example given in this issue you can make it work by factoring out the client side projection.

Change:

var find =
    collection
    .Find("{}")
    .Project(x => new Person(x));
 
var result = find.ToList();

to:

var find =
    collection
    .Find("{}");
 
var results = find.ToList().Select(x => new Person(x)).ToList();

Comment by Esha Bhargava [ 03/Feb/23 ]

cedric.luthi@gmail.com Thank you for reporting this issue. We'll look into it and get back to you soon.

Comment by Cédric Luthi [ 03/Feb/23 ]

The V2 provider was in fact doing client side evaluation for those queries (like EF Core used to do before version 3.0). Throwing an ExpressionNotSupportedException is indeed a better approach (like EF Core does for version 3.0 upwards).

The problematic queries in Hangfire.Mongo have been addressed in pull request #339 and released in version 1.9.2.

This issue can be closed.

Generated at Wed Feb 07 21:48:25 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.