Node.js Mongoose会覆盖文档,而不是“$set”字段

Node.js Mongoose会覆盖文档,而不是“$set”字段,node.js,mongodb,mongoose,Node.js,Mongodb,Mongoose,比如,我有一份文件: { _id: 'some_mongodb_id', name: 'john doe', phone: '+12345678901', } .findOneAndUpdate({_id: 'some_mongodb_id'}, {name: 'Dan smith'}) var collection = db.collection('test'); collection.findOneAndUpdate( {'_id': 'some_mongodb_id'}

比如,我有一份文件:

{
  _id: 'some_mongodb_id',
  name: 'john doe',
  phone: '+12345678901',
}
.findOneAndUpdate({_id: 'some_mongodb_id'}, {name: 'Dan smith'})
var collection = db.collection('test');
collection.findOneAndUpdate(
  {'_id': 'some_mongodb_id'},
  {name: 'Dan smith Only'},
  {upsert: true},
  function (err, doc) {
    console.log(doc);
  }
);
我想更新此文档:

{
  _id: 'some_mongodb_id',
  name: 'john doe',
  phone: '+12345678901',
}
.findOneAndUpdate({_id: 'some_mongodb_id'}, {name: 'Dan smith'})
var collection = db.collection('test');
collection.findOneAndUpdate(
  {'_id': 'some_mongodb_id'},
  {name: 'Dan smith Only'},
  {upsert: true},
  function (err, doc) {
    console.log(doc);
  }
);
结果应该是:

{
  _id: 'some_mongodb_id',
  name: 'Dan smith',
}
应删除未指定的属性


我该怎么做呢?

实际上,由于mongoose实际上是在“干扰”隐藏的更新,这实际上是您提交给常规MongoDB函数的默认操作

所以猫鼬认为这是一种“明智的”便捷方法,可以“假定”你打算在这里发布指令。由于在这种情况下您实际上不想这样做,因此可以通过传递给任何
.update()
方法的选项中的
{overwrite:true}
关闭该行为:

作为一个完整的例子:

const mongoose = require('mongoose'),
      Schema = mongoose.Schema;

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const uri = 'mongodb://localhost/test',
      options = { useMongoClient: true };

const testSchema = new Schema({
  name: String,
  phone: String
});

const Test = mongoose.model('Test', testSchema);

function log(data) {
  console.log(JSON.stringify(data,undefined,2))
}

(async function() {

  try {

    const conn = await mongoose.connect(uri,options);

    // Clean data
    await Promise.all(
      Object.keys(conn.models).map( m => conn.models[m].remove({}) )
    );

    // Create a document
    let test = await Test.create({
      name: 'john doe',
      phone: '+12345678901'
    });
    log(test);

    // This update will apply using $set for the name
    let notover = await Test.findOneAndUpdate(
      { _id: test._id },
      { name: 'Bill S. Preston' },
      { new: true }
    );
    log(notover);

    // This update will just use the supplied object, and overwrite
    let updated = await Test.findOneAndUpdate(
      { _id: test._id },
      { name: 'Dan Smith' },
      { new: true, overwrite: true }
    );
    log(updated);


  } catch (e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }

})()
产生:

Mongoose: tests.remove({}, {})
Mongoose: tests.insert({ name: 'john doe', phone: '+12345678901', _id: ObjectId("596efb0ec941ff0ec319ac1e"), __v: 0 })
{
  "__v": 0,
  "name": "john doe",
  "phone": "+12345678901",
  "_id": "596efb0ec941ff0ec319ac1e"
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { '$set': { name: 'Bill S. Preston' } }, { new: true, upsert: false, remove: false, fields: {} })
{
  "_id": "596efb0ec941ff0ec319ac1e",
  "name": "Bill S. Preston",
  "phone": "+12345678901",
  "__v": 0
}
Mongoose: tests.findAndModify({ _id: ObjectId("596efb0ec941ff0ec319ac1e") }, [], { name: 'Dan Smith' }, { new: true, overwrite: true, upsert: false, remove: false, fields: {} })
{
  "_id": "596efb0ec941ff0ec319ac1e",
  "name": "Dan Smith"
}
显示文档是“覆盖”的,因为我们抑制了否则会被插入的操作。这两个示例首先显示没有应用修改器的
覆盖
选项的情况,然后显示带有
覆盖
选项的情况,其中您为“更新”传递的对象受到尊重,并且不应用此类修改器

注意,MongoDB节点驱动程序“默认”就是这样做的。所以在“隐式”中添加的行为是猫鼬做的,除非你告诉它不要这样做


注意真正的“替换”方法实际上是将
replaceOne
用作的API方法或通过。
overwrite
是mongoose希望应用上面描述和演示的
$set
的传统,但是MongoDB官方API将
replaceOne
作为
更新()的“特殊”之王引入
操作,该操作不允许在语句中使用原子运算符,如
$set
,如果尝试,将出错

这在语义上更加清晰,因为replace非常清楚地读取该方法的实际用途。在对
update()
变量的标准API调用中,当然仍然允许您省略原子运算符,并且无论如何只替换内容。但警告是意料之中的


您可以传递
upsert
选项,它将替换文档:

{
  _id: 'some_mongodb_id',
  name: 'john doe',
  phone: '+12345678901',
}
.findOneAndUpdate({_id: 'some_mongodb_id'}, {name: 'Dan smith'})
var collection = db.collection('test');
collection.findOneAndUpdate(
  {'_id': 'some_mongodb_id'},
  {name: 'Dan smith Only'},
  {upsert: true},
  function (err, doc) {
    console.log(doc);
  }
);
但这里的问题是,回调中的
doc
找到了文档,但没有更新。 因此,您需要执行以下操作:

var collection = db.collection('test');
collection.update(
  {'_id': 'some_mongodb_id'},
  {name: 'Dan smith Only'},
  {upsert: true},
  function (err, doc) {
    collection.findOne({'_id': 'some_mongodb_id'}, function (err, doc) {
        console.log(doc);
    });
  }
);

您是否尝试使用更新功能(而不是findOneAndUpdate)?@israel.zing我有,没有difference@israel.zinc这不会有什么不同。正是“猫鼬”通过添加到“更新对象”中来实现这一点,其中标准驱动程序只需按“原样”传递该对象。因此,需要为mongoose“关闭”此行为,以使其像您实际要求的那样工作。非常感谢,此覆盖功能完全没有文档记录:-(我收到此错误:没有重载与此调用匹配。上次重载出现以下错误。{upsert:true;overwrite:boolean;}类型的参数'不可分配给'QueryFindOneAndUpdateOptions'类型的参数。对象文字只能指定已知属性,并且'QueryFindOneAndUpdateOptions'类型中不存在'overwrite'。ts(2769)您可以使用选项{new:true}返回更新的文档。