Node.js 猫鼬
我已经搜索了一段时间,没有找到任何好的答案。我有一个n-deep树,我将它存储在DB中,我想填充所有的父树,所以最后我得到了完整的树Node.js 猫鼬,node.js,mongodb,mongoose,Node.js,Mongodb,Mongoose,我已经搜索了一段时间,没有找到任何好的答案。我有一个n-deep树,我将它存储在DB中,我想填充所有的父树,所以最后我得到了完整的树 node -parent -parent . . -parent 到目前为止,我填充到第2级,正如我提到的,我需要进入第n级 Node.find().populate('parent').exec(function (err, items) { if (!err) { Node.populate(items, {pa
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);
}
});
只是不要:)
没有什么好办法可以做到这一点。即使你做了一些MapReduce,如果你有或者需要的话,它也会有糟糕的性能和切分问题
Mongo作为NoSQL数据库非常适合存储树文档。如果没有太多的“查找特定叶”查询,您可以存储整个树,然后使用map reduce从中获取一些特定叶。如果这不适合您,请选择两个系列:
{u id:“tree1”,树:{1:[2,{3:[4,{5:6},7]}}
。数字只是节点的ID。这样,您将在一个查询中获得整个文档。然后,您只需提取所有ID并运行第二个查询{u id:1,数据:“something”}
,{u id:2,数据:“something”}
{u id:2,数据:“某物”,孩子:[3,7],父母:[1,12,13]}
这样你就可以从任何一片叶子开始搜索。然后,使用map reduce到达树的这一部分的顶部或底部。另一种方法是利用
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很小,这将起作用。您现在可以(使用)
简单的选择,没有猫鼬:
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
});
我尝试了@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 {
...
}
})
小心,这不是很有效。如果您需要经常或深入地运行此类查询,那么您应该重新考虑您的设计,现在使用
mongoose4
,这是可以做到的。现在,您可以递归到比单个级别更深的级别
示例
User.findOne({ userId: userId })
.populate({
path: 'enrollments.course',
populate: {
path: 'playlists',
model: 'Playlist',
populate: {
path: 'videos',
model: 'Video'
}
}
})
.populate('degrees')
.exec()
您可以从中找到Mongoose Deep Populate的官方文档。这是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);
});
*此外,模型定义必须在中间件定义之后*
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);
});
这可能要晚很多,但mongoose对此有一些文档:
树:{
名称:String,
路径:字符串
}
路径字段将是树中的绝对路径:
/mens
/mens/shoes
/mens/shoes/boots
/womens
/womens/shoes
/womens/shoes/boots
例如,您可以通过一个查询搜索节点“/mens/shoes”的所有子节点:
wait Tree.find({path://^\/mens/shoes})
它将返回路径以/mens/shoes开头的所有文档:
/mens/shoes
/mens/shoes/boots
然后,您只需要一些客户端逻辑就可以将其排列成树结构(MapReduce)谢谢。这不是我想要的。但无论如何还是要谢谢你。我会考虑的。Mongoose deep populate是一个更好的解决方案。你可以在我下面的答案中找到一个例子。这个方法非常有效,我一直在寻找这个解决方案。谢谢你,你应该为此获得一个奖项谢谢你,你救了我一天。你能告诉我在哪里可以找到这方面的参考资料吗?感谢您,但文档不够全面(我的意思是:)这是一个救命稻草。多谢。