插入另一个Schema引用的多个文档

时间:2016-02-08 01:23:51

标签: mongodb mongoose mongodb-query

我有以下两种模式:

var SchemaOne = new mongoose.Schema({
  id_headline: { type: String, required: true },
  tags: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Tag' }]
});

var tagSchema = new mongoose.Schema({
  _id: { type: String, required: true, index: { unique: true } }, // value
  name: { type: String, required: true }
});

如您所见,在第一个模式中有一个对第二个模式的引用数组。

我的问题是:

假设在我的后端服务器中,我收到一个标签数组(只是id),并且在创建SchemaOne文档之前,我需要验证收到的标签是否已存在于数据库中,如果没有,则创建它们。只有在将所有标记存储在数据库中之后,我才可以将此接收的数组分配给要创建的SchemaOne文档的标记数组。

我不确定如何实现这个?你能伸出援助之手吗?

1 个答案:

答案 0 :(得分:1)

因此,我们假设您已将输入发送到您的服务器,基本上解析为:

var input = {
    "id_headline": "title",
    "tags": [
        { "name": "one" },
        { "name": "two" }
    ]
};

正如您所说,您不确定是否存在任何“标签”条目,但当然“名称”对于查找关联对象也是唯一的。

您基本上要做的就是“查找”“标签”中的每个元素,并将文档与“标签”模型中的对象一起使用。这里理想的方法是.findOneAndUpdate(),“upsert”选项设置为true。这将在未找到它的集合中创建文档,并且无论如何都将使用创建的引用返回文档内容。

请注意,在保存主“SchemaOne”对象之前,您希望确保在首先解析这些数组项。 async库有一些方法可以帮助构建它:

async.waterfall(
    [
        function(callback) {
            async.map(input.tags,function(tag,callback) {
                Tag.findOneAndUpdate(
                    { "name": tag.name },
                    { "$setOnInsert": { "name": tag.name } },
                    { "upsert": true, "new": true },
                    callback
                )
            },callback);
        },
        function(tags,callback) {
            Model.findOneAndUpdate(
                { "id_headline": input.id_headline },
                { "$addToSet": { 
                    "tags": { "$each": tags.map(function(tag) { return tag._id }) }
                }},
                { "upsert": true, "new": true },
                callback
            )
        }
    ],
    function(err,result) {
        // if err then do something to report it, otherwise it's done.
    }
)

因此async.waterfall是一种特殊的流控制方法,它将从参数数组中指定的每个函数返回的结果传递给下一个,直到执行结束,您可以选择传入列表中最终函数的结果。它基本上是“级联”或“瀑布”结果到每一步。这是希望将“标签”创建的结果传递给主模型创建/修改。

第一个执行阶段中的async.map查看输入数组中的每个元素。因此,对于“tags”中包含的每个项目,调用.findOneAndUpdate()方法以查找并可能创建(如果未找到)集合中指定的“tag”条目。

由于.map()的输出将是这些文档的数组,因此它只是传递到下一个阶段。因此,每次迭代都会返回一个文档,当迭代完成后,您将获得所有文档。

.findOneAndUpdate()与“upsert”的下一个用法是可选的,当然认为具有匹配“id_headline”的文档可能存在也可能不存在。同样的情况也是如此,如果它在那里则处理“更新”,如果不存在则则简单地创建它。如果知道文档不存在,您可以选择.insert().create(),但“更新”操作会提供一些有趣的选项。

这里是$addToSet的用法,如果文档已经存在,那么指定的项目将“添加”到已经存在的任何内容,当然还有“set”,任何项目已经存在礼物不会是新增的。请注意,当使用原子运算符添加到数组时,此处仅需要_id字段,因此使用.map()函数。

关于“更新”的另一种情况可能是使用$set原子操作简单地“替换”数组内容,如果它意图只存储输入中提到的那些项而不是其他项。< / p>

以类似的方式,在“标签”中“创建”/“查找”项目时显示的$setOnInsert确保在“创建/插入”对象时只有实际的“修改”,并且删除服务器上的一些写入开销。

因此,至少对“标签”条目使用.findOneAndUpdate()的基本原则是处理此问题的最佳方式。这避免了双重处理,例如:

  • 查询文档是否按名称存在
  • 如果未返回任何结果,则发送附加语句以创建一个

这意味着对来回通信的数据库进行两次操作,使用"upserts"的操作简化为每个项目的单个请求。