【问题标题】:(mongoose/promises) How do you check if document was created using findOneAndUpdate with upsert(mongoose/promises) 如何检查文档是否是使用 findOneAndUpdate 和 upsert 创建的
【发布时间】:2015-11-23 18:07:37
【问题描述】:

考虑一下这段代码,我需要在其中创建或更新特定文档。

Inbox.model.findOneAndUpdate({ number: req.phone.number }, {
    number: req.phone.number,
    country: req.phone.country,
    token: hat(),
    appInstalled: true
}, { new: true, upsert: true }).then(function(inbox){
    /*
       do something here with inbox, but only if the inbox was created (not updated)
    */
});

猫鼬是否有能力区分文档是创建还是更新?我需要new: true,因为我需要调用inbox 上的函数。

【问题讨论】:

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


    【解决方案1】:

    对于 .findOneAndUpdate() 或任何用于 mongoose 的 .findAndModify() 核心驱动程序变体,实际的回调签名具有“三个”参数:

     function(err,result,raw)
    

    第一个是任何错误响应,然后是修改或原始文档,取决于选项,第三个是发出语句的写入结果。

    第三个参数应该返回如下数据:

    { lastErrorObject:
       { updatedExisting: false,
         n: 1,
         upserted: 55e12c65f6044f57c8e09a46 },
      value: { _id: 55e12c65f6044f57c8e09a46, 
               number: 55555555, 
               country: 'US', 
               token: "XXX", 
               appInstalled: true,
               __v: 0 },
      ok: 1 }
    

    其中的一致字段为 lastErrorObject.updatedExistingtrue/false 取决于是否发生 upsert 的结果。请注意,当此属性为 false 时,还有一个包含新文档的 _id 响应的“upserted”值,但当它是 true 时则没有。

    因此,您将修改您的处理以考虑第三个条件,但这仅适用于回调而不是承诺:

    Inbox.model.findOneAndUpdate(
        { "number": req.phone.number },
        { 
          "$set": {
              "country": req.phone.country,
              "token": hat(),
              "appInstalled": true
          }
        }, 
        { "new": true, "upsert": true },
        function(err,doc,raw) {
    
          if ( !raw.lastErrorObject.updatedExitsing ) {
             // do things with the new document created
          }
        }
    );
    

    我还强烈建议您在此处使用 update operators 而不是原始对象,因为原始对象总是会覆盖整个文档,但像 $set 这样的运算符只会影响列出的字段。

    还请注意,只要它们的值与未找到的完全匹配,该语句的任何匹配“查询参数”都会自动分配到新文档中。

    鉴于由于某种原因,使用承诺似乎不会返回附加信息,那么除了设置{ new: false} 之外,没有看到使用承诺如何实现这一点,基本上当没有返回文档时,它就是一个新的。

    无论如何,您都有预期要插入的所有文档数据,因此您并不真的需要返回这些数据。事实上,原生驱动程序方法是如何在核心处理此问题的,并且仅在发生 upsert 时以“upserted”_id 值响应。

    这实际上归结为本网站上讨论的另一个问题,如下:

    Can promises have multiple arguments to onFulfilled?

    这实际上归结为 promise 响应中多个对象的解析,这在本机规范中不直接支持,但其中列出了一些方法。

    因此,如果您实现 Bluebird 承诺并在那里使用 .spread() 方法,那么一切都很好:

    var async = require('async'),
        Promise = require('bluebird'),
        mongoose = require('mongoose'),
        Schema = mongoose.Schema;
    
    mongoose.connect('mongodb://localhost/test');
    
    var testSchema = new Schema({
      name: String
    });
    
    var Test = mongoose.model('Test',testSchema,'test');
    Promise.promisifyAll(Test);
    Promise.promisifyAll(Test.prototype);
    
    async.series(
      [
        function(callback) {
          Test.remove({},callback);
        },
        function(callback) {
          var promise = Test.findOneAndUpdateAsync(
            { "name": "Bill" },
            { "$set": { "name": "Bill" } },
            { "new": true, "upsert": true }
          );
    
          promise.spread(function(doc,raw) {
            console.log(doc);
            console.log(raw);
            if ( !raw.lastErrorObject.updatedExisting ) {
              console.log( "new document" );
            }
            callback();
          });
        }
      ],
      function(err) {
        if (err) throw err;
        mongoose.disconnect();
      }
    );
    

    这当然会返回两个对象,然后您可以一致地访问:

    { _id: 55e14b7af6044f57c8e09a4e, name: 'Bill', __v: 0 }
    { lastErrorObject:
       { updatedExisting: false,
         n: 1,
         upserted: 55e14b7af6044f57c8e09a4e },
      value: { _id: 55e14b7af6044f57c8e09a4e, name: 'Bill', __v: 0 },
      ok: 1 }
    

    以下是展示正常行为的完整清单:

    var async = require('async'),
        mongoose = require('mongoose'),
        Schema = mongoose.Schema;
    
    mongoose.connect('mongodb://localhost/test');
    
    var testSchema = new Schema({
      name: String
    });
    
    var Test = mongoose.model('Test',testSchema,'test');
    
    async.series(
      [
        function(callback) {
          Test.remove({},callback);
        },
        function(callback) {
          Test.findOneAndUpdate(
            { "name": "Bill" },
            { "$set": { "name": "Bill" } },
            { "new": true, "upsert": true }
          ).then(function(doc,raw) {
            console.log(doc);
            console.log(raw);
            if ( !raw.lastErrorObject.updatedExisting ) {
              console.log( "new document" );
            }
            callback();
          });
        }
      ],
      function(err) {
        if (err) throw err;
        mongoose.disconnect();
      }
    );
    

    作为记录,本机驱动程序本身没有这个问题,因为响应对象实际上是除了任何错误之外它唯一返回的对象:

    var async = require('async'),
        mongodb = require('mongodb'),
        MongoClient = mongodb.MongoClient;
    
    MongoClient.connect('mongodb://localhost/test',function(err,db) {
    
      var collection = db.collection('test');
    
      collection.findOneAndUpdate(
        { "name": "Bill" },
        { "$set": { "name": "Bill" } },
        { "upsert": true, "returnOriginal": false }
      ).then(function(response) {
        console.log(response);
      });
    });
    

    所以总是这样:

    { lastErrorObject:
       { updatedExisting: false,
         n: 1,
         upserted: 55e13bcbf6044f57c8e09a4b },
      value: { _id: 55e13bcbf6044f57c8e09a4b, name: 'Bill' },
      ok: 1 }
    

    【讨论】:

    • 我已经尝试查看raw,它似乎在最新版本中不再存在(始终未定义)。我也在使用 Promise,它只为 then(function(item)... 提供一个参数
    • 我最终通过创建自己的createOrUpdate static 解决了这个问题。
    • @MattWay 这有点可疑。与普通回调一起工作正常,似乎是关于如何将事物作为承诺传递的。我想我会花几分钟看看这里出了什么问题。
    • 据此github.com/Automattic/mongoose/issues/2931findAndModify 只提供doc(不是raw)。
    • @MattWay 关于这个问题的说法并不完全正确。我添加了一些关于本机驱动程序方法实际作用的信息,并通过一个工作示例对此进行了澄清。这里的问题是承诺,而不是驱动方法的功能。
    【解决方案2】:

    对于将其与 Promises 一起使用的人,根据 this link from Mongoosejs.com 我们可以传递 rawQuery: true 以及其他选项。

    const filter = { name: 'Will Riker' };
    const update = { age: 29 };
    
    await Character.countDocuments(filter); // 0
    
    let res = await Character.findOneAndUpdate(filter, update, {
      new: true,
      upsert: true,
      rawResult: true // Return the raw result from the MongoDB driver
    });
    
    res.value instanceof Character; // true
    // The below property will be `false` if MongoDB upserted a new
    // document, and `true` if MongoDB updated an existing object.
    res.lastErrorObject.updatedExisting; /
    
    

    此查询的结果将采用以下格式

    { lastErrorObject:
       { n: 1,
         updatedExisting: false,
         upserted: 5e6a9e5ec6e44398ae2ac16a },
      value:
       { _id: 5e6a9e5ec6e44398ae2ac16a,
         name: 'Will Riker',
         __v: 0,
         age: 29 },
      ok: 1 }
    

    因此可以通过data.value访问该文档

    【讨论】:

      猜你喜欢
      • 2019-07-02
      • 2021-07-10
      • 2014-12-22
      • 2016-04-30
      • 2019-10-05
      • 2014-09-26
      • 2015-11-22
      • 2016-08-01
      • 1970-01-01
      相关资源
      最近更新 更多