【问题标题】:FindAndUpdate How to check if document was really updatedFindAndUpdate 如何检查文档是否真的更新了
【发布时间】:2017-12-12 23:58:54
【问题描述】:

想象以下模型:

var Office = 
{
    id: 1,
    name: "My Office",
    branches: 
    [
      {
        adddress: "Some street, that avenue",
        isPrincipal: true,
      },
      {
        adddress: "Another address",
        isPrincipal: false,
      },      
    ]
}

我想删除一个分支机构,但我们不能让用户从办公室中删除主要分支机构。所以这是我的功能:

remove: function(body)
{
  return new Promise(function(resolve, reject)
  {
    return Office.findByIdAndUpdate(1, { $pull: {'branches': {_id: body.branch.id}}}, { new: true })
    .then(function(updatedOffice){
      resolve(updatedOffice)
    })
    .catch(function(error){
      reject(error);
    });

  })
}    

我有一些疑问:

  1. 如您所见,我没有在 isPrincipal 属性中包含另一个 WHERE,这是因为我不知道如何确定 office 对象是否实际更改。因为对象总是会被检索到,但是……我怎么能确定呢?
  2. 考虑到我们不能让用户删除主体分支,FindByIdAndUpdate 是不是最好的方法,如果他试图这样做,我们必须显示警告。

