首页 > 解决方案 > 猫鼬保存多级嵌套对象

问题描述

我尝试实现我的第一个基于猫鼬的 REST API。

我现在尝试了几天,但无法启动并运行它。我想用一组控件保存调查,并为每个控件保存一组 controlProperties。

在不同的场景中,我用控件数组保存调查但没有 controlProperties,有时甚至没有控件数组。

有人可以帮我理解我的错误吗?

非常感谢。

结构如下:

调查 -- 控件数组 -- controlProperty 数组

我的架构文件是:

调查.js

const mongoose = require('mongoose');
const Control = require('./control');

const surveySchema = mongoose.Schema({
    _id: mongoose.Schema.Types.ObjectId,
    name: {
        type: String,
        required: true,
        min: 4,
        max: 255
    },
    description: {
        type: String,
        required: false,
        max: 1000
    },
   closeDate: {
       type: Date,
       required: false
   },
   controls: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Control' }]
});

module.exports = mongoose.model('Survey', surveySchema);

控制.js

const mongoose = require('mongoose');
const Survey = require('./survey');

const controlSchema = new mongoose.Schema({
    _id: mongoose.Schema.Types.ObjectId,
    survey: {type: mongoose.Schema.Types.ObjectId, ref: 'Survey'},
    controlType: {
        type: String,
        required: true
    },
    name: {
        type: String, 
        required: true
    },
    isInput: {
        type: Boolean,
        required: true

    },
    order: {
        type: Number,
        required: true
    },
    controlProperties: [{ type: mongoose.Schema.Types.ObjectId, ref: 'ControlProperty' }]
});


module.exports = mongoose.model('Control', controlSchema);

控制属性.js

const mongoose = require('mongoose');
const Control = require('./control');

mongoose.Schema.Types.String.checkRequired(v => v != null);

const controlPropertySchema = new mongoose.Schema({
    _id: mongoose.Schema.Types.ObjectId,
    control: { type: mongoose.Schema.Types.ObjectId, ref: 'Control' },
    propertyName: {
        type: String,
        required: true
    },
    propertyValue: {
        type: String,
        required: true
    },
    order: {
        type: Number,
        required: true
    }

})

module.exports = mongoose.model('ControlProperty', controlPropertySchema);

我接收帖子数据的 node.js 代码是这样的:

/routes/survey.js

router.post("/", (req, res, next) => {


        Survey.find({ _id: req.body._id })
            .exec()
            .then(result => {
                if (result.length >= 1) {
                    return res.status(409).json({
                        message: "Survey exists"
                    });
                } else {

                    const survey = new Survey({
                        _id: new mongoose.Types.ObjectId(),
                        name: req.body.name,
                        description: req.body.description,
                        closeDate: req.body.closeDate,
                        order: req.body.order
                    });


                    let controlData = req.body.controls;
                    let arControls = [];

                    if(controlData != null) {

                        for (var i = 0, clen = controlData.length; i < clen; i++) {
                            let c = controlData[i];
                            let control = new Control({
                                _id: new mongoose.Types.ObjectId(),
                                controlType: c.controlType,
                                name: c.name,
                                isInput: c.isInput,
                                order: c.order
                            })

                            let controlPropertyData = c.controlProperties;
                            let arControlProperty = [];

                            if(controlPropertyData != null) {

                                for (var j = 0, cplen = controlPropertyData.length; j < cplen; j++) {
                                    let cp = controlPropertyData[j];
                                    let controlProperty = new ControlProperty({
                                        _id: new mongoose.Types.ObjectId(),
                                        propertyName: cp.propertyName,
                                        propertyValue: cp.propertyValue, 
                                        order: cp.order                                
                                    })

                                    arControlProperty.push(controlProperty);
                                }

                                ControlProperty.insertMany(arControlProperty, forceServerObjectId=true,function (err,data) {
                                    if(err!=null){
                                        return console.log(err);
                                    }
                                    console.log(" " + j + " controlProperties for control " + i +  " saved");

                                    control.controlProperties = data;
                                    console.log(data);
                                });


                            } 
                            arControls.push(control);


                        }

                        Control.insertMany(arControls, forceServerObjectId=true,function (err,data) {
                            if(err!=null){
                                return console.log(err);
                            }
                            survey.controls = data;

                            console.log("controls saved");
                            console.log(data);
                        });
                    }

                    survey
                        .save()
                        .then(result => {
                            console.log("survey saved");
                            res.status(201).json(survey);
                        })
                        .catch(err => {
                            console.log(err);
                            res.status(500).json({
                                error: err
                            });
                        });
                }

        });
    });

示例帖子数据:

