-
Type: Bug
-
Resolution: Fixed
-
Priority: Unknown
-
Affects Version/s: 2.20.0
-
Component/s: Builders, Serialization
-
None
-
Fully Compatible
-
Dotnet Drivers
-
Not Needed
-
Starting in driver version 2.20.0 with LINQ2, Builders<T>.Projection.Expression fails to deserialize data correctly in certain cases.
var projectionTypedCamel = Builders<CamelDocument>.Projection.Expression(x => new CamelDocument { Id = x.Id, Name = x.Name }); var queryTypedCamel = camelColl.Aggregate().Project(projectionTypedCamel).Limit(1); var resultTypedCamel = queryTypedCamel.FirstOrDefault();
The resulting object contains only default values even though the server returned the correct data.
{ "_id" : ObjectId("000000000000000000000000"), "name" : null, "activeSince" : ISODate("0001-01-01T00:00:00Z"), "isActive" : false }
If we switch to pascal casing rather than camel casing, the Name is returned, but not the Id:
{ "_id" : ObjectId("000000000000000000000000"), "Name" : "Test638544783272212590", "ActiveSince" : ISODate("0001-01-01T00:00:00Z"), "IsActive" : false }
If we project into an anonymous object, both the Id and Name are returned correctly:
{ "_id" : ObjectId("66746157f75c0df9cadda90e"), "Name" : "Test638544783270965320" }
Full repro:
using System; using MongoDB.Bson; using MongoDB.Bson.Serialization.Conventions; using MongoDB.Driver; using MongoDB.Driver.Linq; ConventionRegistry.Register("camelCaseConvention", new ConventionPack { new CamelCaseElementNameConvention() }, t => t == typeof(CamelDocument)); ConventionRegistry.Register("extraElementsConvention", new ConventionPack { new IgnoreExtraElementsConvention(true) }, _ => true); var mongoSettings = MongoClientSettings.FromUrl(new MongoUrl("mongodb://localhost:27017")); mongoSettings.LinqProvider = LinqProvider.V2; var client = new MongoClient(mongoSettings); var db = client.GetDatabase("test"); var pascalColl = db.GetCollection<PascalDocument>("pascal"); var camelColl = db.GetCollection<CamelDocument>("camel"); pascalColl.DeleteMany("{}"); camelColl.DeleteMany("{}"); var camel = new CamelDocument { Id = ObjectId.GenerateNewId(), Name = $"Test{DateTime.Now.Ticks}", ActiveSince = DateTime.UtcNow, IsActive = true }; camelColl.InsertOne(camel); var pascal = new PascalDocument { Id = ObjectId.GenerateNewId(), Name = $"Test{DateTime.Now.Ticks}", ActiveSince = DateTime.UtcNow, IsActive = true }; pascalColl.InsertOne(pascal); var projectionTypedCamel = Builders<CamelDocument>.Projection.Expression(x => new CamelDocument { Id = x.Id, Name = x.Name }); var queryTypedCamel = camelColl.Aggregate().Project(projectionTypedCamel).Limit(1); var resultTypedCamel = queryTypedCamel.FirstOrDefault(); var projectionTypedPascal = Builders<PascalDocument>.Projection.Expression(x => new PascalDocument { Id = x.Id, Name = x.Name }); var queryTypedPascal = pascalColl.Aggregate().Project(projectionTypedPascal).Limit(1); var resultTypedPascal = queryTypedPascal.FirstOrDefault(); var projectionAnonymousCamel = Builders<CamelDocument>.Projection.Expression(x => new { x.Id, x.Name }); var queryAnonymousCamel = camelColl.Aggregate().Project(projectionAnonymousCamel).Limit(1); var resultAnonymousCamel = queryAnonymousCamel.FirstOrDefault(); var projectionAnonymousPascal = Builders<PascalDocument>.Projection.Expression(x => new { x.Id, x.Name }); var queryAnonymousPascal = pascalColl.Aggregate().Project(projectionAnonymousPascal).Limit(1); var resultAnonymousPascal = queryAnonymousPascal.FirstOrDefault(); Console.WriteLine("Generated MQL is identical across queries (modulo casing of field names)..."); Console.WriteLine("Query with projection to typed (camel): " + queryTypedCamel); Console.WriteLine("Query with projection to typed (pascal): " + queryTypedPascal); Console.WriteLine("Query with projection to anonymous (camel): " + queryAnonymousCamel); Console.WriteLine("Query with projection to anonymous (pascal): " + queryAnonymousPascal); Console.WriteLine("Deserialized results are incorrect for typed queries..."); Console.WriteLine($"Result with projection (camel) to CamelDocument: {resultTypedCamel.ToJson()}"); Console.WriteLine($"Result with projection (pascal) to PascalDocument: {resultTypedPascal.ToJson()}"); Console.WriteLine($"Result with projection (camel) to anonymous: {resultAnonymousCamel.ToJson()}"); Console.WriteLine($"Result with projection (pascal) to anonymous: {resultAnonymousPascal.ToJson()}"); record CamelDocument { public ObjectId Id { get; set; } public string Name { get; set; } public DateTime ActiveSince { get; set; } public bool IsActive { get; set; } } record PascalDocument { public ObjectId Id { get; set; } public string Name { get; set; } public DateTime ActiveSince { get; set; } public bool IsActive { get; set; } }
Based on this post in the MongoDB Community Forums. The reporter also provided a self-contained repro.