如何在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 });
现在我们遇到了这个问题:
- 如果我调用
contact.save(function(err){...})
如果具有相同电话号码的联系人已经存在(如预期的那样 – 唯一) - 我无法在联系人上调用
update()
,因为该方法在文档上不存在 - 如果我打电话更新模型:
Contact.update({phone:request.phone}, contact, {upsert: true}, function(err{...})
我进入了一个无限循环,因为Mongoose更新实现显然不需要一个对象作为第二个参数。 - 如果我这样做,但在第二个parameter passing一个请求属性
{status: request.status, phone: request.phone ...}
的关联数组它的工作原理 – 但我没有参考具体的联系和找不到它的updatedAt
和updatedAt
属性。
所以底线,毕竟我试过:给一个文件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新项目。