[CSHARP-3691] GuidRepresentationMode.V3 filter in project of aggregation throws InvalidOperationException Created: 27/May/21  Updated: 28/Oct/23  Resolved: 15/Oct/21

Status: Closed
Project: C# Driver
Component/s: LINQ3
Affects Version/s: 2.12.3
Fix Version/s: 2.14.0

Type: Bug Priority: Unknown
Reporter: Arne Schoonvliet Assignee: Unassigned
Resolution: Fixed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Duplicate
is duplicated by CSHARP-4024 System.InvalidOperationException: Thi... Closed

 Description   

When using GuidRepresentationMode.V3 you cannot filter on GUID in a project using the aggregation framework. The code throws it's expecting GuidRepresentationMode.V2. 

 

Below you can find a simple example: 

public class BrokenSerializers : IDisposable
    {
        public class GuidDocument
        {
            public Guid Id { get; set; }
            public string Name { get; set; }
            public IEnumerable<GuidInnerDocument> Documents { get; set; }
        }        public class GuidInnerDocument
        {
            public Guid Id { get; set; }
            public Guid AnotherId { get; set; }
        }        private IMongoClient _client;
        private IMongoDatabase _database;
        private IMongoCollection<GuidDocument> _collection;        [Fact]
        public async Task GuidRepresentationModeV2ToV3()
        {
            BsonDefaults.GuidRepresentationMode = GuidRepresentationMode.V3;
            BsonSerializer.RegisterSerializer(typeof(Guid), new GuidSerializer(GuidRepresentation.Standard));            BsonClassMap.RegisterClassMap<GuidDocument>(x => x.AutoMap());
            BsonClassMap.RegisterClassMap<GuidInnerDocument>(x => x.AutoMap());            _client = new MongoClient("mongodb://localhost:27017/?replicaSet=rs1");
            _database = _client.GetDatabase("SomeTestDb");
            _collection = _database.GetCollection<GuidDocument>("SomeCollection");
            var documentId = Guid.NewGuid();
            var anotherDocumentId = Guid.NewGuid();            await _collection.InsertOneAsync(new GuidDocument
            {
                Id = documentId,
                Documents = new[]
                {
                    new GuidInnerDocument {Id = Guid.NewGuid(), AnotherId = anotherDocumentId},
                    new GuidInnerDocument {Id = Guid.NewGuid(), AnotherId = Guid.NewGuid()},
                    new GuidInnerDocument { Id = Guid.NewGuid(), AnotherId = Guid.NewGuid() }
                }
            });            var documentQuery2 = await _collection
                .Aggregate()
                .Match(x => x.Documents.Any(y => y.AnotherId == anotherDocumentId))
                .Project(x => new
                {
                    DocumentId = x.Id,
                    Document = x.Documents.First(y => y.AnotherId == anotherDocumentId)
                })
                .SingleOrDefaultAsync();            Assert.Equal(documentId, documentQuery2.DocumentId);
            Assert.Equal(anotherDocumentId, documentQuery2.Document.AnotherId);
        }        public void Dispose()
        {
            new MongoClient("mongodb://localhost:27017/?replicaSet=rs1").DropDatabase("SomeTestDb");
        }
    }
}

Below you can find the exception

