使用数组进行Upsert而不创建重复项

时间:2017-06-20 18:13:55

标签: node.js mongodb mongoose mongoose-schema

我很难'插入'到我的阵列。下面的代码在我的answers数组中创建了一些我绝对不想要的重复项,现在很明显$push将不起作用。我已经尝试过使用我在SO上看到的不同方法一段时间,但没有一个对我有用。使用此网络应用程序,用户可以在网站上查看问题,并回答“是”或“否”response,并允许他们在任何时候更改(插入)他们的response意思是在不同的时间在db上发生一种upsert。怎么绕过这个?

var QuestionSchema = Schema ({
    title       :String,
    admin       :{type: String, ref: 'User'},
    answers     :[{type:  Schema.Types.Mixed, ref: 'Answer'}]
});

var AnswerSchema = Schema ({
    _question   :{type: ObjectId, ref: 'Question'},
    employee    :{type: String, ref: 'User'},
    response    :String,
    isAdmin     :{type: Boolean, ref: 'User'}
})

var UserSchema = Schema({
    username    : String,
    isAdmin     : {type: Boolean, default: false}
});

module.exports = mongoose.model('Question', QuestionSchema);
module.exports = mongoose.model('Answer', AnswerSchema);
module.exports = mongoose.model('User', UserSchema);


           Question.update(
                {_id: req.body.id},
                {$push: {answers: {_question: req.body.id, 
                                    employee: req.body.employee, 
                                    response: req.body.response, //this variable changes (yes/no/null)
                                     isAdmin: req.body.isAdmin}}},
                {safe: true, upsert: true},
                function(err, model) {

                }
            );

2 个答案:

答案 0 :(得分:2)

在我看来,你似乎有点困惑,它反映在你的架构中。你似乎没有完全掌握"嵌入式"之间的差异。和"引用"因为你的架构实际上是无效的#34; mash"这两种技术。

可能最好引导您完成这两项工作。

嵌入式模型

因此,您应该使用更多类似的内容来代替您定义的架构:

var QuestionSchema = Schema ({
    title       :String,
    admin       :{type: String, ref: 'User'},
    answers     :[AnswerSchema]
});

var AnswerSchema = Schema ({
    employee    :{type: String, ref: 'User'},
    response    :String,
    isAdmin     :{type: Boolean, ref: 'User'}
})

mongoose.model('Question', questionSchema);

注意Question唯一实际模型。 AnswerSchema完全"嵌入"。

注意"架构的明确定义"将"answers"中的Question属性定义为"数组" AnswerSchema。这就是你如何嵌入并保持对数组内对象内类型的控制。

至于更新,有一个明确的逻辑模式,但你只是没有强制执行它。你需要做的就是"告诉"您不希望"推送"的更新一个新项目,如果有的东西"唯一"数组中的"employee"已经存在。

另外。这是 NOT 和" upsert"。 Upsert暗示"创建一个新的",这与你想要的不同。你想"推"到现有的"阵列题。如果你离开" upsert"在那里,然后找不到的东西创建一个新的问题。这当然是错的。

Question.update(
  { 
    "_id": req.body.id,
    "answers.employee": { "$ne": req.body.employee },
    }
  },
  { "$push": {
    "answers": { 
      "employee": req.body.employee, 
      "response": req.body.response,
      "isAdmin": req.body.isAdmin
    }
  }},
  function(err, numAffected) {

  });

那将会检查" unique"数组成员中的"employee"已经只有$push,但它不在那里。

作为奖励,如果您打算允许用户更改他们的答案"然后我们用.bulkWrite()

做这个咒语
Question.collection.bulkWrite(
  [
    { "updateOne": { 
      "filter": {
        "_id": req.body.id,
        "answers.employee": req.body.employee,
      },
      "update": {
        "$set": {
          "answers.$.response": req.body.response,
        }
      }
    }},
    { "updateOne": { 
      "filter": {
        "_id": req.body.id,
        "answers.employee": { "$ne": req.body.employee },
      },
      "update": {
        "$push": {
          "answers": { 
            "employee": req.body.employee, 
            "response": req.body.response,
            "isAdmin": req.body.isAdmin
          }
        }
      }
    }}
  ],
  function(err, writeResult) {

  }
);

这有效地将两个更新合二为一。第一个尝试更改现有答案并$set匹配位置的响应,第二个尝试添加新答案,但未在问题中找到答案。

参考模型

使用"引用"模型你实际上拥有自己集合中Answer的真实成员。因此,架构的定义如下:

