-
Type:
Bug
-
Resolution: Unresolved
-
Priority:
Major - P3
-
None
-
Affects Version/s: None
-
Component/s: LINQ3
-
None
-
None
-
Dotnet Drivers
-
None
-
None
-
None
-
None
-
None
-
None
In MongoDB C# Driver version 3.2.1, the LinqProvider V3 incorrectly generates MongoDB queries when performing projections on nested collections that contain ObjectId fields. This results in incorrect query results without throwing any exceptions. This behavior differs from the previous 2.30.0 version of the driver, which correctly preserves ObjectId representations in projections.
Repro code:
BsonClassMap.RegisterClassMap<Document>();
BsonClassMap.RegisterClassMap<Chain>();
BsonClassMap.RegisterClassMap<Unit>();var connectionString = "mongodb://localhost/db?directConnection=true";
var settings = MongoClientSettings.FromUrl(new MongoUrl(connectionString));settings.ClusterConfigurator = builder =>
{
builder.Subscribe<CommandStartedEvent>(e =>
{
Console.WriteLine($"Command
-> {e.Command.ToJson()}");
});
};
var client = new MongoClient(settings);// bootstrap
var collection = client.GetDatabase("db").GetCollection<Chain>("test");var id1 = BsonObjectId.Create(ObjectId.GenerateNewId());
var id2 = BsonObjectId.Create(ObjectId.GenerateNewId());
var id3 = BsonObjectId.Create(ObjectId.GenerateNewId());var chain = new Chain()
{
Parts = new List<Unit>()
{
new()
{
Refs = new List<Document>()
{
new()
,
new()
{
id = id2.ToString(),
},
new()
{
id = id3.ToString(),
},
}
}
}
};await collection.InsertOneAsync(chain);
// end bootstrap
List<string> jobIds = [id2.ToString()];var findFluent = collection
.Find(x => x.Parts.Any(a => a.Refs.Any(b => jobIds.Contains(b.id))))
.Project(chain => new { chain.Parts
.First(p => p.Refs.Any(j => jobIds.Contains(j.id)))
.Refs.First(j => jobIds.Contains(j.id)).id });var result = await findFluent.ToListAsync();// Driver 2.30.0 result.First().id is not null
// Driver 3.2.1 result.First().id is nullpublic class Document
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public string id
}public class Chain : Document
{
public ICollection<Unit> Parts { get; set; }
= new List<Unit>();
}public class Unit
{
public ICollection<Document> Refs
public Unit()
{
Refs = new List<Document>();
}
}
the resulting query when using driver 2.30.0 is:
find({ "Parts" : { "$elemMatch" : { "Refs" : { "$elemMatch" : { "_id" : { "$in" : [ObjectId("67dc3b79a35f548871c29233")] } } } } } }, { "_id" : { "$let" : { "vars" : { "this" : { "$arrayElemAt" : [{ "$filter" : { "input" : { "$let" : { "vars" : { "this" : { "$arrayElemAt" : [{ "$filter" : { "input" : "$Parts", "as" : "p", "cond" : { "$anyElementTrue" : { "$map" : { "input" : "$$p.Refs", "as" : "j", "in" : { "$in" : ["$$j._id", [ObjectId("67dc3b79a35f548871c29233")]] } } } } } }, 0] } }, "in" : "$$this.Refs" } }, "as" : "j", "cond" : { "$in" : ["$$j._id", [ObjectId("67dc3b79a35f548871c29233")]] } } }, 0] } }, "in" : "$$this._id" } } })
for the new driver 3.2.1 it’s:
find({ "Parts" : { "$elemMatch" : { "Refs" : { "$elemMatch" : { "_id" : { "$in" : [{ "$oid" : "67dc3bc04c5e85b06180264f" }] } } } } } }, { "_id" : { "$let" : { "vars" : { "this" : { "$arrayElemAt" : [{ "$filter" : { "input" : { "$let" : { "vars" : { "this" : { "$arrayElemAt" : [{ "$filter" : { "input" : "$Parts", "as" : "p", "cond" : { "$anyElementTrue" : { "$map" : { "input" : "$$p.Refs", "as" : "j", "in" : { "$in" : ["$$j._id", ["67dc3bc04c5e85b06180264f"]] } } } } } }, 0] } }, "in" : "$$this.Refs" } }, "as" : "j", "cond" : { "$in" : ["$$j._id", ["67dc3bc04c5e85b06180264f"]] } } }, 0] } }, "in" : "$$this._id" } } })
Clearly, the ObjectId or $oid is missing in the inner selections of _id. This is especially dangerous issue because it alters the final results without any errors or exceptions.
Reference: Reported in forums here.