System.InvalidOperationException
This constructor can only be used when BsonDefaults.GuidRepresentationMode is V2.
   at MongoDB.Bson.BsonBinaryData..ctor(Guid guid)
   at MongoDB.Bson.BsonTypeMapper.Convert(Object value, Conversion conversion)
   at MongoDB.Bson.BsonTypeMapper.TryMapToBsonValue(Object value, BsonValue& bsonValue)
   at MongoDB.Bson.BsonTypeMapper.MapToBsonValue(Object value)
   at MongoDB.Bson.BsonValue.Create(Object value)
   at MongoDB.Driver.Linq.Translators.AggregateLanguageTranslator.TranslateConstant(Expression node)
   at MongoDB.Driver.Linq.Translators.AggregateLanguageTranslator.TranslateValue(Expression node)
   at MongoDB.Driver.Linq.Translators.AggregateLanguageTranslator.TranslateOperation(BinaryExpression node, String op, Boolean canBeFlattened)
   at MongoDB.Driver.Linq.Translators.AggregateLanguageTranslator.TranslateValue(Expression node)
   at MongoDB.Driver.Linq.Translators.AggregateLanguageTranslator.TranslateWhere(WhereExpression node)
   at MongoDB.Driver.Linq.Translators.AggregateLanguageTranslator.TranslateValue(Expression node)
   at MongoDB.Driver.Linq.Translators.AggregateLanguageTranslator.TryTranslateFirstResultOperator(PipelineExpression node, BsonValue& result)
   at MongoDB.Driver.Linq.Translators.AggregateLanguageTranslator.TranslatePipeline(PipelineExpression node)
   at MongoDB.Driver.Linq.Translators.AggregateLanguageTranslator.TranslateValue(Expression node)
   at MongoDB.Driver.Linq.Translators.AggregateLanguageTranslator.TranslateMapping(ProjectionMapping mapping)
   at MongoDB.Driver.Linq.Translators.AggregateLanguageTranslator.TranslateNew(NewExpression node)
   at MongoDB.Driver.Linq.Translators.AggregateLanguageTranslator.TranslateValue(Expression node)
   at MongoDB.Driver.Linq.Translators.AggregateLanguageTranslator.Translate(Expression node, ExpressionTranslationOptions translationOptions)
   at MongoDB.Driver.Linq.Translators.AggregateProjectTranslator.TranslateProject(Expression expression, ExpressionTranslationOptions translationOptions)
   at MongoDB.Driver.Linq.Translators.AggregateProjectTranslator.Translate[TDocument,TResult](Expression`1 projector, IBsonSerializer`1 parameterSerializer, IBsonSerializerRegistry serializerRegistry, ExpressionTranslationOptions translationOptions)
   at MongoDB.Driver.ProjectExpressionProjection`2.Render(IBsonSerializer`1 inputSerializer, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.PipelineStageDefinitionBuilder.<>c__DisplayClass32_0`2.<Project>b__0(IBsonSerializer`1 s, IBsonSerializerRegistry sr)
   at MongoDB.Driver.DelegatedPipelineStageDefinition`2.Render(IBsonSerializer`1 inputSerializer, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.AppendedStagePipelineDefinition`3.Render(IBsonSerializer`1 inputSerializer, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.AppendedStagePipelineDefinition`3.Render(IBsonSerializer`1 inputSerializer, IBsonSerializerRegistry serializerRegistry)
   at MongoDB.Driver.MongoCollectionImpl`1.AggregateAsync[TResult](IClientSessionHandle session, PipelineDefinition`2 pipeline, AggregateOptions options, CancellationToken cancellationToken)
   at MongoDB.Driver.MongoCollectionImpl`1.UsingImplicitSessionAsync[TResult](Func`2 funcAsync, CancellationToken cancellationToken)
   at MongoDB.Driver.IAsyncCursorSourceExtensions.SingleOrDefaultAsync[TDocument](IAsyncCursorSource`1 source, CancellationToken cancellationToken)
   at Intigriti.Mongo.IntegrationTests.BrokenSerializers.GuidRepresentationModeV2ToV3() in D:\code\library\Mongo\Intigriti.Mongo.IntegrationTests\BrokenSerializers.cs:line 116
   at Xunit.Sdk.TestInvoker`1.<>c__DisplayClass48_1.<<InvokeTestMethodAsync>b__1>d.MoveNext() in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\Frameworks\Runners\TestInvoker.cs:line 264
--- End of stack trace from previous location ---
   at Xunit.Sdk.ExecutionTimer.AggregateAsync(Func`1 asyncAction) in C:\Dev\xunit\xunit\src\xunit.execution\Sdk\Frameworks\ExecutionTimer.cs:line 48
   at Xunit.Sdk.ExceptionAggregator.RunAsync(Func`1 code) in C:\Dev\xunit\xunit\src\xunit.core\Sdk\ExceptionAggregator.cs:line 90

Same exception is happing with version 2.13.0-beta of the driver



 Comments   
Comment by Ahiya Elster [ 06/Jul/23 ]

This fixed it, thanks for the quick response!

Comment by James Kovacs [ 04/Jul/23 ]

Hi, elster.ahiya@gmail.com,

The root cause of the problem is the use of the implicit conversion from Guid to BsonValue. (See BsonValue.cs line 731.) This implicit conversion is only valid for GuidRepresentationMode.V2. In V3 mode, you must explicitly specify the representation of each Guid as different Guid values within a document can have different representations. Because you are specify the pipeline stage as a BsonDocument, there is no BsonClassMap or BSON attributes to look up to determine how it should be mapped. In order to specify the GuidRepresentation, you cannot use the implicit conversion, but must instead use the BsonBinaryData constructor as shown below.

PipelineDefinition<BsonDocument, BsonDocument> pipeline = new[]
{
    new BsonDocument("$match", new BsonDocument()
        .Add("_id", new BsonBinaryData(id, GuidRepresentation.Standard))),
//more pipeline steps here
};

Hope this answers your question.

Sincerely,
James

Comment by Ahiya Elster [ 04/Jul/23 ]

Hi,

I'm getting the error when I run the following code:

//id is a Guid gotten as a method parameter
PipelineDefinition<BusinessItemModel, BusinessItemFullModel> pipeline = new[]
{
new BsonDocument("$match", new BsonDocument()
.Add("_id", id)),
//more pipeline steps here
}
var result = await Collection.Aggregate(pipeline).ToListAsync(); 

And the exception is:

System.InvalidOperationException: This constructor can only be used when BsonDefaults.GuidRepresentationMode is V2.

 

I also have the following code in my startup class:

BsonDefaults.GuidRepresentation = GuidRepresentation.Standard;
BsonDefaults.GuidRepresentationMode = GuidRepresentationMode.V3;
BsonSerializer.RegisterSerializer(new GuidSerializer(GuidRepresentation.Standard)); 

If possible I wish to use the built in aggregation pipeline instead of the LINQ alternative

Is there any possible fix that I can do for this?

Mongo c# driver version: 2.19.1

Mongo db version: 5.0.14

 

Thanks

Comment by Arne Schoonvliet [ 17/Oct/21 ]

Hi Dmitry

Thanks for the update! I will check it out!

Comment by Dmitry Lukyanov (Inactive) [ 15/Oct/21 ]

This issue has been fixed in the new LINQ provider (known as LINQ3) which will be included in the upcoming 2.14 release.

Configure your MongoClientSettings to use LinqProvider.V3 if you want to use this functionality.

To configure a client to use the LINQ3 provider use code like the following

var connectionString = "mongodb://localhost";
var clientSettings = MongoClientSettings.FromConnectionString(connectionString);
clientSettings.LinqProvider = LinqProvider.V3;
var client = new MongoClient(clientSettings);

Comment by James Kovacs [ 03/Jun/21 ]

Hi, Arne,

We are currently completing a code review of the new LINQ implementation before merging it into the main repository. Our goal is to ship it as an opt-in replacement for our current LINQ provider in an upcoming beta release of 2.13.0.

Sincerely,
James

Comment by Arne Schoonvliet [ 03/Jun/21 ]

Hi James

Thanks for checking and confirming the problem. What is the ETA for the new driver with it's new LINQ implementation? I guess this will be the 3.x.x version together with mongodb 5.0.0? Is it possible to checkout a certain branch of the driver on github so I can already check some other parts of our code with the new driver already?

Sincerely
Arne

Comment by James Kovacs [ 02/Jun/21 ]

Hi, Arne,

Thank you for reporting this issue. We have reproduced the problem and identified the cause. When generating an array filter expression containing a GUID during the project stage, we assume V2 GuidRepresentationMode to translate the GUID, which is invalid when in V3 GuidRepresentationMode. If you remove the following portion of the project, the query is generated and executed successfully:

.Project(x => new {
    DocumentId = x.Id,
    Document = x.Documents.First(y => y.AnotherId == anotherDocumentId) <== remove this
})

Returning the entire array is also successful:

.Project(x => new {
    DocumentId = x.Id,
    Documents = x.Documents
})

As is filtering on a non-GUID field. (Name field added to the inner class.)

.Project(x => new {
    DocumentId = x.Id,
    Document = x.Documents.First(y => y.Name == someName)
})

We have confirmed that this issue is resolved in our new LINQ implementation, which will ship in an upcoming version of the driver.

Thank you again for reporting this issue. You can monitor this issue for updates on the availability of a fix.

Sincerely,
James

Comment by Mikalai Mazurenka (Inactive) [ 28/May/21 ]

Hi arne@intigriti.be

Thanks for reporting this issue!

We need some time to investigate it and will come back to you.

Comment by Arne Schoonvliet [ 27/May/21 ]

One thing to note, code above works when using GuidRepresentationMode.V2 and GuidRepresentation.Standard as global default.

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