Details
-
Bug
-
Resolution: Fixed
-
Major - P3
-
None
-
None
Description
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
Attachments
Issue Links
- depends on
-
CSHARP-3314 LINQ3: Implement Known Serializers strategy
-
- Closed
-