(mongoose/promises) 如何检查文档是不是是使用 findOneAndUpdate 和 upsert 创建的

Posted

技术标签:

【中文标题】(mongoose/promises) 如何检查文档是不是是使用 findOneAndUpdate 和 upsert 创建的【英文标题】:(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 上的函数。

【问题讨论】:

【参考方案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访问该文档

【讨论】:

以上是关于(mongoose/promises) 如何检查文档是不是是使用 findOneAndUpdate 和 upsert 创建的的主要内容,如果未能解决你的问题,请参考以下文章

如何在进行检查期间定位二进制文​​件?

如何检查CKEditor中是不是有一些文字?

从base64检查文​​件类型?

如何检查TXT文本是否ANSI编码格式的?

检查文本区域是不是包含符号并删除符号后的所有内容

如何在表格中显示文本区域的换行符?