[CSHARP-4588] LINQ3 LinqProvider do not support public extension method Created: 30/Mar/23  Updated: 18/Aug/23  Resolved: 31/Mar/23

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

Type: Bug Priority: Critical - P2
Reporter: Wojciech Nagorski Assignee: Robert Stam
Resolution: Duplicate Votes: 0
Labels: LINQ3
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Duplicate
duplicates CSHARP-4498 .Project(c => c.Reference()) not work Closed
duplicates CSHARP-4763 Consider supporting client side proje... Scheduled
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

MongoDB.Driver since version 1.19.0 does not support the extension method inside the Project() method. In version 2.19.1 this still doesn't work. 

How to Reproduce

After updating MongoDB.Driver from 2.18.0 to 2.19.1, I got an exception:

 

MongoDB.Driver.Linq.ExpressionNotSupportedException: Expression not supported: execution.ToExecutionCommonDto().
   at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodCallExpressionToAggregationExpressionTranslator.Translate(TranslationContext context, MethodCallExpression 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.FindAsync[TProjection](IClientSessionHandle session, FilterDefinition`1 filter, FindOptions`2 options, CancellationToken cancellationToken)
   at MongoDB.Driver.FindFluent`2.ToCursorAsync(CancellationToken cancellationToken)
   at MongoDB.Driver.IAsyncCursorSourceExtensions.ToListAsync[TDocument](IAsyncCursorSource`1 source, CancellationToken cancellationToken) 

Where the query code looks like this:

 

 

            return await this.databaseContext.Executions
                .Find(this.databaseContext.CurrentSession, filterBuilder.And(filters))
                .Project(execution => execution.ToExecutionCommonDto())
                .ToListAsync(CancellationToken.None); 

I use the public extension method ToExecutionCommonDto() that converts the database model to the DTO model:

 

 

return new ExecutionCommonDto
            {
                Id = execution.Id,
                StartTime = execution.Time?.StartTime,
                EndTime = execution.Time?.EndTime,
                Timeout = execution.Time?.Timeout,
                Status = execution.Status?.Status,
                Result = execution.ExecutionResult?.Result
            }; 

And this code worked in version 1.18.0, but since version 1.19.0 it stopped working and in 1.19.1 they still don't work.

I'm using MongoDB inside container:

  mongoDb:
    image: bitnami/mongodb:5.0.3
    restart: always
    ports:
      - "27017:27017"
    environment:
      - MONGODB_DATABASE=database
      - MONGODB_USERNAME=user
      - MONGODB_PASSWORD=password
      - MONGODB_ADVERTISED_HOSTNAME=mongoDb
      - MONGODB_REPLICA_SET_MODE=primary
      - MONGODB_ROOT_PASSWORD=password
      - MONGODB_REPLICA_SET_KEY=C1D6B99752EC4929
      - MONGODB_SYSTEM_LOG_VERBOSITY=0
    volumes:
      - mongoDb_volume:/bitnami:rw
    networks:
      - app-network
 
  mongoDb-replica:
    image: bitnami/mongodb:5.0.3
    restart: always
    privileged: true
    environment:
      - MONGODB_ADVERTISED_HOSTNAME=mongoDb-replica
      - MONGODB_REPLICA_SET_MODE=secondary
      - MONGODB_INITIAL_PRIMARY_HOST=mongoDb
      - MONGODB_INITIAL_PRIMARY_PORT_NUMBER=27017
      - MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD=password
      - MONGODB_REPLICA_SET_KEY=C1D6B99752EC4929
      - MONGODB_SYSTEM_LOG_VERBOSITY=0
    depends_on:
      - mongoDb
    networks:
      - app-network  
  mongoDb-arbiter:
    image: bitnami/mongodb:5.0.3
    restart: always
    privileged: true
    environment:
      - MONGODB_ADVERTISED_HOSTNAME=mongoDb-arbiter
      - MONGODB_REPLICA_SET_MODE=arbiter
      - MONGODB_INITIAL_PRIMARY_HOST=mongoDb
      - MONGODB_INITIAL_PRIMARY_PORT_NUMBER=27017
      - MONGODB_INITIAL_PRIMARY_ROOT_PASSWORD=password
      - MONGODB_REPLICA_SET_KEY=C1D6B99752EC4929
      - MONGODB_SYSTEM_LOG_VERBOSITY=0
    depends_on:
      - mongoDb
    networks:
      - app-network 

 



 Comments   
Comment by Robert Stam [ 31/Mar/23 ]

The LINQ3 provider does not support client side projections in a Find projection. This was a deliberate design choice. You can either do the projection server side by using an aggregation pipeline, or you can do the projection client side explicitly by factoring it out.

To factor out a client side projection explicitly replace something like:

var find = collection
    .Find(filter)
    .Project(execution => execution.ToExecutionCommonDto());

with:

var find = collection
    .Find(filter)
    .ToEnumerable()
    .Select(execution => execution.ToExecutionCommonDto());

The call to `ToEnumerable()` separates what is executed on the server from what is executed client side.

Note that the query is not sent to the server until you attempt to enumerate the `find` variable, for example by calling `find.ToList()`.

Comment by Wojciech Nagorski [ 30/Mar/23 ]

The public extension looks:

        public static ExecutionCommonDto ToExecutionCommonDto(this Execution execution)
        {
            return new ExecutionCommonDto
            {
                Id = execution.Id,
                StartTime = execution.Time?.StartTime,
                EndTime = execution.Time?.EndTime,
                Timeout = execution.Time?.Timeout,
                Status = execution.Status?.Status,
                Result = execution.ExecutionResult?.Result
            };
        } 

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