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

也许是时候了,也许是我在稀less的文档中淹死了,不能把我的头围绕在Mongo的更新的概念:)

这是交易:

我有联系人模式和模式(缩短的属性):

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

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

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

现在我们遇到了这个问题:

  1. 如果我调用contact.save(function(err){...})如果具有相同电话号码的联系人已经存在(如预期的那样 – 唯一)
  2. 我无法在联系人上调用update() ,因为该方法在文档上不存在
  3. 如果我打电话更新模型:
    Contact.update({phone:request.phone}, contact, {upsert: true}, function(err{...})
    我进入了一个无限循环,因为Mongoose更新实现显然不需要一个对象作为第二个参数。
  4. 如果我这样做,但在第二个parameter passing一个请求属性{status: request.status, phone: request.phone ...}的关联数组它的工作原理 – 但我没有参考具体的联系和找不到它的updatedAtupdatedAt属性。

所以底线,毕竟我试过:给一个文件contact ,如何更新它,如果存在,或添加它,如果它不?

谢谢你的时间。

Mongoose现在支持findOneAndUpdate (调用MongoDB findAndModify )。

upsert = true选项创build该对象,如果它不存在。 默认为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"); }); 

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

  • 默认
  • 制定者
  • validation
  • 中间件

我刚刚烧了3个小时,试图解决同样的问题。 具体来说,我想“replace”整个文档,如果它存在,或者另外插入。 这是解决scheme:

 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{...}); 

我在Mongoose项目页面上创build了一个问题,要求将这个信息添加到文档中。

你近在咫尺

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

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

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

那么,我等了很久,没有回答。 最后放弃了整个更新/ upsert的方法,并与:

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

它工作吗? 是的。 我对此感到满意吗? 可能不会。 2个DB调用,而不是一个。
希望未来的Mongoose实现将提供一个Model.upsert函数。

我创build了一个StackOverflow帐户JUST来回答这个问题。 在无情地search互联网之后,我自己写了一些东西。 这是我做的,所以它可以应用于任何mongoose模型。 要么导入这个函数,要么直接把它添加到你正在进行更新的代码中。

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

然后插入一个mongoose文件做下面的事情,

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

这个解决scheme可能需要2个数据库调用,但是你得到的好处是,

  • 因为您正在使用.save()模式validation您的模型
  • 您可以在更新调用中插入深层嵌套对象而无需手动枚举,因此如果更改模型,则不必担心更新代码

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

而且,对于数组,如果现有对象的数组长于replace它的数组,则旧数组末尾的值将保留。 插入整个数组的简单方法是在upsert之前将旧数组设置为空数组,如果这是您打算执行的操作。

UPDATE – 01/16/2016我添加了一个额外的条件,如果有一个原始值的数组,Mongoose没有意识到数组变得更新没有使用“设置”function。

我需要将文档更新/插入到一个集合中,我所做的是创build一个新的对象字面,如下所示:

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

我从我的数据库中的其他地方得到的数据组成,然后在模型上调用更新

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

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

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

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

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

我正在使用mongoose版本3.6.16

非常优雅的解决scheme,您可以通过使用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); }); }); 
 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!' }); }); }); }); 

这里是一个更好的方法来解决在mongoose的更新方法,你可以检查Scotch.io的更多细节。 这绝对为我工作!

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

upsert用于在2.4上正常工作

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

看一看,它包含一些重要的信息

更新:

这并不意味着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); }); 
 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); } }); } });
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); } }); } }); 

对于任何人到这里仍然寻找一个良好的解决scheme“插入”钩支持,这是我已经testing和工作。 它仍然需要2个数据库调用,但比我在一次调用中尝试的任何东西都要稳定得多。

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

如果发电机可用,则变得更加容易:

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

这是最简单的创build/更新解决scheme,也称为中间件和validation器。

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

这对我工作。

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

这个coffeescript适用于我的Node – 诀窍是_id get在从客户端发送并返回时剥离了它的ObjectID包装器,所以这需要replace更新(当没有提供_id时,保存将恢复为插入和添加一)。

 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 

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

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

用它作为mongoose插件。 它build立了一个recursion合并传入对象的静态方法。

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

以Martin Kuzdowicz在上面发布的内容为基础。 我使用以下来使用mongoose和json对象的深度合并进行更新。 除了mongoose中的model.save()函数外,这允许mongoose做一个完整的validation,即使是依赖于json中的其他值的validation。 它确实需要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); }); }); 
 User.findByIdAndUpdate(req.param('userId'), req.body, (err, user) => { if(err) return res.json(err); res.json({ success: true }); }); 

没有其他解决scheme为我工作。 我正在使用post请求和更新数据,如果发现其他插入它,_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'); }); 

阅读上面的post后,我决定使用这个代码:

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

如果r为空,我们创build新的项目。 否则,在更新中使用upsert是因为更新不会创build新项目。