FindAndUpdate如何检查文档是否真正更新

时间:2017-07-09 00:28:51

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

想象一下以下模型:

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是最好的方法,考虑到我们不能让用户删除主要分支,如果他试图这样做,我们必须显示警告。

2 个答案:

答案 0 :(得分:2)

查看更新是否适用于$pull之类的唯一真正可靠的方法是基本上检查返回的文档,看看您打算$pull的数据是否仍在那里

对于任何"findAndUpdate"种各种行为都是如此,并且有正当理由,以及普通.update()实际上也是#34 ;可靠"告诉你实际上是否进行了修改。

走过案例:

检查退回的内容

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

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不执行"相等"使用" Objects"。你会检查"左"和"对"如果提供的东西已经是'#34; cast"到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是&#34; true&#34;是否某事&#34;实际更新的指标&#34;。因此,如果它是1,那么$pull实际上会产生影响,但是当0实际上没有从数组中删除任何内容并且没有任何内容被修改时。

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

findAndModify谎言!

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

问题是(以及需要与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));

这里的问题是即使我们知道&#34;这不应该修改,反应说不然:

{
  "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
}

所以,即使在完成所有这些工作之后,第三个&#34;在回调响应之外的争论中,我们仍然没有得到关于更新的正确信息。

结论

所以如果你想要可靠的&#34;使用单个请求执行此操作(并且无法可靠地执行多个请求,因为文档可能会更改!)然后您的两个选项是:

  1. 检查返回的文档,看看您要删除的数据是否仍然存在。

  2. 忘记返回一份文件并相信.update()总是告诉你&#34;真相&#34; ;)

  3. 您使用哪一个取决于应用程序使用模式,但这些是返回&#34;可靠&#34;的两种不同方式。结果

    列表的位

    所以,只是为了确定,这是一个列出所有示例并展示它们实际返回的列表:

    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
    }
    

答案 1 :(得分:0)

在这种情况下,通过两个步骤进行查找和更新会更好,因为您刚才说您可以选择警告用户。

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

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

哪个会返回办公室文件。如果是,则继续findByIdAndUpdate(这比修改和保存已找到的文档更好)。如果没有,则向用户返回禁止的消息。