-
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
-