如何在Mongoose中更新/更新文档?

时间:2022-10-30 14:17:18

Perhaps it's the time, perhaps it's me drowning in sparse documentation and not being able to wrap my head around the concept of updating in Mongoose :)

也许是时候了,也许是我沉浸在稀疏的文档中,无法用Mongoose来概括更新的概念:

Here's the deal:

这里的交易:

I have a contact schema and model (shortened properties):

我有一个联系模式和模型(简称属性):

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

var mongooseTypes = require("mongoose-types"),
    useTimestamps = mongooseTypes.useTimestamps;


var ContactSchema = new Schema({
    phone: {
        type: String,
        index: {
            unique: true,
            dropDups: true
        }
    },
    status: {
        type: String,
        lowercase: true,
        trim: true,
        default: 'on'
    }
});
ContactSchema.plugin(useTimestamps);
mongoose.model('Contact', ContactSchema); //is this line superflous??
var Contact = mongoose.model('Contact', ContactSchema);

I receive a request from the client, containing the fields I need and use my model thusly:

我收到客户的请求,包含我需要的字段,并使用我的模型:

mongoose.connect(connectionString);
var contact = new Contact({
    phone: request.phone,
    status: request.status
});

And now we reach the problem:

现在我们要解决的问题是

  1. If I call contact.save(function(err){...}) I'll receive an error if the contact with the same phone number already exists (as expected - unique)
  2. 如果我调用contact.save(function(err){…}
  3. I can't call update() on contact, since that method does not exist on a document
  4. 我不能在contact上调用update(),因为该方法在文档中不存在
  5. If I call update on the model:
    Contact.update({phone:request.phone}, contact, {upsert: true}, function(err{...})
    I get into an infinite loop of some sorts, since the Mongoose update implementation clearly doesn't want an object as the second parameter.
  6. 如果我在模型上调用update: Contact.update({phone:request)。phone}, contact, {upsert: true},函数(err{…})我进入了某种无限循环,因为Mongoose更新实现显然不想将对象作为第二个参数。
  7. If I do the same, but in the second parameter I pass an associative array of the request properties {status: request.status, phone: request.phone ...} it works - but then I have no reference to the specific contact and cannot find out its createdAt and updatedAt properties.
  8. 如果我执行相同的操作,但是在第二个参数中,我传递了请求属性{status: request的关联数组。状态,电话:请求。电话……}它工作—但是我没有对特定联系人的引用,也无法找到它的createdAt和updatedAt属性。

So the bottom line, after all I tried: given a document contact, how do I update it if it exists, or add it if it doesn't?

总之,我尝试过:给定一个文档联系人,如果它存在,我如何更新它,如果不存在,我如何添加它?

Thanks for your time.

谢谢你的时间。

23 个解决方案

#1


298  

Mongoose now supports this natively with findOneAndUpdate (calls MongoDB findAndModify).

Mongoose现在使用findOneAndUpdate(调用MongoDB findAndModify)来支持这一特性。

The upsert = true option creates the object if it doesn't exist. defaults to false.

如果对象不存在,则upsert = true选项将创建该对象。默认值为false。

var query = {'username':req.user.username};
req.newData.username = req.user.username;
MyModel.findOneAndUpdate(query, req.newData, {upsert:true}, function(err, doc){
    if (err) return res.send(500, { error: err });
    return res.send("succesfully saved");
});

Edit: Mongoose does not support these hooks with this method:

编辑:Mongoose不支持这种方法的钩子:

  • defaults
  • 违约
  • setters
  • setter
  • validators
  • 验证器
  • middleware
  • 中间件

#2


167  

I just burned a solid 3 hours trying to solve the same problem. Specifically, I wanted to "replace" the entire document if it exists, or insert it otherwise. Here's the solution:

我只是用了3个小时来解决同样的问题。具体地说,如果整个文档存在,我想“替换”它,或者插入它。解决方案:

var contact = new Contact({
  phone: request.phone,
  status: request.status
});

// Convert the Model instance to a simple object using Model's 'toObject' function
// to prevent weirdness like infinite looping...
var upsertData = contact.toObject();

// Delete the _id property, otherwise Mongo will return a "Mod on _id not allowed" error
delete upsertData._id;

// Do the upsert, which works like this: If no Contact document exists with 
// _id = contact.id, then create a new doc using upsertData.
// Otherwise, update the existing doc with upsertData
Contact.update({_id: contact.id}, upsertData, {upsert: true}, function(err{...});

I created an issue on the Mongoose project page requesting that info about this be added to the docs.

我在Mongoose项目页面上创建了一个问题,请求将有关这方面的信息添加到文档中。

#3


83  

You were close with

你是亲密的

Contact.update({phone:request.phone}, contact, {upsert: true}, function(err){...})

but your second parameter should be an object with a modification operator for example

但是第二个参数应该是一个带有修改操作符的对象。

Contact.update({phone:request.phone}, {$set: { phone: request.phone }}, {upsert: true}, function(err){...})

#4


59  

Well, I waited long enough and no answer. Finally gave up the whole update/upsert approach and went with:

我等了很久,没有人回答。最后放弃了整个更新/维护方法,继续:

ContactSchema.findOne({phone: request.phone}, function(err, contact) {
    if(!err) {
        if(!contact) {
            contact = new ContactSchema();
            contact.phone = request.phone;
        }
        contact.status = request.status;
        contact.save(function(err) {
            if(!err) {
                console.log("contact " + contact.phone + " created at " + contact.createdAt + " updated at " + contact.updatedAt);
            }
            else {
                console.log("Error: could not save contact " + contact.phone);
            }
        });
    }
});

Does it work? Yep. Am I happy with this? Probably not. 2 DB calls instead of one.
Hopefully a future Mongoose implementation would come up with a Model.upsert function.

它工作吗?是的。我对此满意吗?可能不会。2 DB而不是1。希望将来的Mongoose实现能够给出一个模型。插入功能。

#5


17  

Very elegant solution you can achieve by using chain of Promises:

非常优雅的解决方案,你可以通过使用承诺链实现:

app.put('url', (req, res) => {

    const modelId = req.body.model_id;
    const newName = req.body.name;

    MyModel.findById(modelId).then((model) => {
        return Object.assign(model, {name: newName});
    }).then((model) => {
        return model.save();
    }).then((updatedModel) => {
        res.json({
            msg: 'model updated',
            updatedModel
        });
    }).catch((err) => {
        res.send(err);
    });
});

#6


14  

I created a * account JUST to answer this question. After fruitlessly searching the interwebs I just wrote something myself. This is how I did it so it can be applied to any mongoose model. Either import this function or add it directly into your code where you are doing the updating.

我创建了一个*帐户来回答这个问题。在毫无结果地搜索互联网之后,我自己写了一些东西。我就是这样做的,所以它可以应用到任何蒙古模型中。要么导入这个函数,要么直接将它添加到正在进行更新的代码中。

function upsertObject (src, dest) {

  function recursiveFunc (src, dest) {
    _.forOwn(src, function (value, key) {
      if(_.isObject(value) && _.keys(value).length !== 0) {
        dest[key] = dest[key] || {};
        recursiveFunc(src[key], dest[key])
      } else if (_.isArray(src) && !_.isObject(src[key])) {
          dest.set(key, value);
      } else {
        dest[key] = value;
      }
    });
  }

  recursiveFunc(src, dest);

  return dest;
}

Then to upsert a mongoose document do the following,

然后更新一个蒙古文档,

YourModel.upsert = function (id, newData, callBack) {
  this.findById(id, function (err, oldData) {
    if(err) {
      callBack(err);
    } else {
      upsertObject(newData, oldData).save(callBack);
    }
  });
};

This solution may require 2 DB calls however you do get the benefit of,

这个解决方案可能需要2个DB的调用,但是您可以从,

  • Schema validation against your model because you are using .save()
  • 针对您的模型进行模式验证,因为您正在使用.save()
  • You can upsert deeply nested objects without manual enumeration in your update call, so if your model changes you do not have to worry about updating your code
  • 您可以在更新调用中无需手动枚举的情况下对深度嵌套的对象进行更新,因此,如果您的模型发生了更改,您不必担心更新代码

Just remember that the destination object will always override the source even if the source has an existing value

只要记住,目标对象将始终覆盖源,即使源具有现有的值

Also, for arrays, if the existing object has a longer array than the one replacing it then the values at the end of the old array will remain. An easy way to upsert the entire array is to set the old array to be an empty array before the upsert if that is what you are intending on doing.

同样,对于数组,如果现有对象的数组比替换的数组长,那么旧数组的末尾值将保留。更新整个数组的一个简单方法是将旧数组设置为在upsert之前的一个空数组,如果您打算这样做的话。

UPDATE - 01/16/2016 I added an extra condition for if there is an array of primitive values, Mongoose does not realize the array becomes updated without using the "set" function.

更新- 01/16/2016我增加了一个额外的条件,如果有一个基元值数组,Mongoose没有意识到数组在不使用“set”函数的情况下会被更新。

#7


12  

I needed to update/upsert a document into one collection, what I did was to create a new object literal like this:

我需要将一个文档更新/更新到一个集合中,我所做的就是创建一个新的对象文字:

notificationObject = {
    user_id: user.user_id,
    feed: {
        feed_id: feed.feed_id,
        channel_id: feed.channel_id,
        feed_title: ''
    }
};

composed from data that I get from somewhere else in my database and then call update on the Model

从数据库中的其他地方获得的数据组成,然后调用模型的update

Notification.update(notificationObject, notificationObject, {upsert: true}, function(err, num, n){
    if(err){
        throw err;
    }
    console.log(num, n);
});

this is the ouput that I get after running the script for the first time:

这是我第一次运行脚本后得到的输出:

1 { updatedExisting: false,
    upserted: 5289267a861b659b6a00c638,
    n: 1,
    connectionId: 11,
    err: null,
    ok: 1 }

And this is the output when I run the script for the second time:

这是我第二次运行脚本时的输出:

1 { updatedExisting: true, n: 1, connectionId: 18, err: null, ok: 1 }

I'm using mongoose version 3.6.16

我使用的是mongoose 3.6.16版本

#8


8  

app.put('url', function(req, res) {

        // use our bear model to find the bear we want
        Bear.findById(req.params.bear_id, function(err, bear) {

            if (err)
                res.send(err);

            bear.name = req.body.name;  // update the bears info

            // save the bear
            bear.save(function(err) {
                if (err)
                    res.send(err);

                res.json({ message: 'Bear updated!' });
            });

        });
    });

Here is a better approach to solving the update method in mongoose, you can check Scotch.io for more details. This definitely worked for me!!!

这里有一个更好的方法来解决mongoose的更新方法,您可以检查Scotch。io为更多的细节。这对我绝对有效!!!

#9


7  

There is a bug introduced in 2.6, and affects to 2.7 as well

在2.6中引入了一个bug,并影响到2.7。

The upsert used to work correctly on 2.4

upsert在2.4版本中正常工作

https://groups.google.com/forum/#!topic/mongodb-user/UcKvx4p4hnY https://jira.mongodb.org/browse/SERVER-13843

https://groups.google.com/forum/ !主题/ mongodb-user UcKvx4p4hnY https://jira.mongodb.org/browse/server - 13843

Take a look, it contains some important info

看看,里面有一些重要的信息

UPDATED:

It doesnt mean upsert does not work. Here is a nice example of how to use it:

这并不意味着upsert不起作用。这里有一个如何使用它的好例子:

User.findByIdAndUpdate(userId, {online: true, $setOnInsert: {username: username, friends: []}}, {upsert: true})
    .populate('friends')
    .exec(function (err, user) {
        if (err) throw err;
        console.log(user);

        // Emit load event

        socket.emit('load', user);
    });

#10


3  

this worked for me.

这为我工作。

app.put('/student/:id', (req, res) => {
    Student.findByIdAndUpdate(req.params.id, req.body, (err, user) => {
        if (err) {
            return res
                .status(500)
                .send({error: "unsuccessful"})
        };
        res.send({success: "success"});
    });

});

#11


2  

ContactSchema.connection.findOne({phone: request.phone}, function(err, contact) {
    if(!err) {
        if(!contact) {
            contact = new ContactSchema();
            contact.phone = request.phone;
        }
        contact.status = request.status;
        contact.save(function(err) {
            if(!err) {
                console.log("contact " + contact.phone + " created at " + contact.createdAt + " updated at " + contact.updatedAt);
            }
            else {
                console.log("Error: could not save contact " + contact.phone);
            }
        });
    }
});

#12


2  

//Here is my code to it... work like ninj

router.param('contractor', function(req, res, next, id) {
  var query = Contractors.findById(id);

  query.exec(function (err, contractor){
    if (err) { return next(err); }
    if (!contractor) { return next(new Error("can't find contractor")); }

    req.contractor = contractor;
    return next();
  });
});

router.get('/contractors/:contractor/save', function(req, res, next) {

    contractor = req.contractor ;
    contractor.update({'_id':contractor._id},{upsert: true},function(err,contractor){
       if(err){ 
            res.json(err);
            return next(); 
            }
    return res.json(contractor); 
  });
});


--

#13


1  

For anyone arriving here still looking for good a solution for "upserting" with hooks support, this is what I have tested and working. It still requires 2 DB calls but is much more stable than anything I've tried in a single call.

对于任何来到这里的人来说,他们仍然在寻找一种可以用钩子支撑的“向上”的解决方案,这就是我所测试和工作的。它仍然需要两个DB调用,但是比我在单个调用中尝试过的任何东西都要稳定得多。

// Create or update a Person by unique email.
// @param person - a new or existing Person
function savePerson(person, done) {
  var fieldsToUpdate = ['name', 'phone', 'address'];

  Person.findOne({
    email: person.email
  }, function(err, toUpdate) {
    if (err) {
      done(err);
    }

    if (toUpdate) {
      // Mongoose object have extra properties, we can either omit those props
      // or specify which ones we want to update.  I chose to update the ones I know exist
      // to avoid breaking things if Mongoose objects change in the future.
      _.merge(toUpdate, _.pick(person, fieldsToUpdate));
    } else {      
      toUpdate = person;
    }

    toUpdate.save(function(err, updated, numberAffected) {
      if (err) {
        done(err);
      }

      done(null, updated, numberAffected);
    });
  });
}

#14


1  

If generators are available it becomes even more easier:

如果有发电机,就更容易:

var query = {'username':this.req.user.username};
this.req.newData.username = this.req.user.username;
this.body = yield MyModel.findOneAndUpdate(query, this.req.newData).exec();

#15


1  

Here's the simplest solution that creates/updates and also calls the middleware and validators.

这是创建/更新并调用中间件和验证器的最简单的解决方案。

Contact.findOne({ phone: request.phone }, (err, doc) => {
    const contact = (doc) ? Object.assign(doc, request) : new Contact(request);

    contact.save((saveErr, savedContact) => {
        if (saveErr) throw saveErr;
        console.log(savedContact);
    });
})

#16


1  

I just came back to this issue after a while, and decided to publish a plugin based on the answer by Aaron Mast.

过了一会儿,我又回到了这个问题上,并决定基于Aaron Mast的回答发布一个插件。

https://www.npmjs.com/package/mongoose-recursive-upsert

https://www.npmjs.com/package/mongoose-recursive-upsert

Use it as a mongoose plugin. It sets up a static method which will recursively merge the object passed in.

使用它作为一个mongoose插件。它设置一个静态方法,该方法将递归地合并传入的对象。

Model.upsert({unique: 'value'}, updateObject});

#17


0  

This coffeescript works for me with Node - the trick is that the _id get's stripped of its ObjectID wrapper when sent and returned from the client and so this needs to be replaced for updates (when no _id is provided, save will revert to insert and add one).

这个coffeescript与Node一起工作——关键是在从客户端发送和返回时,_id get被剥离了它的ObjectID包装,因此需要替换它进行更新(当没有提供_id时,save将恢复为insert和add 1)。

app.post '/new', (req, res) ->
    # post data becomes .query
    data = req.query
    coll = db.collection 'restos'
    data._id = ObjectID(data._id) if data._id

    coll.save data, {safe:true}, (err, result) ->
        console.log("error: "+err) if err
        return res.send 500, err if err

        console.log(result)
        return res.send 200, JSON.stringify result

#18


0  

to build on what Martin Kuzdowicz posted above. I use the following to do an update using mongoose and a deep merge of json objects. Along with the model.save() function in mongoose this allows mongoose to do a full validation even one that relies on other values in the json. it does require the deepmerge package https://www.npmjs.com/package/deepmerge. But that is a very light weight package.

以马丁·库兹多维兹的文章为基础。我使用下面的代码进行更新,使用mongoose和json对象的深度合并。除了mongoose中的model.save()函数外,它还允许mongoose进行完整的验证,即使它依赖于json中的其他值。它确实需要deepmerge包https://www.npmjs.com/package/deepmerge。但这是一个很轻的包装。

var merge = require('deepmerge');

app.put('url', (req, res) => {

    const modelId = req.body.model_id;

    MyModel.findById(modelId).then((model) => {
        return Object.assign(model, merge(model.toObject(), req.body));
    }).then((model) => {
        return model.save();
    }).then((updatedModel) => {
        res.json({
            msg: 'model updated',
            updatedModel
        });
    }).catch((err) => {
        res.send(err);
    });
});

#19


0  

User.findByIdAndUpdate(req.param('userId'), req.body, (err, user) => {
    if(err) return res.json(err);

    res.json({ success: true });
});

#20


0  

No other solution worked for me. I'm using a post request and updating data if found else insert it, also _id is sent with the request body that's needs to be removed.

没有其他的办法对我有效。我正在使用post请求并更新数据,如果发现了else,插入它,_id也会随需要删除的请求体一起发送。

router.post('/user/createOrUpdate', function(req,res){
    var request_data = req.body;
    var userModel = new User(request_data);
    var upsertData = userModel.toObject();
    delete upsertData._id;

    var currentUserId;
    if (request_data._id || request_data._id !== '') {
        currentUserId = new mongoose.mongo.ObjectId(request_data._id);
    } else {
        currentUserId = new mongoose.mongo.ObjectId();
    }

    User.update({_id: currentUserId}, upsertData, {upsert: true},
        function (err) {
            if (err) throw err;
        }
    );
    res.redirect('/home');

});

#21


0  

Following Traveling Tech Guy's answer, which already awesome, we can create a plugin and attach it to mongoose once we initialise it so that .upsert() will be available on all models.

接下来,我们将创建一个插件并将其附加到mongoose上,这样。upsert()就可以在所有模型上使用了。

plugins.js

plugins.js

export default (schema, options) => {
  schema.statics.upsert = async function(query, data) {
    let record = await this.findOne(query)
    if (!record) {
      record = new this(data)
    } else {
      Object.keys(data).forEach(k => {
        record[k] = data[k]
      })
    }
    return await record.save()
  }
}

db.js

db.js

import mongoose from 'mongoose'

import Plugins from './plugins'

mongoose.connect({ ... })
mongoose.plugin(Plugins)

export default mongoose

Then you can do something like User.upsert({ _id: 1 }, { foo: 'bar' }) or YouModel.upsert({ bar: 'foo' }, { value: 1 }) whenever you want.

然后你可以做一些像User这样的事情。upsert({_id: 1}, {foo: 'bar'})或YouModel。upsert({bar: 'foo'}, {value: 1})

#22


0  

You can simply udpate the record with this and get the updated data in response

您可以简单地将记录进行udpate,并获得相应的更新后的数据。

router.patch('/:id', (req,res,next)=>{
const id = req.params.id;
Product.findByIdAndUpdate(id, req.body, {new: true}, 
function(err,model) {
    if(!err){
       res.status(201).json({
      data : model
       });
    }
   else{
      res.status(500).json({
      message: "not found any relative data"
               })
       }
     }); 
   }); 

#23


-3  

After reading the posts above, I decided to use this code:

读完上面的文章后,我决定使用下面的代码:

    itemModel.findOne({'pid':obj.pid},function(e,r){
        if(r!=null)
        {
             itemModel.update({'pid':obj.pid},obj,{upsert:true},cb);
        }
        else
        {
            var item=new itemModel(obj);
            item.save(cb);
        }
    });

if r is null, we create new item. Otherwise, use upsert in update because update does not create new item.

如果r为空,则创建新项。否则,在update中使用upsert,因为update不会创建新项。

#1


298  

Mongoose now supports this natively with findOneAndUpdate (calls MongoDB findAndModify).

Mongoose现在使用findOneAndUpdate(调用MongoDB findAndModify)来支持这一特性。

The upsert = true option creates the object if it doesn't exist. defaults to false.

如果对象不存在,则upsert = true选项将创建该对象。默认值为false。

var query = {'username':req.user.username};
req.newData.username = req.user.username;
MyModel.findOneAndUpdate(query, req.newData, {upsert:true}, function(err, doc){
    if (err) return res.send(500, { error: err });
    return res.send("succesfully saved");
});

Edit: Mongoose does not support these hooks with this method:

编辑:Mongoose不支持这种方法的钩子:

  • defaults
  • 违约
  • setters
  • setter
  • validators
  • 验证器
  • middleware
  • 中间件

#2


167  

I just burned a solid 3 hours trying to solve the same problem. Specifically, I wanted to "replace" the entire document if it exists, or insert it otherwise. Here's the solution:

我只是用了3个小时来解决同样的问题。具体地说,如果整个文档存在,我想“替换”它,或者插入它。解决方案:

var contact = new Contact({
  phone: request.phone,
  status: request.status
});

// Convert the Model instance to a simple object using Model's 'toObject' function
// to prevent weirdness like infinite looping...
var upsertData = contact.toObject();

// Delete the _id property, otherwise Mongo will return a "Mod on _id not allowed" error
delete upsertData._id;

// Do the upsert, which works like this: If no Contact document exists with 
// _id = contact.id, then create a new doc using upsertData.
// Otherwise, update the existing doc with upsertData
Contact.update({_id: contact.id}, upsertData, {upsert: true}, function(err{...});

I created an issue on the Mongoose project page requesting that info about this be added to the docs.

我在Mongoose项目页面上创建了一个问题,请求将有关这方面的信息添加到文档中。

#3


83  

You were close with

你是亲密的

Contact.update({phone:request.phone}, contact, {upsert: true}, function(err){...})

but your second parameter should be an object with a modification operator for example

但是第二个参数应该是一个带有修改操作符的对象。

Contact.update({phone:request.phone}, {$set: { phone: request.phone }}, {upsert: true}, function(err){...})

#4


59  

Well, I waited long enough and no answer. Finally gave up the whole update/upsert approach and went with:

我等了很久,没有人回答。最后放弃了整个更新/维护方法,继续:

ContactSchema.findOne({phone: request.phone}, function(err, contact) {
    if(!err) {
        if(!contact) {
            contact = new ContactSchema();
            contact.phone = request.phone;
        }
        contact.status = request.status;
        contact.save(function(err) {
            if(!err) {
                console.log("contact " + contact.phone + " created at " + contact.createdAt + " updated at " + contact.updatedAt);
            }
            else {
                console.log("Error: could not save contact " + contact.phone);
            }
        });
    }
});

Does it work? Yep. Am I happy with this? Probably not. 2 DB calls instead of one.
Hopefully a future Mongoose implementation would come up with a Model.upsert function.

它工作吗?是的。我对此满意吗?可能不会。2 DB而不是1。希望将来的Mongoose实现能够给出一个模型。插入功能。

#5


17  

Very elegant solution you can achieve by using chain of Promises:

非常优雅的解决方案,你可以通过使用承诺链实现:

app.put('url', (req, res) => {

    const modelId = req.body.model_id;
    const newName = req.body.name;

    MyModel.findById(modelId).then((model) => {
        return Object.assign(model, {name: newName});
    }).then((model) => {
        return model.save();
    }).then((updatedModel) => {
        res.json({
            msg: 'model updated',
            updatedModel
        });
    }).catch((err) => {
        res.send(err);
    });
});

#6


14  

I created a * account JUST to answer this question. After fruitlessly searching the interwebs I just wrote something myself. This is how I did it so it can be applied to any mongoose model. Either import this function or add it directly into your code where you are doing the updating.

我创建了一个*帐户来回答这个问题。在毫无结果地搜索互联网之后,我自己写了一些东西。我就是这样做的,所以它可以应用到任何蒙古模型中。要么导入这个函数,要么直接将它添加到正在进行更新的代码中。

function upsertObject (src, dest) {

  function recursiveFunc (src, dest) {
    _.forOwn(src, function (value, key) {
      if(_.isObject(value) && _.keys(value).length !== 0) {
        dest[key] = dest[key] || {};
        recursiveFunc(src[key], dest[key])
      } else if (_.isArray(src) && !_.isObject(src[key])) {
          dest.set(key, value);
      } else {
        dest[key] = value;
      }
    });
  }

  recursiveFunc(src, dest);

  return dest;
}

Then to upsert a mongoose document do the following,

然后更新一个蒙古文档,

YourModel.upsert = function (id, newData, callBack) {
  this.findById(id, function (err, oldData) {
    if(err) {
      callBack(err);
    } else {
      upsertObject(newData, oldData).save(callBack);
    }
  });
};

This solution may require 2 DB calls however you do get the benefit of,

这个解决方案可能需要2个DB的调用,但是您可以从,

  • Schema validation against your model because you are using .save()
  • 针对您的模型进行模式验证,因为您正在使用.save()
  • You can upsert deeply nested objects without manual enumeration in your update call, so if your model changes you do not have to worry about updating your code
  • 您可以在更新调用中无需手动枚举的情况下对深度嵌套的对象进行更新,因此,如果您的模型发生了更改,您不必担心更新代码

Just remember that the destination object will always override the source even if the source has an existing value

只要记住,目标对象将始终覆盖源,即使源具有现有的值

Also, for arrays, if the existing object has a longer array than the one replacing it then the values at the end of the old array will remain. An easy way to upsert the entire array is to set the old array to be an empty array before the upsert if that is what you are intending on doing.

同样,对于数组,如果现有对象的数组比替换的数组长,那么旧数组的末尾值将保留。更新整个数组的一个简单方法是将旧数组设置为在upsert之前的一个空数组,如果您打算这样做的话。

UPDATE - 01/16/2016 I added an extra condition for if there is an array of primitive values, Mongoose does not realize the array becomes updated without using the "set" function.

更新- 01/16/2016我增加了一个额外的条件,如果有一个基元值数组,Mongoose没有意识到数组在不使用“set”函数的情况下会被更新。

#7


12  

I needed to update/upsert a document into one collection, what I did was to create a new object literal like this:

我需要将一个文档更新/更新到一个集合中,我所做的就是创建一个新的对象文字:

notificationObject = {
    user_id: user.user_id,
    feed: {
        feed_id: feed.feed_id,
        channel_id: feed.channel_id,
        feed_title: ''
    }
};

composed from data that I get from somewhere else in my database and then call update on the Model

从数据库中的其他地方获得的数据组成,然后调用模型的update

Notification.update(notificationObject, notificationObject, {upsert: true}, function(err, num, n){
    if(err){
        throw err;
    }
    console.log(num, n);
});

this is the ouput that I get after running the script for the first time:

这是我第一次运行脚本后得到的输出:

1 { updatedExisting: false,
    upserted: 5289267a861b659b6a00c638,
    n: 1,
    connectionId: 11,
    err: null,
    ok: 1 }

And this is the output when I run the script for the second time:

这是我第二次运行脚本时的输出:

1 { updatedExisting: true, n: 1, connectionId: 18, err: null, ok: 1 }

I'm using mongoose version 3.6.16

我使用的是mongoose 3.6.16版本

#8


8  

app.put('url', function(req, res) {

        // use our bear model to find the bear we want
        Bear.findById(req.params.bear_id, function(err, bear) {

            if (err)
                res.send(err);

            bear.name = req.body.name;  // update the bears info

            // save the bear
            bear.save(function(err) {
                if (err)
                    res.send(err);

                res.json({ message: 'Bear updated!' });
            });

        });
    });

Here is a better approach to solving the update method in mongoose, you can check Scotch.io for more details. This definitely worked for me!!!

这里有一个更好的方法来解决mongoose的更新方法,您可以检查Scotch。io为更多的细节。这对我绝对有效!!!

#9


7  

There is a bug introduced in 2.6, and affects to 2.7 as well

在2.6中引入了一个bug,并影响到2.7。

The upsert used to work correctly on 2.4

upsert在2.4版本中正常工作

https://groups.google.com/forum/#!topic/mongodb-user/UcKvx4p4hnY https://jira.mongodb.org/browse/SERVER-13843

https://groups.google.com/forum/ !主题/ mongodb-user UcKvx4p4hnY https://jira.mongodb.org/browse/server - 13843

Take a look, it contains some important info

看看,里面有一些重要的信息

UPDATED:

It doesnt mean upsert does not work. Here is a nice example of how to use it:

这并不意味着upsert不起作用。这里有一个如何使用它的好例子:

User.findByIdAndUpdate(userId, {online: true, $setOnInsert: {username: username, friends: []}}, {upsert: true})
    .populate('friends')
    .exec(function (err, user) {
        if (err) throw err;
        console.log(user);

        // Emit load event

        socket.emit('load', user);
    });

#10


3  

this worked for me.

这为我工作。

app.put('/student/:id', (req, res) => {
    Student.findByIdAndUpdate(req.params.id, req.body, (err, user) => {
        if (err) {
            return res
                .status(500)
                .send({error: "unsuccessful"})
        };
        res.send({success: "success"});
    });

});

#11


2  

ContactSchema.connection.findOne({phone: request.phone}, function(err, contact) {
    if(!err) {
        if(!contact) {
            contact = new ContactSchema();
            contact.phone = request.phone;
        }
        contact.status = request.status;
        contact.save(function(err) {
            if(!err) {
                console.log("contact " + contact.phone + " created at " + contact.createdAt + " updated at " + contact.updatedAt);
            }
            else {
                console.log("Error: could not save contact " + contact.phone);
            }
        });
    }
});

#12


2  

//Here is my code to it... work like ninj

router.param('contractor', function(req, res, next, id) {
  var query = Contractors.findById(id);

  query.exec(function (err, contractor){
    if (err) { return next(err); }
    if (!contractor) { return next(new Error("can't find contractor")); }

    req.contractor = contractor;
    return next();
  });
});

router.get('/contractors/:contractor/save', function(req, res, next) {

    contractor = req.contractor ;
    contractor.update({'_id':contractor._id},{upsert: true},function(err,contractor){
       if(err){ 
            res.json(err);
            return next(); 
            }
    return res.json(contractor); 
  });
});


--

#13


1  

For anyone arriving here still looking for good a solution for "upserting" with hooks support, this is what I have tested and working. It still requires 2 DB calls but is much more stable than anything I've tried in a single call.

对于任何来到这里的人来说,他们仍然在寻找一种可以用钩子支撑的“向上”的解决方案,这就是我所测试和工作的。它仍然需要两个DB调用,但是比我在单个调用中尝试过的任何东西都要稳定得多。

// Create or update a Person by unique email.
// @param person - a new or existing Person
function savePerson(person, done) {
  var fieldsToUpdate = ['name', 'phone', 'address'];

  Person.findOne({
    email: person.email
  }, function(err, toUpdate) {
    if (err) {
      done(err);
    }

    if (toUpdate) {
      // Mongoose object have extra properties, we can either omit those props
      // or specify which ones we want to update.  I chose to update the ones I know exist
      // to avoid breaking things if Mongoose objects change in the future.
      _.merge(toUpdate, _.pick(person, fieldsToUpdate));
    } else {      
      toUpdate = person;
    }

    toUpdate.save(function(err, updated, numberAffected) {
      if (err) {
        done(err);
      }

      done(null, updated, numberAffected);
    });
  });
}

#14


1  

If generators are available it becomes even more easier:

如果有发电机,就更容易:

var query = {'username':this.req.user.username};
this.req.newData.username = this.req.user.username;
this.body = yield MyModel.findOneAndUpdate(query, this.req.newData).exec();

#15


1  

Here's the simplest solution that creates/updates and also calls the middleware and validators.

这是创建/更新并调用中间件和验证器的最简单的解决方案。

Contact.findOne({ phone: request.phone }, (err, doc) => {
    const contact = (doc) ? Object.assign(doc, request) : new Contact(request);

    contact.save((saveErr, savedContact) => {
        if (saveErr) throw saveErr;
        console.log(savedContact);
    });
})

#16


1  

I just came back to this issue after a while, and decided to publish a plugin based on the answer by Aaron Mast.

过了一会儿,我又回到了这个问题上,并决定基于Aaron Mast的回答发布一个插件。

https://www.npmjs.com/package/mongoose-recursive-upsert

https://www.npmjs.com/package/mongoose-recursive-upsert

Use it as a mongoose plugin. It sets up a static method which will recursively merge the object passed in.

使用它作为一个mongoose插件。它设置一个静态方法,该方法将递归地合并传入的对象。

Model.upsert({unique: 'value'}, updateObject});

#17


0  

This coffeescript works for me with Node - the trick is that the _id get's stripped of its ObjectID wrapper when sent and returned from the client and so this needs to be replaced for updates (when no _id is provided, save will revert to insert and add one).

这个coffeescript与Node一起工作——关键是在从客户端发送和返回时,_id get被剥离了它的ObjectID包装,因此需要替换它进行更新(当没有提供_id时,save将恢复为insert和add 1)。

app.post '/new', (req, res) ->
    # post data becomes .query
    data = req.query
    coll = db.collection 'restos'
    data._id = ObjectID(data._id) if data._id

    coll.save data, {safe:true}, (err, result) ->
        console.log("error: "+err) if err
        return res.send 500, err if err

        console.log(result)
        return res.send 200, JSON.stringify result

#18


0  

to build on what Martin Kuzdowicz posted above. I use the following to do an update using mongoose and a deep merge of json objects. Along with the model.save() function in mongoose this allows mongoose to do a full validation even one that relies on other values in the json. it does require the deepmerge package https://www.npmjs.com/package/deepmerge. But that is a very light weight package.

以马丁·库兹多维兹的文章为基础。我使用下面的代码进行更新,使用mongoose和json对象的深度合并。除了mongoose中的model.save()函数外,它还允许mongoose进行完整的验证,即使它依赖于json中的其他值。它确实需要deepmerge包https://www.npmjs.com/package/deepmerge。但这是一个很轻的包装。

var merge = require('deepmerge');

app.put('url', (req, res) => {

    const modelId = req.body.model_id;

    MyModel.findById(modelId).then((model) => {
        return Object.assign(model, merge(model.toObject(), req.body));
    }).then((model) => {
        return model.save();
    }).then((updatedModel) => {
        res.json({
            msg: 'model updated',
            updatedModel
        });
    }).catch((err) => {
        res.send(err);
    });
});

#19


0  

User.findByIdAndUpdate(req.param('userId'), req.body, (err, user) => {
    if(err) return res.json(err);

    res.json({ success: true });
});

#20


0  

No other solution worked for me. I'm using a post request and updating data if found else insert it, also _id is sent with the request body that's needs to be removed.

没有其他的办法对我有效。我正在使用post请求并更新数据,如果发现了else,插入它,_id也会随需要删除的请求体一起发送。

router.post('/user/createOrUpdate', function(req,res){
    var request_data = req.body;
    var userModel = new User(request_data);
    var upsertData = userModel.toObject();
    delete upsertData._id;

    var currentUserId;
    if (request_data._id || request_data._id !== '') {
        currentUserId = new mongoose.mongo.ObjectId(request_data._id);
    } else {
        currentUserId = new mongoose.mongo.ObjectId();
    }

    User.update({_id: currentUserId}, upsertData, {upsert: true},
        function (err) {
            if (err) throw err;
        }
    );
    res.redirect('/home');

});

#21


0  

Following Traveling Tech Guy's answer, which already awesome, we can create a plugin and attach it to mongoose once we initialise it so that .upsert() will be available on all models.

接下来,我们将创建一个插件并将其附加到mongoose上,这样。upsert()就可以在所有模型上使用了。

plugins.js

plugins.js

export default (schema, options) => {
  schema.statics.upsert = async function(query, data) {
    let record = await this.findOne(query)
    if (!record) {
      record = new this(data)
    } else {
      Object.keys(data).forEach(k => {
        record[k] = data[k]
      })
    }
    return await record.save()
  }
}

db.js

db.js

import mongoose from 'mongoose'

import Plugins from './plugins'

mongoose.connect({ ... })
mongoose.plugin(Plugins)

export default mongoose

Then you can do something like User.upsert({ _id: 1 }, { foo: 'bar' }) or YouModel.upsert({ bar: 'foo' }, { value: 1 }) whenever you want.

然后你可以做一些像User这样的事情。upsert({_id: 1}, {foo: 'bar'})或YouModel。upsert({bar: 'foo'}, {value: 1})

#22


0  

You can simply udpate the record with this and get the updated data in response

您可以简单地将记录进行udpate,并获得相应的更新后的数据。

router.patch('/:id', (req,res,next)=>{
const id = req.params.id;
Product.findByIdAndUpdate(id, req.body, {new: true}, 
function(err,model) {
    if(!err){
       res.status(201).json({
      data : model
       });
    }
   else{
      res.status(500).json({
      message: "not found any relative data"
               })
       }
     }); 
   }); 

#23


-3  

After reading the posts above, I decided to use this code:

读完上面的文章后,我决定使用下面的代码:

    itemModel.findOne({'pid':obj.pid},function(e,r){
        if(r!=null)
        {
             itemModel.update({'pid':obj.pid},obj,{upsert:true},cb);
        }
        else
        {
            var item=new itemModel(obj);
            item.save(cb);
        }
    });

if r is null, we create new item. Otherwise, use upsert in update because update does not create new item.

如果r为空,则创建新项。否则,在update中使用upsert,因为update不会创建新项。