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

想象下面的模型:

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

我想删除一个分支,但我们不能让用户从办公室删除主要分支。 所以这是我的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); }); }) } 

我在这里有一些疑问:

  1. 正如你所看到的,我没有在isPrincipal属性中包含另一个WHERE,这是因为我不知道如何确定Office对象是否实际上已经改变了。 因为这个对象会一直被找回,但是…我怎么知道这个是肯定的?
  2. FindByIdAndUpdate是最好的方法,考虑到我们不能让用户删除主分支,如果他想这样做,我们必须显示警告。

唯一真正可靠的方式来查看是否应用了更新类似于$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值,那么你将检查“左”和“右”,但在这种情况下,我们知道另一个input是“string”。

只需使用.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谎言!

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

问题是(以及需要与承诺更多的工作),结果是不真实的:

 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 } 

所以即使是在callback响应中出现“第三个”参数之后,我们仍然没有得到有关更新的正确信息。


结论

所以,如果你想“可靠地”做到这一点与一个单一的请求(你不能可靠地做到这一点与多个请求,因为文件可以改变之间!),那么你的两个select是:

  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 } 

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

关于发现的一个笔记。 你有一个对象branches数组。 匹配find $ elemMatch中的多个字段是必要的。 该查询将如下所示:

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

哪个将返回办公室文件或不。 如果是这样,则继续执行findByIdAndUpdate (这比修改和保存已find的文档要好)。 如果不是,则向用户返回禁止消息。