也许是时候了,也许是我淹没在稀疏的文档中,无法理解在 Mongoose 中更新的概念 :)
这是交易:
我有一个联系人架构和模型(缩短的属性):
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);
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 更新实现显然不希望对象作为第二个参数。如果我这样做,但在第二个参数中我传递请求属性的关联数组 {status: request.status, phone: request.phone ...} 它可以工作 - 但是我没有参考具体的联系人,也无法找到它的 createdAt 和 updatedAt 属性。
所以底线,毕竟我尝试过:给定一个文档 contact
,如果它存在,我如何更新它,或者如果它不存在则添加它?
谢谢你的时间。
save
连接 pre
怎么样?
Mongoose 现在通过 findOneAndUpdate(调用 MongoDB findAndModify)本机支持此功能。
如果对象不存在,upsert = true 选项会创建该对象。默认为假。
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 不支持使用此方法的这些钩子:
默认值
二传手
验证者
中间件
我刚刚花了 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{...});
我创建了 an issue on the Mongoose project page 请求将有关此的信息添加到文档中。
MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn);
和 MyModel.update({ name: 'Tobi' }, { ferret: true }, { multi: true }, fn);
你和你很亲近
Contact.update({phone:request.phone}, contact, {upsert: true}, function(err){...})
但是您的第二个参数应该是一个带有修改运算符的对象,例如
Contact.update({phone:request.phone}, {$set: { phone: request.phone }}, {upsert: true}, function(err){...})
{$set: ... }
部分作为我阅读的自动形式
好吧,我等了足够长的时间,没有答案。最后放弃了整个更新/更新方法并使用:
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 调用,而不是 1 个。
希望未来的 Mongoose 实现能够提供 Model.upsert
函数。
.upsert()
在所有型号上都可用,请参阅我基于此的答案。 stackoverflow.com/a/50208331/1586406
我是 Mongoose 的维护者。更新插入文档的更现代方法是使用 Model.updateOne()
function。
await Contact.updateOne({
phone: request.phone
}, { status: request.status }, { upsert: true });
如果您需要插入的文档,可以使用 Model.findOneAndUpdate()
const doc = await Contact.findOneAndUpdate({
phone: request.phone
}, { status: request.status }, { upsert: true, useFindAndModify: false });
关键要点是您需要将 filter
参数中的唯一属性放入 updateOne()
或 findOneAndUpdate()
,而将其他属性放入 update
参数中。
这是关于 upserting documents with Mongoose 的教程。
您可以通过使用 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);
});
});
(model) => { return model.save(); }
重写为 model => model.save()
,并将 (err) => { res.send(err); }
重写为 err => res.send(err)
;)
我创建了一个 StackOverflow 帐户只是为了回答这个问题。在无果地搜索互联网之后,我自己写了一些东西。我就是这样做的,因此它可以应用于任何猫鼬模型。导入此函数或将其直接添加到您正在进行更新的代码中。
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);
}
});
};
此解决方案可能需要 2 次 DB 调用,但您确实获得了以下好处,
因为您使用的是 .save(),所以针对您的模型进行模式验证
您可以在更新调用中插入深度嵌套的对象而无需手动枚举,因此如果您的模型发生更改,您不必担心更新代码
请记住,即使源具有现有值,目标对象也将始终覆盖源
此外,对于数组,如果现有对象的数组比替换它的数组长,则旧数组末尾的值将保留。 upsert 整个数组的一种简单方法是在 upsert 之前将旧数组设置为空数组,如果这是您打算这样做的话。
更新 - 2016 年 1 月 16 日我添加了一个额外条件,如果存在原始值数组,Mongoose 不会意识到该数组在不使用“set”函数的情况下会更新。
if(_.isObject(value) && _.keys(value).length !== 0) {
以停止堆栈溢出。 Lodash 4+ 在这里,它似乎在 keys
调用中将非对象值转换为对象,因此递归保护总是正确的。也许有更好的方法,但它现在几乎对我有用......
我需要将文档更新/插入到一个集合中,我所做的是创建一个新的对象文字,如下所示:
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 }
我正在使用猫鼬版本 3.6.16
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!' });
});
});
});
这是解决猫鼬更新方法的更好方法,您可以查看Scotch.io了解更多详细信息。这绝对对我有用!!!
2.6 中引入了一个错误,并影响到 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);
});
您可以简单地使用此更新记录并获取更新的数据作为响应
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"
})
}
});
});
这对我有用。
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"}); }); });
这是创建/更新同时调用中间件和验证器的最简单方法。
Contact.findOne({ phone: request.phone }, (err, doc) => {
const contact = (doc) ? doc.set(request) : new Contact(request);
contact.save((saveErr, savedContact) => {
if (saveErr) throw saveErr;
console.log(savedContact);
});
})
对于到达这里的任何人仍在寻找具有钩子支持的“更新插入”的良好解决方案,这就是我已经测试和工作的内容。它仍然需要 2 个 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);
});
});
}
如果生成器可用,它会变得更加容易:
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();
没有其他解决方案对我有用。如果发现其他插入它,我正在使用发布请求并更新数据,并且 _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');
});
按照 Traveling Tech Guy 的回答,这已经很棒了,我们可以创建一个插件,并在初始化后将其附加到 mongoose,以便 .upsert()
将在所有模型上可用。
插件.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()
}
}
数据库.js
import mongoose from 'mongoose'
import Plugins from './plugins'
mongoose.connect({ ... })
mongoose.plugin(Plugins)
export default mongoose
然后,您可以随时执行 User.upsert({ _id: 1 }, { foo: 'bar' })
或 YouModel.upsert({ bar: 'foo' }, { value: 1 })
之类的操作。
//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);
});
});
--
User.findByIdAndUpdate(req.param('userId'), req.body, (err, user) => {
if(err) return res.json(err);
res.json({ success: true });
});
一段时间后我才回到这个问题,并决定根据 Aaron Mast 的回答发布一个插件。
https://www.npmjs.com/package/mongoose-recursive-upsert
将其用作猫鼬插件。它设置了一个静态方法,该方法将递归合并传入的对象。
Model.upsert({unique: 'value'}, updateObject});
这个coffeescript适用于Node - 诀窍是_id get在从客户端发送和返回时被剥离其ObjectID包装器,因此需要更换更新(当没有提供_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
以 Martin Kuzdowicz 上面发布的内容为基础。我使用以下内容使用猫鼬和 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);
});
});
req.body
。
在阅读了上面的帖子后,我决定使用这段代码:
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 为空,我们创建新项目。否则,请在更新中使用 upsert,因为更新不会创建新项目。
不定期副业成功案例分享
newData
:(