[CSHARP-4499] Support Convert calls to a base type in filter translators Created: 30/Jan/23  Updated: 28/Oct/23  Resolved: 15/Feb/23

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

Type: Bug Priority: Unknown
Reporter: DanikRaikhlin N/A Assignee: Robert Stam
Resolution: Fixed Votes: 3
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Attachments: Zip Archive ConsoleApp4.zip    
Issue Links:
Duplicate
is duplicated by CSHARP-4502 UpdateMany call fails after updating ... Closed
is duplicated by CSHARP-4509 Support Convert calls to a base type ... Closed
is duplicated by CSHARP-4546 Expression not supported: Convert Err... Closed
is duplicated by CSHARP-4510 Several expressions no longer transla... Closed
is duplicated by CSHARP-4519 UpdateDefinition can't handle casts w... Closed
Problem/Incident
causes CSHARP-4609 InvalidCastException with LinqProvide... Closed
Documentation Changes: Not Needed
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

In our solution we are generating dynamic queries based on generics. The folowing snippet worked with Linq2 provider but since the new update (MongoDB.Driver 2.19.0) its broken in the Linq3 provider. __ 

It has something to do with object type of the property. String property works while ObjectId property doesn't. Looking at how .NET renders the expression it adds a Convert() method around the ObjectId property while not doing that for a string property. 

How to Reproduce

No MongoDb instance needed as this just fails to render the query. Attached basic console app reproducing the scenario.

ConsoleApp4.zip

Failing part:

Expression<Func<ConcreteClass, object>> expression = x => x.InternalId;
 
 var fieldDefinition = new ExpressionFieldDefinition<ConcreteClass, object>(expression);
 
var query = collection
        .Aggregate()
        .Sort(Builders<ConcreteClass>.Sort.Ascending(fieldDefinition))
        .ToString();

 

Additional Background

StackTrace:

Unhandled exception. MongoDB.Driver.Linq.ExpressionNotSupportedException: Expression not supported: Convert(x.InternalId, Object).
   at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.ToFilterFieldTranslators.ConvertExpressionToFilterFieldTranslator.Translate(TranslationContext context, UnaryExpression expression)
   at MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.ToFilterFieldTranslators.ExpressionToFilterFieldTranslator.Translate(TranslationContext context, Expression expression)
   at MongoDB.Driver.Linq.Linq3Implementation.LinqProviderAdapterV3.TranslateExpressionToField[TDocument,TField](Expression`1 expression, IBsonSerializer`1 documentSerializer, IBsonSerializerRegistry serializerRegistry, Boolean allowScalarValueForArrayField)
   at MongoDB.Driver.ExpressionFieldDefinition`2.Render(IBsonSerializer`1 documentSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider, Boolean allowScalarValueForArrayField)
   at MongoDB.Driver.ExpressionFieldDefinition`2.Render(IBsonSerializer`1 documentSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
   at MongoDB.Driver.UntypedFieldDefinitionAdapter`2.Render(IBsonSerializer`1 documentSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
   at MongoDB.Driver.DirectionalSortDefinition`1.Render(IBsonSerializer`1 documentSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
   at MongoDB.Driver.SortPipelineStageDefinition`1.Render(IBsonSerializer`1 inputSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
   at MongoDB.Driver.AppendedStagePipelineDefinition`3.Render(IBsonSerializer`1 inputSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
   at MongoDB.Driver.PipelineDefinition`2.ToString(IBsonSerializer`1 inputSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
   at MongoDB.Driver.PipelineDefinition`2.ToString(LinqProvider linqProvider)
   at MongoDB.Driver.AggregateFluent`2.ToString()



 Comments   
Comment by Githook User [ 24/Mar/23 ]

Author:

{'name': 'rstam', 'email': 'robert@robertstam.org', 'username': 'rstam'}

Message: CSHARP-4499: Support Convert calls to a base type in filter translators.
Branch: v2.19.x
https://github.com/mongodb/mongo-csharp-driver/commit/6632df24ddadf2b6a11b078811d720ab2d06683e

Comment by Githook User [ 15/Feb/23 ]

Author:

{'name': 'rstam', 'email': 'robert@robertstam.org', 'username': 'rstam'}

Message: CSHARP-4499: Support Convert calls to a base type in filter translators.
Branch: master
https://github.com/mongodb/mongo-csharp-driver/commit/f0bed8a6db60f6c32b6c126658af83bc6f45b2bf

Comment by DanikRaikhlin N/A [ 01/Feb/23 ]

As you mention the serialization of the TField part then it might be handy to expand on the scenario. 

The created FieldDefintion will eventually be used in a FilterDefinition where the value will be an 'object' retrieved by the labda expression: 'Builders<TModel>.Filter.Gt(xx.Key.FieldDefinition, xx.Value)'. So based on your response serializing the value part of a FilterDefinition could be an issue? Then again it was and is always possible to explicitly cast a string to a FieldDefinition and use it the same way (my current workaround) and the serialization of the object went fine which then indicates for me that it shouldn't affect the serialization part? But ofcourse i don't know the inner workings of the driver so i can overlook things.

Comment by Robert Stam [ 01/Feb/23 ]

Thanks for your quick response. I'm glad you have a workaround you can use in the meantime.

I do have a code change that addresses the mismatch between the actual type and the declared type (`ObjectId` vs `object` in your example).

If the field definition is ONLY being used to figure out the field name, the change currently in code review will be fine.

But if the field definition is also used to determine how to serialize values of type `TField` that probably won't work properly.

For `Sort.Ascending` it is only the field name that matters, not the actual type.

Comment by DanikRaikhlin N/A [ 01/Feb/23 ]

Thanks for taking a look!

The example that i've given to reproduce the issue is an oversimplification of the actual scenario that we have. In our usecase at that point we don't know what the concrete class nor what the property actually is. We define the labda expression as a property accessor to get the actual value from a class but also using the same expression to create a FilterDefinition dynamically in a very generic way. So yes if i knew the concrete type and the concrete property type then yes ofcourse i could change 'object' to that specific type but that isn't possible for us in this scenario. However for us i've already build a workaround for to extract the property name from the expression and use that to create a FieldDefinition.

 

The decision on weather this is a bug or by design i'm leaving up to you. I'm just demonstrating a difference in rendering of queries between LINQ2 and LINQ3 provider.

Comment by Robert Stam [ 01/Feb/23 ]

Actually, I'm no longer sure whether there is a bug here or not.

When you use the `FieldDefinition<TDocument, TField>` class we expect that `TField` will accurately reflect the true type of the field.

In your example, the true type of the field is `ObjectId`, but you are declaring the field definition to be `FieldDefinition<ConcreteClass, object>` and `object` is not the true type of the field.

Note that your example would run correctly if you did either of the following two things:

1. Use `ObjectId` as the `TField` type:

var fieldDefinition = new ExpressionFieldDefinition<ConcreteClass, ObjectId>(x => x.InternalId);

 
2. Use the `FieldDefinition<TDocument>` class instead:

var fieldDefinition = new ExpressionFieldDefinition<ConcreteClass>((ConcreteClass x) => x.InternalId);

Let me know if using one of these two "more correct" forms addresses your issue.

Thanks!

Comment by Robert Stam [ 30/Jan/23 ]

Thank you for reporting this issue.

I am able to reproduce and have found a fix that will be in the next release.

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