var QuestionSchema = Schema ({
    title       :String,
    admin       :{type: String, ref: 'User'},
    answers     :[{ type: Schema.Types.ObjectId, ref: 'Answer' }]
});

var AnswerSchema = Schema ({
    _question   :{type: ObjectId, ref: 'Question'},
    employee    :{type: String, ref: 'User'},
    response    :String,
    isAdmin     :{type: Boolean, ref: 'User'}
})

mongoose.model('Answer', answerSchema);
mongoose.model('Question', questionSchema);

N.B 其他参考资料来自User,例如:

    employee    :{type: String, ref: 'User'},
    isAdmin     :{type: Boolean, ref: 'User'}

这些也是非常不正确的,也应该是Schema.Type.ObjectId,因为他们"参考" _id的实际User字段。但这实际上超出了这个问题的范围,所以如果你在阅读之后仍然没有理解,那么Ask a New Question所以有人可以解释。接下来是答案。

那是"将军"虽然模式的形状,重要的是"ref""模型" {,它是注册名称。你可以选择使用现代猫鼬版本中的'Anwser'字段和#34;虚拟"但我跳过" Adavanced Usage"现在,通过一系列"引用来保持简单"仍然在"_question"模型中。

在这种情况下,由于Question模型实际上在其自己的"集合"中,因此操作实际上变为" upserts"。我们只想创造"创造"如果对给定的Answer ID没有"employee"响应。

同时使用Promise链进行演示:

"_question"

这实际上是一个简单的陈述,因为"当匹配" 我们希望用请求的有效负载更改Answer.update( { "_question": req.body.id, "employee": req.body.employee }, { "$set": { "response": req.body.reponse }, "$setOnInsert": { "isAdmin": req.body.isAdmin } }, { "upsert": true } ).then(resp => { if ( resp.hasOwnProperty("upserted") ) { return Question.update( { "_id": req.body.id, "answers": { "$ne": resp.upserted[0]._id }, { "$push": { "answers": resp.upserted[0]._id } } ).exec() } return; }).then(resp => { // either undefined where it was not an upsert or // the update result from Question where it was }).catch(err => { // something }) 数据时,实际上只有当" upserting" 或"创建/插入"当我们实际更改其他数据时,例如"response"(对于查询表达式的一部分始终隐含创建)和"employee"明显不应随每个更新请求更改,我们将明确使用{ {3}}所以将这两个字段写入实际的"创建"。

在"承诺链"我们实际上是要查看对"isAdmin"的更新请求是否实际上导致" upsert",当它发生时我们想要附加到Answer的数组中它还没有存在。与"嵌入式"大致相同。例如,在使用" update"进行修改之前,最好先查看数组是否确实包含该项目。或者,您可以$setOnInsert在这里,让查询与Question Question匹配。但对我来说,这是一个浪费的写作。

摘要

这些是你处理这个问题的不同方法。每个都有自己的用例,你可以在其中看到我的一些其他答案的总摘要:

不是"必需"阅读,但它可能有助于扩展您对哪种方法最适合您的特定情况的洞察力。

工作示例

复制它们并将它们放在一个目录中并执行_id以安装本地依赖项。代码将运行并在数据库中创建集合进行更改。

使用npm install打开日志记录,因此您应该查看控制台输出并查看它的作用,以及生成的集合,其中答案将记录到相关问题中,并覆盖而不是"复制"那也是意图。

如果必须,请更改连接字符串。但这就是为了它的目的,你应该在这个列表中改变。答案中描述的两种方法都得到了证明。

<强>的package.json

mongoose.set(debug,true)

<强> index.js

{
  "name": "colex",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "async": "^2.4.1",
    "mongodb": "^2.2.29",
    "mongoose": "^4.10.7"
  }
}

答案 1 :(得分:0)

在Neill更新他的答案(我使用$pull&amp; $push)之前,我的快速解决方法。像他一样工作,但我会标记他的正确,因为我相信它更有效率。

                Question.update(
                    {_id: req.body.id},
                    {$pull: {answers: { employee: req.body.employee}}},
                    {safe: true, multi:true, upsert: true},
                    function(err, model) {

                    }
                );

                Question.update(
                    {_id: req.body.id},
                    {$push: {answers: {_question: req.body.id, 
                                        employee: req.body.employee, 
                                        response: req.body.response,
                                         isAdmin: req.body.isAdmin}}},
                    {safe: true, upsert: true},
                    function(err, model) {

                    }
                );