关于使用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吗?


3 回复

关于使用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.ObjectIdref 关键字,可以将子文档作为独立的文档存储在数据库中。这种方式避免了无限嵌套的问题,同时保持了数据的一致性和灵活性。

  • 动态添加子文档:当需要向父文档添加子文档时,只需创建一个新的子文档并将其 _id 添加到父文档的 children 数组中即可。

这种方法不仅解决了嵌套层级不确定的问题,还提升了性能和可维护性。如果你希望更进一步,还可以探索使用插件如 mongoose-treemongoose-deep-populate 来简化树形结构的管理和查询。

回到顶部