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

IT技术 javascript mongodb node.js mongoose
2021-01-25 06:50:15

也许是时候了,也许是我沉浸在稀疏的文档中而无法理解 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
});

现在我们遇到了问题:

  1. 如果我打电话给contact.save(function(err){...})我会收到一个错误,如果具有相同电话号码的联系人已经存在(正如预期的那样 - 唯一)
  2. 我无法update()联系,因为文档中不存在该方法
  3. 如果我对模型调用 update:
    Contact.update({phone:request.phone}, contact, {upsert: true}, function(err{...})
    我会进入某种无限循环,因为 Mongoose 更新实现显然不希望将对象作为第二个参数。
  4. 如果我也这样做,但在第二个参数中,我传递了一个{status: request.status, phone: request.phone ...}它工作的请求属性的关联数组- 但是我没有对特定联系人的引用,也无法找到它的createdAtupdatedAt属性。

所以最重要的是,毕竟我尝试过:给定一个文档contact,如果它存在,我如何更新它,或者如果它不存在,我如何添加它?

谢谢你的时间。

6个回答

Mongoose 现在通过findOneAndUpdate(调用 MongoDB findAndModify)原生支持这一点

如果对象不存在, 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.');
});

在旧版本中,Mongoose 不支持使用此方法的这些钩子:

  • 默认值
  • 二传手
  • 验证器
  • 中间件
听起来像是 Mongoose 或 MongoDB 中的错误?
2021-03-13 06:50:15
来自文档:“...当使用 findAndModify 助手时,以下不适用:默认值、设置器、验证器、中间件” mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate
2021-03-21 06:50:15
findOneAndUpdate 的问题在于不会执行预保存。
2021-03-27 06:50:15
这应该是最新的答案。大多数其他使用两个调用或(我相信)回退到本机 mongodb 驱动程序。
2021-04-02 06:50:15
@JamieHutber 这不是默认设置,它是一个自定义属性
2021-04-04 06:50:15

我刚刚花了 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{...});

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

对于未找到案例文档,使用哪个_id?mongoose生成它还是被查询的那个?
2021-03-29 06:50:15
目前文档似乎很差。API 文档中有一些(在页面上搜索“更新”。看起来像这样:MyModel.update({ age: { $gt: 18 } }, { oldEnough: true }, fn);MyModel.update({ name: 'Tobi' }, { ferret: true }, { multi: true }, fn);
2021-04-03 06:50:15

你很亲近

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

但是你的第二个参数应该是一个带有修改运算符的对象,例如

Contact.update({phone:request.phone}, {$set: { phone: request.phone }}, {upsert: true}, function(err){...})
我不认为你需要{$set: ... }这里部分作为它的自动形式我的阅读
2021-03-13 06:50:15
这在撰写本文时有效,我不再使用 MongoDB,所以我无法谈论最近几个月的变化:D
2021-03-14 06:50:15
如果您要不时使用本机驱动程序,则不使用 $set 可能是一个坏习惯。
2021-03-20 06:50:15
您可以使用 $set 和 $setOnInsert 在插入的情况下仅设置某些字段
2021-03-21 06:50:15
是的,mongoose说它把所有东西都变成了 $set
2021-04-09 06:50:15

好吧,我等了很长时间,没有回答。最终放弃了整个更新/更新插入方法并采用:

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功能。

尽管这应该可行,但您现在正在运行 2 个操作(查找、更新),而此时只需要 1 个(更新插入)。@chrixian 显示了执行此操作的正确方法。
2021-03-13 06:50:15
如果您需要.upsert()在所有型号上都可用,请参阅我基于此的回答stackoverflow.com/a/50208331/1586406
2021-03-15 06:50:15
本示例使用 MongoDB 2.2 中添加的接口在文档表单中指定 multi 和 upsert 选项。.. include:: /includes/fact-upsert-multi-options.rst文档说明了这一点,不知道从哪里开始。
2021-03-23 06:50:15
@fiznool 看起来您可以runValidators: true在更新期间手动传入选项更新文档(但是,更新验证器仅运行$set$unset操作)
2021-03-27 06:50:15
值得注意的是,这是允许 Mongoose 验证器启动的唯一答案。根据文档,如果您调用 update,则不会进行验证。
2021-03-30 06:50:15

我是mongoose的维护者。更新插入文档的更现代方法是使用Model.updateOne()函数.

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()or findOneAndUpdate(),并将其他属性放入update参数中。

这是有关使用 Mongoose 插入文档的教程