Mongoose overwrite the document rather that `$set` fields

后端 未结 2 528
旧时难觅i
旧时难觅i 2020-11-30 12:26

Say, i have a document:

{
  _id: \'some_mongodb_id\',
  name: \'john doe\',
  phone: \'+12345678901\',
}

I want to update this document:

相关标签:
2条回答
  • 2020-11-30 13:13

    Actually, but for the fact that mongoose is actually "messing with" the update under the covers, this is actually the default action of your submission to a regular MongoDB function.

    So mongoose deems it "wise" as a convenience method to "presume" you meant to issue a $set instruction here. Since you actually do not want to do that in this case, you turn off that behavior via { overwrite: true } in the options passed to any .update() method:

    As a full example:

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

    Produces:

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

    Showing the document is "overwritten" because we suppressed the $set operation that otherwise would have been interpolated. The two samples show first without the overwrite option, which applies the $set modifier, and then "with" the overwrite option, where the object you passed in for the "update" is respected and no such $set modifier is applied.

    Note, this is how the MongoDB Node driver does this "by default". So the behavior of adding in the "implicit" $set is being done by mongoose, unless you tell it not to.


    NOTE The true way to "replace" would actually be to use replaceOne, either as the API method of replaceOne() or through bulkWrite(). The overwrite is a legacy of how mongoose wants to apply $set as described and demonstrated above, however the MongoDB official API introduces replaceOne as a "special" king of update() operation which does not allow the usage of atomic operators like $set within the statement and will error if you try.

    This is much clearer semantically since replace reads very clearly as to what the method is actually used for. Within standard API calls to the update() variants of course still allow you to omit the atomic operators and will just replace content anyway. But warnings should be expected.

    0 讨论(0)
  • 2020-11-30 13:15

    You can pass upsert option, and it will replace document:

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

    But the problem here - is that doc in callback is found document but not updated. Hence you need perform something like this:

    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);
        });
      }
    );
    
    0 讨论(0)
提交回复
热议问题