如何检查文档是否真的更新了

时间:2021-03-25 02:33:29

Imagine the following model:

想象以下模型:

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

I'd like to remove a branch, but we can't let the user remove the principal branch from an office. So here's my function:

我想删除一个分支,但是我们不能让用户从一个办公室删除主体分支。这是我的功能:

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);
    });

  })
}    

I have some doubts here:

我有一些疑问:

  1. As you can see I haven't included the another WHERE on the isPrincipal property, this is because I don't know how can I determine whether the office object got actually changed. Because the object will alway be retrieved but... How can I know this for sure?
  2. 正如你看到的,我没有包含isPrincipal属性上的另一个,这是因为我不知道如何确定office对象是否真的被更改了。因为对象会一直被检索,但是……我怎么知道呢?
  3. Is FindByIdAndUpdate the best approach considering we can't let a user delete the principal branch, and if he's trying to do so, we have to show a warning.
  4. FindByIdAndUpdate是最好的方法,因为我们不能让用户删除主分支,如果用户试图删除主分支,我们必须显示一个警告。

2 个解决方案

#1


2  

The only real reliable way to see if an update was applied for something like a $pull is to basically check the returned document and see if the data you intended to $pull is still in there or not.

查看是否对$pull之类的东西应用了更新的唯一真正可靠的方法是基本上检查返回的文档,并查看想要$pull的数据是否还在那里。

That's for any of the "findAndUpdate" variety of actions, and there is a valid reason for that as well as it also being the case that a plain .update() will actually "reliably" tell you if the modification was in fact made.

对于任何“findAndUpdate”类型的操作,这都是合理的,而且一个普通的.update()实际上也会“可靠地”告诉您修改是否已经完成。

To walk through the cases:

浏览案件:

Check the Returned Content

This basically involves looking at the array in the returned document in order to see if what we asked to remove is actually there:

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

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))

We use .toHexString() in order to compare the actual value from an ObjectId since JavaScript just does not do "equality" with "Objects". You would check on both "left" and "right" if supplying something that was already "cast" to an ObjectId value, but in this case we know the other input is a "string".

我们使用. tohexstring()来比较objective - d的实际值,因为JavaScript不会与对象“相等”。如果向一个objective值提供已经“cast”的东西,您将检查“left”和“right”,但是在本例中,我们知道另一个输入是“string”。

Just use .update(), "It's reliable"

The other case here to consider brings into question if you "really need" the returned modified data anyway. Because the .update() method, will reliably return a result telling you if anything was actually modified:

如果您“确实需要”返回的修改后的数据,那么这里要考虑的另一种情况就会产生问题。因为.update()方法将可靠地返回一个结果,告诉您是否有任何实际修改:

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

Where result here will look like:

这里的结果是:

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

And in which the nModified is a "true" indicator of whether something "actually updated". Therefore if it's 1 then the $pull actually had an effect, but when 0 nothing was actually removed from the array and nothing was modified.

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

This is because the method actually uses the updated API, which does have reliable results indicating actual modifications. The same would apply to something like a $set which did not actually change the value because the the value supplied was equal to what already existed in the document.

这是因为该方法实际上使用了更新的API,它有可靠的结果来指示实际的修改。同样的情况也适用于$set,它实际上并没有改变值,因为所提供的值等于文档中已经存在的值。

findAndModify Lies!

The other case here you might think of when looking closely at the documentation is to actually inspect the "raw result" and see if the document was modified or not. There is actually an indicator in the specification for this.

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

The problem is ( as well as requiring more work with Promises ) that the result is not actually truthful:

问题是(也需要更多的工作和承诺)结果实际上并不真实:

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));

The problem here is that even when we "know" this should not modify, the response says otherwise:

这里的问题是,即使我们“知道”这一点不应该改变,但答案是相反的:

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

So even after all that work to get the "third" argument out of the callback response, we still did not get told the correct information about the update.

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


Concluding

So if you want to "reliably" do this with a single request ( and you cannot reliably do that with multiple requests since the document could change in between! ) then your two options are:

因此,如果您想要“可靠地”对单个请求执行此操作(而且您不能对多个请求执行此操作,因为文档可能在这两个请求之间进行更改!)那么你的两个选择是:

  1. Check the returned document to see if the data you wanted to remove is still there.

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

  2. Forget returning a document and trust that .update() always tells you the "truth" ;)

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

Which one of these you use depends on the application usage pattern, but those are the two different ways of returning an "reliable" result.

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


Bit of a Listing

So just to be sure, here's a listing that goes through all the examples and demonstrates what they actually return:

这里有一份清单列出了所有的例子并展示了它们实际返回的结果:

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();
  }
);

