-
Type:
Bug
-
Resolution: Fixed
-
Priority:
Major - P3
-
Affects Version/s: None
-
Component/s: LINQ
-
None
-
None
-
None
-
None
-
None
-
None
-
None
-
None
When a Select() is used inside a Project(), and the Select() expression refers to the Project() lambda variable, the generated projection does not properly reference the Project() lambda variable and its properties.
Given a local database named "test" and
db.parents.insert([
{_id:1, name:"parent1"},
{_id:2, name:"parent2"}
]);
db.children.insert([
{_id:1, name:"child1", parentId:2},
{_id:2, name:"child2", parentId:1}
]);
db.grandChildren.insert([
{_id:1, name:"grandchild1", childId: 2},
{_id:2, name:"grandchild2", childId: 1}
]);
And query method GetTree()
using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using MongoDB.Bson; using MongoDB.Driver; namespace MongoDbTest.Repository { public class Repository : IRepository { private readonly IMongoCollection<Child> _children; private readonly IMongoCollection<GrandChild> _grandChildren; private readonly IMongoCollection<Parent> _parents; // There is no way to refer to a let variable ($$products) from a lambda function. // We have to use a BsonDocument (strings unfortunately) to define our lookup pipeline. // In reality, there are other criteria in the $and private const string DeliveriesLookupPipelineString = @" {$match:{ $expr:{ $and:[ {$in:[""$childId"",""$$children._id""]} ] } } } "; private static readonly PipelineDefinition<GrandChild, GrandChild> GrandchildrenLookupPipeline = new[] { BsonDocument.Parse(DeliveriesLookupPipelineString) }; public Repository( IMongoCollection<Parent> parents, IMongoCollection<Child> children, IMongoCollection<GrandChild> grandChildren) { _parents = parents; _children = children; _grandChildren = grandChildren; } public Task<List<ParentTree>> GetTree() { return _parents .Aggregate() .Lookup<Parent, Child, ParentProjection>( _children, parent => parent.Id, child => child.ParentId, parentProjection => parentProjection.Children) .Lookup<ParentProjection, GrandChild, GrandChild, IEnumerable<GrandChild>, ParentProjection>( _grandChildren, new BsonDocument {{"children", "$children"}}, GrandchildrenLookupPipeline, childProjection => childProjection.GrandChildren) .Project(parent => new ParentTree { Id = parent.Id, ParentName = parent.Name, Children = parent.Children .Select(child => new ChildTree { Id = child.Id, Name = child.Name, GrandChildren = parent .GrandChildren // For some reason the generated code refers to child.grandChildren .Where(gc => gc.ChildId == child.Id) // and here the generated code compares gc.childId to gc._id rather than to child._id .Select(gc => new GrandChildProjection() { Id = gc.Id, Name = gc.Name }) }) }).ToListAsync(); } } }
The following MongoDb aggregate is generated (2 lines commented with // incorrect)
db.parents.aggregate(
[
{
"$lookup":{
"from":"children",
"localField":"_id",
"foreignField":"parentId",
"as":"children"
}
},
{
"$lookup":{
"from":"grandChildren",
"let":{
"children":"$children"
},
"pipeline":[
{
"$match":{
"$expr":{
"$and":[
{
"$in":[
"$childId",
"$$children._id"
]
}
]
}
}
}
],
"as":"grandChildren"
}
},
{
"$project":{
"Id":"$_id",
"ParentName":"$name",
"Children":{
"$map":{
"input":"$children",
"as":"child",
"in":{
"Id":"$$child._id",
"Name":"$$child.name",
"GrandChildren":{
"$map":{
"input":{
"$filter":{
"input":"$$child.grandChildren", // This is wrong
"as":"gc",
"cond":{
"$eq":[
"$$gc.childId",
"$$gc._id" // This is wrong
]
}
}
},
"as":"gc",
"in":{
"Id":"$$gc._id",
"Name":"$$gc.name"
}
}
}
}
}
},
"_id":0
}
}
]
);
But the proper aggregate would be (2 lines commented with // correct)
db.parents.aggregate(
[
{
"$lookup":{
"from":"children",
"localField":"_id",
"foreignField":"parentId",
"as":"children"
}
},
{
"$lookup":{
"from":"grandChildren",
"let":{
"children":"$children"
},
"pipeline":[
{
"$match":{
"$expr":{
"$and":[
{
"$in":[
"$childId",
"$$children._id"
]
}
]
}
}
}
],
"as":"grandChildren"
}
},
{
"$project":{
"Id":"$_id",
"ParentName":"$name",
"Children":{
"$map":{
"input":"$children",
"as":"child",
"in":{
"Id":"$$child._id",
"Name":"$$child.name",
"GrandChildren":{
"$map":{
"input":{
"$filter":{
"input":"$grandChildren", // correct
"as":"gc",
"cond":{
"$eq":[
"$$gc.childId",
"$$child._id" // correct
]
}
}
},
"as":"gc",
"in":{
"Id":"$$gc._id",
"Name":"$$gc.name"
}
}
}
}
}
},
"_id":0
}
}
]
);
Attached is the .NET Core solution, and database archive
- depends on
-
CSHARP-3314 LINQ3: Implement Known Serializers strategy
-
- Closed
-