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

Tra*_*Guy 334 javascript mongoose mongodb node.js

也许是时候了,也许是我淹没在稀疏的文档中,而且无法绕过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);
Run Code Online (Sandbox Code Playgroud)

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

mongoose.connect(connectionString);
var contact = new Contact({
    phone: request.phone,
    status: request.status
});
Run Code Online (Sandbox Code Playgroud)

现在我们解决了这个问题:

  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,如果它存在,如何更新它,如果不存在则如何添加?

谢谢你的时间.

Pas*_*ius 392

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.');
});
Run Code Online (Sandbox Code Playgroud)

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

  • 默认
  • 制定者
  • 验证
  • 中间件

  • 这应该是最新的答案.大多数其他使用两个电话或(我相信)回归本机mongodb驱动程序. (14认同)
  • findOneAndUpdate的问题是不会执行预保存. (10认同)
  • 从文档:"......使用findAndModify助手时,不应用以下内容:默认值,设置器,验证器,中间件"http://mongoosejs.com/docs/api.html#model_Model.findOneAndUpdate (7认同)
  • 听起来像Mongoose或MongoDB中的错误? (2认同)
  • 我必须承认我的请求没有 `newData` :( (2认同)
  • @JamieHutber默认情况下不设置它,它是一个自定义属性 (2认同)

Cli*_*ris 186

我刚刚烧了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{...});
Run Code Online (Sandbox Code Playgroud)

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


chr*_*ian 89

你很亲密

Contact.update({phone:request.phone}, contact, {upsert: true}, function(err){...})
Run Code Online (Sandbox Code Playgroud)

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

Contact.update({phone:request.phone}, {$set: { phone: request.phone }}, {upsert: true}, function(err){...})
Run Code Online (Sandbox Code Playgroud)

  • 我不认为你需要`{$ set:...}`这里的部分作为我的阅读自动形式 (14认同)
  • 是的,mongoose说它把所有东西变成了$ set (5认同)
  • 如果您不时使用本机驱动程序,那么不使用$ set可能是一个坏习惯. (5认同)

Tra*_*Guy 69

好吧,我等了很久没有回答.最后放弃了整个更新/ 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);
            }
        });
    }
});
Run Code Online (Sandbox Code Playgroud)

它有用吗?是的.我对此感到满意吗?可能不是.2个DB调用而不是一个.
希望未来的Mongoose实现能够提供一个Model.upsert功能.

  • 值得注意的是,这是允许Mongoose验证器启动的唯一答案.[根据文档](http://mongoosejs.com/docs/api.html#model_Model.update),如果您不进行验证呼叫更新. (12认同)
  • **此示例使用MongoDB 2.2中添加的接口来指定文档表单中的multi和upsert选项... include :: /includes/fact-upsert-multi-options.rst**文档说明了这一点,不知道从哪里开始. (2认同)

vka*_*v15 46

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

await Contact.updateOne({
    phone: request.phone
}, { status: request.status }, { upsert: true });
Run Code Online (Sandbox Code Playgroud)

如果您需要更新的文档,您可以使用 Model.findOneAndUpdate()

const doc = await Contact.findOneAndUpdate({
    phone: request.phone
}, { status: request.status }, { upsert: true, useFindAndModify: false });
Run Code Online (Sandbox Code Playgroud)

关键点是您需要将filter参数中的唯一属性放入updateOne()or findOneAndUpdate(),并将其他属性放入update参数中。

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


Mar*_*icz 22

使用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);
    });
});
Run Code Online (Sandbox Code Playgroud)

  • 更优雅的是重写`(model)=> {return model.save(); ``as`mode => model.save()`,还有`(err)=> {res.send(err); }`as`err => res.send(err)`;) (3认同)

Aar*_*ast 15

我创建了一个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;
}
Run Code Online (Sandbox Code Playgroud)

然后要插入一个猫鼬文件,执行以下操作,

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

此解决方案可能需要2个DB呼叫,但您确实可以获得以下优势:

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

请记住,即使源具有现有值,目标对象也将始终覆盖源

此外,对于数组,如果现有对象的数组长于替换它的数组,则旧数组末尾的值将保留.一个简单的方法来upsert整个数组是在upsert之前将旧数组设置为一个空数组,如果这是你想要做的.

更新 - 01/16/2016我添加了一个额外的条件,如果有一个原始值数组,Mongoose没有意识到数组变得更新而不使用"set"函数.

  • +1为此创建acc:P希望我可以给另一个+ 1只是为了使用.save(),因为findOneAndUpate()使我们无法使用验证器和pre,post等东西.谢谢,我也会检查一下 (2认同)

小智 12

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

notificationObject = {
    user_id: user.user_id,
    feed: {
        feed_id: feed.feed_id,
        channel_id: feed.channel_id,
        feed_title: ''
    }
};
Run Code Online (Sandbox Code Playgroud)

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

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

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

1 { updatedExisting: false,
    upserted: 5289267a861b659b6a00c638,
    n: 1,
    connectionId: 11,
    err: null,
    ok: 1 }
Run Code Online (Sandbox Code Playgroud)

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

1 { updatedExisting: true, n: 1, connectionId: 18, err: null, ok: 1 }
Run Code Online (Sandbox Code Playgroud)

我正在使用mongoose版本3.6.16


Eyo*_*Eyo 10

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

        });
    });
Run Code Online (Sandbox Code Playgroud)

这是在mongoose中解决更新方法的更好方法,您可以查看Scotch.io以获取更多详细信息.这对我来说绝对有用!!!

  • 认为这与MongoDB的更新做同样的事情是错误的.这不是原子的. (5认同)

hel*_*pse 8

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);
    });
Run Code Online (Sandbox Code Playgroud)


Emm*_*kwe 5

这对我有用。

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

});
Run Code Online (Sandbox Code Playgroud)


小智 5

您可以使用此更新记录,并获取更新的数据作为响应

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"
                })
            }
        });
});
Run Code Online (Sandbox Code Playgroud)