我一直在寻找,但我找不到任何好的答案。我有 n-deep 树,我存储在数据库中,我想填充所有父母,所以最后我得到完整的树
node
-parent
-parent
.
.
-parent
到目前为止,我填充到第2级,正如我所提到的,我需要达到 n 的水平。
Node.find().populate('parent').exec(function (err, items) {
if (!err) {
Node.populate(items, {path: 'parent.parent'}, function (err, data) {
return res.send(data);
});
} else {
res.statusCode = code;
return res.send(err.message);
}
});
答案 0 :(得分:17)
您现在可以执行此操作(使用https://www.mongodb.com/blog/post/introducing-version-40-mongoose-nodejs-odm)
var mongoose = require('mongoose');
// mongoose.Promise = require('bluebird'); // it should work with native Promise
mongoose.connect('mongodb://......');
var NodeSchema = new mongoose.Schema({
children: [{type: mongoose.Schema.Types.ObjectId, ref: 'Node'}],
name: String
});
var autoPopulateChildren = function(next) {
this.populate('children');
next();
};
NodeSchema
.pre('findOne', autoPopulateChildren)
.pre('find', autoPopulateChildren)
var Node = mongoose.model('Node', NodeSchema)
var root=new Node({name:'1'})
var header=new Node({name:'2'})
var main=new Node({name:'3'})
var foo=new Node({name:'foo'})
var bar=new Node({name:'bar'})
root.children=[header, main]
main.children=[foo, bar]
Node.remove({})
.then(Promise.all([foo, bar, header, main, root].map(p=>p.save())))
.then(_=>Node.findOne({name:'1'}))
.then(r=>console.log(r.children[1].children[0].name)) // foo
简单的替代方案,没有Mongoose:
function upsert(coll, o){ // takes object returns ids inserted
if (o.children){
return Promise.all(o.children.map(i=>upsert(coll,i)))
.then(children=>Object.assign(o, {children})) // replace the objects children by their mongo ids
.then(o=>coll.insertOne(o))
.then(r=>r.insertedId);
} else {
return coll.insertOne(o)
.then(r=>r.insertedId);
}
}
var root = {
name: '1',
children: [
{
name: '2'
},
{
name: '3',
children: [
{
name: 'foo'
},
{
name: 'bar'
}
]
}
]
}
upsert(mycoll, root)
const populateChildren = (coll, _id) => // takes a collection and a document id and returns this document fully nested with its children
coll.findOne({_id})
.then(function(o){
if (!o.children) return o;
return Promise.all(o.children.map(i=>populateChildren(coll,i)))
.then(children=>Object.assign(o, {children}))
});
const populateParents = (coll, _id) => // takes a collection and a document id and returns this document fully nested with its parents, that's more what OP wanted
coll.findOne({_id})
.then(function(o){
if (!o.parent) return o;
return populateParents(coll, o.parent))) // o.parent should be an id
.then(parent => Object.assign(o, {parent})) // replace that id with the document
});
答案 1 :(得分:11)
另一种方法是利用Model.populate()
返回承诺的事实,并且你可以履行另一个承诺的承诺。
您可以通过以下方式递归填充相关节点:
Node.findOne({ "_id": req.params.id }, function(err, node) {
populateParents(node).then(function(){
// Do something with node
});
});
populateParents
可能如下所示:
var Promise = require('bluebird');
function populateParents(node) {
return Node.populate(node, { path: "parent" }).then(function(node) {
return node.parent ? populateParents(node.parent) : Promise.fulfill(node);
});
}
这不是最高效的方法,但如果你的N很小,那就行了。
答案 2 :(得分:4)
现在Mongoose 4
可以做到这一点。现在你可以进行更深层次的递归。
示例强>
User.findOne({ userId: userId })
.populate({
path: 'enrollments.course',
populate: {
path: 'playlists',
model: 'Playlist',
populate: {
path: 'videos',
model: 'Video'
}
}
})
.populate('degrees')
.exec()
您可以从here找到Mongoose Deep Populate的官方文档。
答案 3 :(得分:2)
请不要:)
没有好方法可以做到这一点。即使你做了一些map-reduce,它也会有糟糕的性能和分片问题,如果你有它或者需要它。
Mongo作为NoSQL数据库非常适合存储树文档。您可以存储整个树,然后使用map-reduce从中获取一些特定的叶子,如果您没有很多“查找特定叶子”查询。如果这对您不起作用,请使用两个集合:
简化树结构:{_id: "tree1", tree: {1: [2, {3: [4, {5: 6}, 7]}]}}
。数字只是节点的ID。这样,您将在一个查询中获得整个文档。然后,您只需提取所有ID并运行第二个查询。
节点:{_id: 1, data: "something"}
,{_id: 2, data: "something else"}
。
然后你可以编写简单的循环函数,它将第一个集合中的节点ID替换为第二个集合中的数据。 2个查询和简单的客户端处理。
小更新:
您可以将第二个集合扩展为更灵活:
{_id: 2, data: "something", children:[3, 7], parents: [1, 12, 13]}
通过这种方式,您可以从任何叶子开始搜索。然后,使用map-reduce来到树的这一部分的顶部或底部。
答案 4 :(得分:0)
我尝试了@ fzembow的解决方案,但它似乎从最深的填充路径返回对象。在我的情况下,我需要递归填充对象,但然后返回相同的对象。我这样做了:
// Schema definition
const NodeSchema = new Schema({
name: { type: String, unique: true, required: true },
parent: { type: Schema.Types.ObjectId, ref: 'Node' },
});
const Node = mongoose.model('Node', NodeSchema);
// method
const Promise = require('bluebird');
const recursivelyPopulatePath = (entry, path) => {
if (entry[path]) {
return Node.findById(entry[path])
.then((foundPath) => {
return recursivelyPopulatePath(foundPath, path)
.then((populatedFoundPath) => {
entry[path] = populatedFoundPath;
return Promise.resolve(entry);
});
});
}
return Promise.resolve(entry);
};
//sample usage
Node.findOne({ name: 'someName' })
.then((category) => {
if (category) {
recursivelyPopulatePath(category, 'parent')
.then((populatedNode) => {
// ^^^^^^^^^^^^^^^^^ here is your object but populated recursively
});
} else {
...
}
})
注意它效率不高。如果您需要经常或深层次地运行此类查询,那么您应该重新考虑您的设计
答案 5 :(得分:0)
这是解决caub的问题和解决方案的更直接的方法。一开始我觉得很难理解,所以我将这个版本放在一起。
重要的是,您需要同时使用“ findOne”和“ find”中间件挂钩才能使该解决方案正常工作。 *
*另外,模型定义必须位于中间件定义之后*
const mongoose = require('mongoose');
const NodeSchema = new mongoose.Schema({
children: [mongoose.Schema.Types.ObjectId],
name: String
});
const autoPopulateChildren = function (next) {
this.populate('children');
next();
};
NodeSchema
.pre('findOne', autoPopulateChildren)
.pre('find', autoPopulateChildren)
const Node = mongoose.model('Node', NodeSchema)
const root = new Node({ name: '1' })
const main = new Node({ name: '3' })
const foo = new Node({ name: 'foo' })
root.children = [main]
main.children = [foo]
mongoose.connect('mongodb://localhost:27017/try', { useNewUrlParser: true }, async () => {
await Node.remove({});
await foo.save();
await main.save();
await root.save();
const result = await Node.findOne({ name: '1' });
console.log(result.children[0].children[0].name);
});
答案 6 :(得分:0)
也许晚了很多,但猫鼬对此有一些文档:
我认为第一个更适合你,因为你正在寻找填充父母。
使用该解决方案,您可以使用一个正则表达式查询,搜索与您设计的输出树匹配的所有文档。
您将使用此架构设置文档:
Tree: {
name: String,
path: String
}
Paths 字段将是树中的绝对路径:
/mens
/mens/shoes
/mens/shoes/boots
/womens
/womens/shoes
/womens/shoes/boots
例如,您可以使用一个查询搜索节点“/mens/shoes”的所有子节点:
await Tree.find({ path: /^\/mens/shoes })
它将返回路径以 /mens/shoes 开头的所有文档:
/mens/shoes
/mens/shoes/boots
然后你只需要一些客户端逻辑来安排它在树结构中(一个map-reduce)