关于使用Mongoose进行无限嵌套子文件的问题(Nodejs)
关于使用Mongoose进行无限嵌套子文件的问题(Nodejs)
最近一直在用Mongoose进行数据库交互,但出现了一个问题。 我原本是这样使用Schema
var fatherSchema = new Schema{
//一些数据结构
children: [chilldrenSchema]
};
var chilldrenSchema = new Schema{
//一些数据结构
children: [chilldrenSchema]
};
数据库正常存储好father(首先children是无值得)后,也能正常保存children(使用查询回调中返回father,添加father.children=一个json,最后再使用.save),无法保存father.children[0].newChildren。(也是使用的是find-save,)
mogoose的issue里说 是Schema定义顺序的问题,也就是childrenSchema要放在fatherSchema前面,若childrenSchema还嵌套了一个,就需要在在childrenShema前面定义好secondChildrenSchema。 我丑陋的解决方法就是这样从后往前依次声明 var ninethChildrenSchema = {} ;… var secondChildrenShema = {}; 那么问题来了,我的这些孩子的层级在实际应用中是不确定的(可能特别多也可能只有3-4层),有什么优美的方法可以动态声明Schema吗?
关于使用Mongoose进行无限嵌套子文件的问题(Nodejs)
最近一直在用Mongoose进行数据库交互,但遇到了一个问题。我原本是这样使用Schema:
var fatherSchema = new mongoose.Schema({
// 一些数据结构
children: [childSchema]
});
var childSchema = new mongoose.Schema({
// 一些数据结构
children: [childSchema]
});
数据库正常存储好father
(首先children
是无值的)后,也能正常保存children
(使用查询回调中返回father
,添加father.children
为一个JSON,最后再使用.save()
)。但是无法保存father.children[0].newChildren
。(也是使用的是find
-save
)
在Mongoose的GitHub issue中提到,这是由于Schema定义顺序的问题。也就是说,childSchema
需要在fatherSchema
之前定义,如果childSchema
还嵌套了另一个子Schema,就需要在childSchema
之前定义好。
我曾经尝试的丑陋解决方法是从后往前依次声明所有的子Schema:
var ninthChildSchema = new mongoose.Schema({});
var eighthChildSchema = new mongoose.Schema({});
var seventhChildSchema = new mongoose.Schema({});
// ...
var firstChildSchema = new mongoose.Schema({});
var childSchema = new mongoose.Schema({
// 一些数据结构
children: [firstChildSchema]
});
var fatherSchema = new mongoose.Schema({
// 一些数据结构
children: [childSchema]
});
这种方法虽然能解决问题,但在实际应用中,这些子层级的数量是不确定的(可能特别多也可能只有3-4层)。那么如何优雅地处理这种动态声明Schema的问题呢?
解决方案
为了实现动态声明Schema,我们可以使用递归的方法来定义Schema。具体来说,我们可以创建一个函数来生成嵌套的Schema:
function createNestedSchema(depth = 0, maxDepth = 5) {
const schema = {
name: String,
children: depth < maxDepth ? [createNestedSchema(depth + 1, maxDepth)] : []
};
return new mongoose.Schema(schema);
}
const fatherSchema = new mongoose.Schema({
name: String,
children: [createNestedSchema()]
});
在这个例子中,我们定义了一个递归函数createNestedSchema
,它会根据给定的最大深度maxDepth
来创建嵌套的Schema。这样我们就可以灵活地定义不同深度的嵌套Schema,而无需手动声明每个层次的Schema。
示例代码
const mongoose = require('mongoose');
function createNestedSchema(depth = 0, maxDepth = 5) {
const schema = {
name: String,
children: depth < maxDepth ? [createNestedSchema(depth + 1, maxDepth)] : []
};
return new mongoose.Schema(schema);
}
const fatherSchema = new mongoose.Schema({
name: String,
children: [createNestedSchema()]
});
const Father = mongoose.model('Father', fatherSchema);
async function main() {
await mongoose.connect('mongodb://localhost/test', { useNewUrlParser: true, useUnifiedTopology: true });
const father = new Father({
name: 'John Doe',
children: [
{
name: 'Child 1',
children: [
{
name: 'Grandchild 1'
}
]
}
]
});
await father.save();
console.log('Saved successfully!');
}
main().catch(err => console.error(err));
通过这种方式,我们可以动态地创建任意深度的嵌套Schema,并且代码更加简洁和可维护。
具体细节我写在 博客第二段了。
在处理无限嵌套子文档时,直接使用Mongoose的内置Schema定义确实存在限制,尤其是当嵌套层级不确定时。为了解决这个问题,可以考虑使用引用(references)而非直接嵌套Schema。具体来说,你可以使用mongoose.Schema.Types.ObjectId
来引用其他文档。
示例代码
定义父文档模型
const mongoose = require('mongoose');
const ChildSchema = new mongoose.Schema({
// 子文档的字段定义
});
const FatherSchema = new mongoose.Schema({
children: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Child' }]
});
使用引用
当你需要保存子文档时,可以先创建子文档,然后将其 _id
添加到父文档的 children
数组中:
const Child = mongoose.model('Child', ChildSchema);
const Father = mongoose.model('Father', FatherSchema);
async function addChildToFather(fatherId, childData) {
const child = await new Child(childData).save();
await Father.findByIdAndUpdate(
fatherId,
{ $push: { children: child._id } },
{ new: true }
);
}
解释
-
使用引用:通过使用
mongoose.Schema.Types.ObjectId
和ref
关键字,可以将子文档作为独立的文档存储在数据库中。这种方式避免了无限嵌套的问题,同时保持了数据的一致性和灵活性。 -
动态添加子文档:当需要向父文档添加子文档时,只需创建一个新的子文档并将其
_id
添加到父文档的children
数组中即可。
这种方法不仅解决了嵌套层级不确定的问题,还提升了性能和可维护性。如果你希望更进一步,还可以探索使用插件如 mongoose-tree
或 mongoose-deep-populate
来简化树形结构的管理和查询。