【问题讨论】:

    标签: javascript node.js mongodb mongoose mongodb-query


    【解决方案1】:

    查看是否为$pull 之类的内容应用了更新的唯一真正可靠的方法是基本上检查返回的文档,看看您打算发送给$pull 的数据是否仍然存在。

    这适用于任何"findAndUpdate" 的各种操作,这是有正当理由的,而且普通的.update() 实际上会“可靠地”告诉您修改是否确实存在制作。

    浏览案例:

    检查返回的内容

    这基本上涉及查看返回文档中的数组,以查看我们要求删除的内容是否确实存在:

    var pullId = "5961de06ea264532c684611a";
    
    Office.findByIdAndUpdate(1,
      { "$pull": { "branches": { "_id": pullId } } },
      { "new": true }
    ).then(office => {
      // Check if the supplied value is still in the array
      console.log(
        "Still there?: %s",
        (office.branches.find( b => b._id.toHexString() === pullId))
          ? true : false
      );
    }).catch(err => console.error(err))
    

    我们使用.toHexString() 来比较ObjectId 的实际值,因为JavaScript 只是不使用“对象”进行“相等”。如果将已经“强制转换”的内容提供给 ObjectId 值,您将检查“左”和“右”,但在这种情况下,我们知道另一个输入是“字符串”。

    只需使用 .update(),“它很可靠”

    如果您“确实需要”返回的修改数据,这里要考虑的另一种情况会带来问题。因为.update() 方法会可靠地返回一个结果,告诉您是否实际修改了任何内容:

    Office.update(
      { "_id": 1 },
      { "$pull": { "branches": { "_id": pullId } } },
    ).then(result => {
      log(result);
    }).catch(err => console.error(err))
    

    result 的位置如下所示:

    {
      "n": 1,
      "nModified": 1,        // <--- This always tells the truth, and cannot lie!
      "opTime": {
        "ts": "6440673063762657282",
        "t": 4
      },
      "electionId": "7fffffff0000000000000004",
      "ok": 1
    }
    

    其中nModified 是一个“真实”指标,表明某事是否“实际更新”。因此,如果它是1,那么$pull 实际上是有效果的,但是当0 时实际上并没有从数组中删除任何内容,也没有进行任何修改。

    这是因为该方法实际上使用了更新后的 API,它确实具有表明实际修改的可靠结果。这同样适用于像 $set 这样的东西,它实际上并没有改变值,因为提供的值等于文档中已经存在的值。

    findAndModify 谎言!

    您在仔细查看文档时可能会想到的另一种情况是实际检查“原始结果”并查看文档是否被修改。规范中实际上有一个指标。

    问题是(以及需要更多使用 Promises 的工作)结果实际上并不真实:

    var bogusId = "5961de06ea264532c684611a"; // We know this is not there!
    
    Promise((resolve,reject) => {
      Office.findByIdAndUpdate(1,
        { "$pull": { "branches": { "_id": bogusId } } },
        { "new": true, "passRawResult" },
        (err,result,raw) => {        // We cannot pass multiple results to a Promise
          if (err) reject(err);
          resolve({ result, raw });   // So we wrap it!
        }
      )
    })
    .then(response => log(response.raw))
    .catch(err => console.error(err));
    

    这里的问题是,即使我们“知道”这不应该修改,响应也会说:

    {
      "lastErrorObject": {
        "updatedExisting": true,
        "n": 1                     // <--- LIES! IT'S ALL LIES!!!
      },
      "value": {
        "_id": 1,
        "name": "My Office",
        "branches": [
          {
            "address": "Third address",
            "isPrincipal": false,
            "_id": "5961de06ea264532c6846118"
          }
        ],
        "__v": 0
      },
      "ok": 1,
      "_kareemIgnore": true
    }
    

    因此,即使在完成所有工作以从回调响应中获取“第三个”参数后,我们仍然没有得到有关更新的正确信息。


    结束

    因此,如果您想通过单个请求“可靠地”执行此操作(并且您无法可靠地通过多个请求执行此操作,因为 文档可能会更改之间!) 那么你的两个选择是:

    1. 检查返回的文档,看看你要删除的数据是否还在。

    2. 忘记返回文件,相信.update() 总是告诉你“真相”;)

    您使用哪一种取决于应用程序的使用模式,但这是返回“可靠”结果的两种不同方式。


    部分列表

    所以为了确定起见,这里列出了所有示例并演示了它们实际返回的内容:

    const async = require('async'),
          mongoose = require('mongoose'),
          Schema = mongoose.Schema;
    
    mongoose.Promise = global.Promise;
    mongoose.set('debug',true);
    
    mongoose.connect('mongodb://localhost/test');
    
    const branchesSchema = new Schema({
      address: String,
      isPrincipal: Boolean
    });
    
    const officeSchema = new Schema({
      _id: Number,
      name: String,
      branches: [branchesSchema]
    },{ _id: false });
    
    const Office = mongoose.model('Office', officeSchema);
    
    function log(data) {
      console.log(JSON.stringify(data,undefined,2))
    }
    
    const testId = "5961a56d3ffd3d5e19c61610";
    
    async.series(
      [
        // Clean data
        (callback) =>
          async.each(mongoose.models,(model,callback) =>
            model.remove({},callback),callback),
    
        // Insert some data and pull
        (callback) =>
          async.waterfall(
            [
              // Create and demonstrate
              (callback) =>
                Office.create({
                  _id: 1,
                  name: "My Office",
                  branches: [
                    {
                      address: "Some street, that avenue",
                      isPrincipal: true
                    },
                    {
                      address: "Another address",
                      isPrincipal: false
                    },
                    {
                      address: "Third address",
                      isPrincipal: false
                    }
                  ]
                },callback),
    
              // Demo Alternates
              (office,callback) =>
                async.mapSeries(
                  [true,false].map((t,i) => ({ t, branch: office.branches[i] })),
                  (test,callback) =>
                    (test.t)
                      ? Office.findByIdAndUpdate(office._id,
                          { "$pull": { "branches": { "_id": test.branch._id } } },
                          { "new": true , "passRawResult": true },
                          (err,result,raw) => {
                            if (err) callback(err);
                            log(result);
                            log(raw);
                            callback();
                          })
                      : Office.findByIdAndUpdate(office._id,
                          { "$pull": { "branches": { "_id": test.branch._id } } },
                          { "new": true } // false here
                        ).then(result => {
                          log(result);
                          console.log(
                            "Present %s",
                            (result.branches.find( b =>
                              b._id.toHexString() === test.branch._id.toHexString() ))
                              ? true : false
                          );
                          callback();
                        }).catch(err => callback(err)),
                  callback
                )
            ],
            callback
          ),
    
        // Find and demonstate fails
        (callback) =>
          async.waterfall(
            [
              (callback) => Office.findOne({},callback),
    
              (office,callback) =>
                async.eachSeries([true,false],(item,callback) =>
                  (item)
                    ? Office.findByIdAndUpdate(office._id,
                        { "$pull": { "branches": { "_id": testId } } },
                        { "new": true, "passRawResult": true },
                        (err,result,raw) => {
                          if (err) callback(err);
                          log(result);
                          log(raw);
                          callback();
                        }
                      )
                    : Office.findByIdAndUpdate(office._id,
                        { "$pull": { "branches": { "_id": testId } } },
                        { "new": true }
                      ).then(result => {
                        console.log(result);
                        console.log(
                          "Present %s",
                          (result.branches.find( b =>
                            b._id.toHexString() === office.branches[0]._id.toHexString()))
                            ? true : false
                        );
                        callback();
                      })
                      .catch(err => callback(err)),
                  callback)
    
            ],
            callback
          ),
    
        // Demonstrate update() modified shows 0
        (callback) =>
          Office.update(
            {},
            { "$pull": { "branches": { "_id": testId } } }
          ).then(result => {
            log(result);
            callback();
          })
          .catch(err => callback(err)),
    
        // Demonstrate wrapped promise
        (callback) =>
          Office.findOne()
            .then(office => {
              return new Promise((resolve,reject) => {
                Office.findByIdAndUpdate(office._id,
                  { "$pull": { "branches": { "_id": testId } } },
                  { "new": true, "passRawResult": true },
                  (err,result,raw) => {
                    if (err) reject(err);
                    resolve(raw)
                  }
                );
              })
            })
            .then(office => {
              log(office);
              callback();
            })
            .catch(err => callback(err))
    
      ],
      (err) => {
        if (err) throw err;
        mongoose.disconnect();
      }
    );
    

    以及它产生的输出:

    Mongoose: offices.remove({}, {})
    Mongoose: offices.insert({ _id: 1, name: 'My Office', branches: [ { address: 'Some street, that avenue', isPrincipal: true, _id: ObjectId("5961e5211a73e8331b44d74b") }, { address: 'Another address', isPrincipal: false, _id: ObjectId("5961e5211a73e8331b44d74a") }, { address: 'Third address', isPrincipal: false, _id: ObjectId("5961e5211a73e8331b44d749") } ], __v: 0 })
    Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961e5211a73e8331b44d74b") } } }, { new: true, passRawResult: true, upsert: false, remove: false, fields: {} })
    {
      "_id": 1,
      "name": "My Office",
      "__v": 0,
      "branches": [
        {
          "address": "Another address",
          "isPrincipal": false,
          "_id": "5961e5211a73e8331b44d74a"
        },
        {
          "address": "Third address",
          "isPrincipal": false,
          "_id": "5961e5211a73e8331b44d749"
        }
      ]
    }
    {
      "lastErrorObject": {
        "updatedExisting": true,
        "n": 1
      },
      "value": {
        "_id": 1,
        "name": "My Office",
        "branches": [
          {
            "address": "Another address",
            "isPrincipal": false,
            "_id": "5961e5211a73e8331b44d74a"
          },
          {
            "address": "Third address",
            "isPrincipal": false,
            "_id": "5961e5211a73e8331b44d749"
          }
        ],
        "__v": 0
      },
      "ok": 1,
      "_kareemIgnore": true
    }
    Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961e5211a73e8331b44d74a") } } }, { new: true, upsert: false, remove: false, fields: {} })
    {
      "_id": 1,
      "name": "My Office",
      "__v": 0,
      "branches": [
        {
          "address": "Third address",
          "isPrincipal": false,
          "_id": "5961e5211a73e8331b44d749"
        }
      ]
    }
    Present false
    Mongoose: offices.findOne({}, { fields: {} })
    Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961a56d3ffd3d5e19c61610") } } }, { new: true, passRawResult: true, upsert: false, remove: false, fields: {} })
    {
      "_id": 1,
      "name": "My Office",
      "__v": 0,
      "branches": [
        {
          "address": "Third address",
          "isPrincipal": false,
          "_id": "5961e5211a73e8331b44d749"
        }
      ]
    }
    {
      "lastErrorObject": {
        "updatedExisting": true,
        "n": 1
      },
      "value": {
        "_id": 1,
        "name": "My Office",
        "branches": [
          {
            "address": "Third address",
            "isPrincipal": false,
            "_id": "5961e5211a73e8331b44d749"
          }
        ],
        "__v": 0
      },
      "ok": 1,
      "_kareemIgnore": true
    }
    Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961a56d3ffd3d5e19c61610") } } }, { new: true, upsert: false, remove: false, fields: {} })
    { _id: 1,
      name: 'My Office',
      __v: 0,
      branches:
       [ { address: 'Third address',
           isPrincipal: false,
           _id: 5961e5211a73e8331b44d749 } ] }
    Present true
    Mongoose: offices.update({}, { '$pull': { branches: { _id: ObjectId("5961a56d3ffd3d5e19c61610") } } }, {})
    {
      "n": 1,
      "nModified": 0,
      "opTime": {
        "ts": "6440680872013201413",
        "t": 4
      },
      "electionId": "7fffffff0000000000000004",
      "ok": 1
    }
    Mongoose: offices.findOne({}, { fields: {} })
    Mongoose: offices.findAndModify({ _id: 1 }, [], { '$pull': { branches: { _id: ObjectId("5961a56d3ffd3d5e19c61610") } } }, { new: true, passRawResult: true, upsert: false, remove: false, fields: {} })
    {
      "lastErrorObject": {
        "updatedExisting": true,
        "n": 1
      },
      "value": {
        "_id": 1,
        "name": "My Office",
        "branches": [
          {
            "address": "Third address",
            "isPrincipal": false,
            "_id": "5961e5211a73e8331b44d749"
          }
        ],
        "__v": 0
      },
      "ok": 1,
      "_kareemIgnore": true
    }
    

    【讨论】:

    • 这是一个专门的答案。让我谢谢你,我终于去findByIdAndUpdate然后检查对象是否存在。谢谢大师!
    【解决方案2】:

    在这种情况下,分两步查找和更新会更好,正如您刚才所说,您可以选择警告用户。

    关于查找的说明。你有一个对象数组branches。要匹配find 中的多个字段,需要$elemMatch。查询将类似于:

    Office.findOne({_id: 1, "branches" : {$elemMatch: {"_id": body.branch.id, "isPrincipal": false}}})
    

    这将返回或不返回办公室文件。如果是,则继续使用findByIdAndUpdate(这比修改和保存已找到的文档要好)。如果没有,则向用户返回一条禁止消息。

    【讨论】:

    • 所以这里的建议有很大的问题。简而言之,作为一个“单独的”请求,您无法确保在“修改”和您提议的“稍后阅读”之间没有其他任何修改文档。另一个进程可能在那个时间范围内修改了文档,实际上有一些方法可以判断“更新”是否真的改变了某些东西。
    • 我建议find 然后update,而不是相反。因此,“稍后阅读”部分令人困惑。无论如何,您提出的update 方式要好得多,因为它通过一个请求处理“允许”或“禁止”:)
    • 顺序无关紧要。这不可靠。您实际上应该阅读我在这方面写的内容并学习一些东西。
    • 做了两次。我实际上提到你的方式更好,这意味着我已经阅读了它。还不如删除我的答案。
    猜你喜欢
    • 2021-06-24
    • 2019-07-02
    • 2012-09-16
    • 1970-01-01
    • 2021-01-06
    • 1970-01-01
    • 1970-01-01
    • 2018-03-23
    • 2012-08-22
    相关资源
    最近更新 更多