{   
    "name": "TestSurvey",
    "description": "This is a test survey",
    "controls":  [
        {       
            "controlType": "Label",
            "name": "Label1",
            "isInput": false,
            "order": 1,
            "controlProperties": [
                {
                    "propertyName": "FontSize", 
                    "propertyValue": "Large",
                    "order": 1
                },
                {
                    "propertyName": "BackgroundColor", 
                    "propertyValue": "Darkgreen",
                    "order": 2
                },
                {
                    "propertyName": "FontAttributes", 
                    "propertyValue": "Bold",
                    "order": 3
                },
                {
                    "propertyName": "HorizontalOptions", 
                    "propertyValue": "Fill",
                    "order": 4
                },
                {
                    "propertyName": "HorizontalTextAlignment", 
                    "propertyValue": "Center",
                    "order": 5
                },
                {
                    "propertyName": "TextColor", 
                    "propertyValue": "White",
                    "order": 6
                },
                {
                    "propertyName": "Text", 
                    "propertyValue": "Paris Work-Life Balance",
                    "order": 7
                }
            ]
        },
        {       
            "controlType": "Label",
            "name": "Label2",
            "isInput": false,
            "order": 2,
            "controlProperties": [
                {
                    "propertyName": "FontSize", 
                    "propertyValue": "Medium",
                    "order": 1
                },
                {
                    "propertyName": "Margin", 
                    "propertyValue": "20,0,20,0",
                    "order": 2
                },
                {
                    "propertyName": "FontAttributes", 
                    "propertyValue": "Bold",
                    "order": 3
                },
                {
                    "propertyName": "HorizontalOptions", 
                    "propertyValue": "StartAndExpand",
                    "order": 4
                },
                {
                    "propertyName": "HorizontalTextAlignment", 
                    "propertyValue": "Center",
                    "order": 5
                },
                {
                    "propertyName": "Text", 
                    "propertyValue": "Dear [[FirstName]], \nwas your workload on the case 12345 - 67(Company) compliant to the BCG Work Life Balance Ground Rules over the past week ?",
                    "order": 6
                }
            ]
        },
        {
            "controlType": "PWLBControl",
            "name": "PWLB1",
            "isInput": true,
            "order": 3,
            "controlProperties": [
                {
                        "propertyName": "Margin", 
                        "propertyValue": "20,0,20,0",
                        "order": 1
                }
            ]
        },      
        {
            "controlType": "Button",
            "name": "button1",
            "isInput": false,
            "order": 4,
            "controlProperties": [
                {
                    "propertyName": "Text", 
                    "propertyValue": "Submit",
                    "order": 1
                },
                        {
                    "propertyName": "HorizontalOptions", 
                    "propertyValue": "StartAndExpand",
                    "order": 2
                },
                {
                    "propertyName": "IsSubmitButton",
                    "propertyValue": true,
                    "order": 3
                }
            ]
        },
        {
            "controlType": "Image",
            "name": "image1",
            "isInput": false,
            "order": 5,
            "controlProperties": [
                {
                    "propertyName": "Source",
                    "propertyValue": "",
                    "order": 1
                },
                {
                    "propertyName": "VerticalOptions",
                    "propertyValue": "End",
                    "order": 2
                }
            ]
        }
    ]
}

标签: node.jsmongodbexpressmongoose

解决方案


无需为 controlProperties 和控件保留单独的集合。可以嵌入controlPropertySchema里面controlSchema,也可以嵌入controlSchema里面surveySchema。所以最后我们将只有一个集合进行调查。

这将使在一次插入操作中创建调查成为可能。您还可以在一次读取操作中获取所有调查信息。

另外还有几点建议:

  1. 最好不要将 _id 字段添加到模式中,mongodb 会处理它。
  2. 如果存在具有给定 _id 的调查,我看到您使用它。最好使用name字段来检查调查是否已经存在。
  3. minmax选项用于数字类型,用于字符串类型 minlengthmaxlength使用。文档

所以surveySchema必须看起来像这样:

const mongoose = require("mongoose");

const controlPropertySchema = new mongoose.Schema({
  // _id: mongoose.Schema.Types.ObjectId,
  // control: { type: mongoose.Schema.Types.ObjectId, ref: "Control" },
  propertyName: {
    type: String,
    required: true
  },
  propertyValue: {
    type: String,
    required: true
  },
  order: {
    type: Number,
    required: true
  }
});

const controlSchema = new mongoose.Schema({
  //_id: mongoose.Schema.Types.ObjectId,
  //  survey: {type: mongoose.Schema.Types.ObjectId, ref: 'Survey'},
  controlType: {
    type: String,
    required: true
  },
  name: {
    type: String,
    required: true
  },
  isInput: {
    type: Boolean,
    required: true
  },
  order: {
    type: Number,
    required: true
  },
  controlProperties: [controlPropertySchema]
  //controlProperties: [{ type: mongoose.Schema.Types.ObjectId, ref: "ControlProperty" }]
});