And the output it produces:

以及它产生的输出:

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
}

#2


0  

Finding and updating in two steps would better in this case, as you just said you'll have the option of warning the user.

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

A note on find. You have an array of objects branches. To match more than one field in find $elemMatch is needed. The query will look something like:

找到的注意。你有一个对象分支的数组。要匹配多个字段,需要找到$elemMatch。查询将如下所示:

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

Which will either return the office document or not. If it does, you proceed with findByIdAndUpdate (which is better than modifying and saving the already found document). If it does not, return a forbidden message to the user.

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

#1


2  

The only real reliable way to see if an update was applied for something like a $pull is to basically check the returned document and see if the data you intended to $pull is still in there or not.

查看是否对$pull之类的东西应用了更新的唯一真正可靠的方法是基本上检查返回的文档,并查看想要$pull的数据是否还在那里。

That's for any of the "findAndUpdate" variety of actions, and there is a valid reason for that as well as it also being the case that a plain .update() will actually "reliably" tell you if the modification was in fact made.

对于任何“findAndUpdate”类型的操作,这都是合理的,而且一个普通的.update()实际上也会“可靠地”告诉您修改是否已经完成。

To walk through the cases:

浏览案件:

Check the Returned Content

This basically involves looking at the array in the returned document in order to see if what we asked to remove is actually there:

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

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))

We use .toHexString() in order to compare the actual value from an ObjectId since JavaScript just does not do "equality" with "Objects". You would check on both "left" and "right" if supplying something that was already "cast" to an ObjectId value, but in this case we know the other input is a "string".

我们使用. tohexstring()来比较objective - d的实际值,因为JavaScript不会与对象“相等”。如果向一个objective值提供已经“cast”的东西,您将检查“left”和“right”,但是在本例中,我们知道另一个输入是“string”。

Just use .update(), "It's reliable"

The other case here to consider brings into question if you "really need" the returned modified data anyway. Because the .update() method, will reliably return a result telling you if anything was actually modified:

如果您“确实需要”返回的修改后的数据,那么这里要考虑的另一种情况就会产生问题。因为.update()方法将可靠地返回一个结果,告诉您是否有任何实际修改:

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

Where result here will look like:

这里的结果是:

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

And in which the nModified is a "true" indicator of whether something "actually updated". Therefore if it's 1 then the $pull actually had an effect, but when 0 nothing was actually removed from the array and nothing was modified.

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

This is because the method actually uses the updated API, which does have reliable results indicating actual modifications. The same would apply to something like a $set which did not actually change the value because the the value supplied was equal to what already existed in the document.

这是因为该方法实际上使用了更新的API,它有可靠的结果来指示实际的修改。同样的情况也适用于$set,它实际上并没有改变值,因为所提供的值等于文档中已经存在的值。

findAndModify Lies!

The other case here you might think of when looking closely at the documentation is to actually inspect the "raw result" and see if the document was modified or not. There is actually an indicator in the specification for this.

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

The problem is ( as well as requiring more work with Promises ) that the result is not actually truthful:

问题是(也需要更多的工作和承诺)结果实际上并不真实:

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));

The problem here is that even when we "know" this should not modify, the response says otherwise:

这里的问题是,即使我们“知道”这一点不应该改变,但答案是相反的:

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

So even after all that work to get the "third" argument out of the callback response, we still did not get told the correct information about the update.

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


Concluding

So if you want to "reliably" do this with a single request ( and you cannot reliably do that with multiple requests since the document could change in between! ) then your two options are:

因此,如果您想要“可靠地”对单个请求执行此操作(而且您不能对多个请求执行此操作,因为文档可能在这两个请求之间进行更改!)那么你的两个选择是:

  1. Check the returned document to see if the data you wanted to remove is still there.

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

  2. Forget returning a document and trust that .update() always tells you the "truth" ;)

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

Which one of these you use depends on the application usage pattern, but those are the two different ways of returning an "reliable" result.

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


Bit of a Listing

So just to be sure, here's a listing that goes through all the examples and demonstrates what they actually return:

这里有一份清单列出了所有的例子并展示了它们实际返回的结果:

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();
  }
);

And the output it produces:

以及它产生的输出:

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
}

#2


0  

Finding and updating in two steps would better in this case, as you just said you'll have the option of warning the user.

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

A note on find. You have an array of objects branches. To match more than one field in find $elemMatch is needed. The query will look something like:

找到的注意。你有一个对象分支的数组。要匹配多个字段,需要找到$elemMatch。查询将如下所示:

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

Which will either return the office document or not. If it does, you proceed with findByIdAndUpdate (which is better than modifying and saving the already found document). If it does not, return a forbidden message to the user.

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