const surveySchema = mongoose.Schema({
  // _id: mongoose.Schema.Types.ObjectId,
  name: {
    type: String,
    required: true,
    minlength: 4,
    maxlength: 255
  },
  description: {
    type: String,
    required: false,
    maxlength: 1000
  },
  closeDate: {
    type: Date,
    required: false
  },
  controls: [controlSchema]
  // controls: [{ type: mongoose.Schema.Types.ObjectId, ref: "Control" }]
});

module.exports = mongoose.model("Survey", surveySchema);

现在我们可以使用这个发布路径创建一个调查:(请注意,我们不进行任何转换,因为我们的请求正文的结构与surveySchema相同)

router.post("/surveys", async (req, res) => {
  try {
    let survey = await Survey.findOne({ name: req.body.name });

    if (survey) {
      return res.status(400).send("A survey already exists with that name");
    }

    const result = await Survey.create(req.body);
    res.send(result);
  } catch (err) {
    console.log(err);

    if (err.name === "ValidationError") {
      return res.status(400).send(err.errors);
    }
    res.status(500).send("Something went wrong");
  }
});

在您的请求正文中,有一个空的propertyValue,所以我将其更改为"propertyValue": "I was empty",还有一个布尔值而不是字符串,所以我将其更改为"propertyValue": "I was true"

您可以使用此更正的请求正文:

{
    "name": "TestSurvey",
    "description": "This is a test survey",
    "controls": [
        {
            "controlType": "Label",
            "name": "Label1",
            "isInput": false,
            "order": 1,
            "controlProperties": [
                {
                    "propertyName": "FontSize",
                    "propertyValue": "Large",
                    "order": 1
                },
                {
                    "propertyName": "BackgroundColor",
                    "propertyValue": "Darkgreen",
                    "order": 2
                },
                {
                    "propertyName": "FontAttributes",
                    "propertyValue": "Bold",
                    "order": 3
                },
                {
                    "propertyName": "HorizontalOptions",
                    "propertyValue": "Fill",
                    "order": 4
                },
                {
                    "propertyName": "HorizontalTextAlignment",
                    "propertyValue": "Center",
                    "order": 5
                },
                {
                    "propertyName": "TextColor",
                    "propertyValue": "White",
                    "order": 6
                },
                {
                    "propertyName": "Text",
                    "propertyValue": "Paris Work-Life Balance",
                    "order": 7
                }
            ]
        },
        {
            "controlType": "Label",
            "name": "Label2",
            "isInput": false,
            "order": 2,
            "controlProperties": [
                {
                    "propertyName": "FontSize",
                    "propertyValue": "Medium",
                    "order": 1
                },
                {
                    "propertyName": "Margin",
                    "propertyValue": "20,0,20,0",
                    "order": 2
                },
                {
                    "propertyName": "FontAttributes",
                    "propertyValue": "Bold",
                    "order": 3
                },
                {
                    "propertyName": "HorizontalOptions",
                    "propertyValue": "StartAndExpand",
                    "order": 4
                },
                {
                    "propertyName": "HorizontalTextAlignment",
                    "propertyValue": "Center",
                    "order": 5
                },
                {
                    "propertyName": "Text",
                    "propertyValue": "Dear [[FirstName]], \nwas your workload on the case 12345 - 67(Company) compliant to the BCG Work Life Balance Ground Rules over the past week ?",
                    "order": 6
                }
            ]
        },
        {
            "controlType": "PWLBControl",
            "name": "PWLB1",
            "isInput": true,
            "order": 3,
            "controlProperties": [
                {
                    "propertyName": "Margin",
                    "propertyValue": "20,0,20,0",
                    "order": 1
                }
            ]
        },
        {
            "controlType": "Button",
            "name": "button1",
            "isInput": false,
            "order": 4,
            "controlProperties": [
                {
                    "propertyName": "Text",
                    "propertyValue": "Submit",
                    "order": 1
                },
                {
                    "propertyName": "HorizontalOptions",
                    "propertyValue": "StartAndExpand",
                    "order": 2
                },
                {
                    "propertyName": "IsSubmitButton",
                    "propertyValue": "I was true",
                    "order": 3
                }
            ]
        },
        {
            "controlType": "Image",
            "name": "image1",
            "isInput": false,
            "order": 5,
            "controlProperties": [
                {
                    "propertyName": "Source",
                    "propertyValue": "I was empty",
                    "order": 1
                },
                {
                    "propertyName": "VerticalOptions",
                    "propertyValue": "End",
                    "order": 2
                }
            ]
        }
    ]
}

推荐阅读