首页 文章

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

提问于
浏览
282

也许是时候了,也许是我淹没在稀疏的文档中,而且无法绕过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);
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() ,因为该方法在文档中不存在

  • 如果我在模型上调用update:
    Contact.update({phone:request.phone}, contact, {upsert: true}, function(err{...})
    我进入了一些无限循环,因为Mongoose更新实现显然不希望将对象作为第二个参数 .

  • 如果我这样做,但在第二个参数中我传递了一个请求属性的关联数组 {status: request.status, phone: request.phone ...} 它可以工作 - 但是我没有引用特定的联系人,也找不到它的 createdAtupdatedAt 属性 .

毕竟我尝试了底线:给定一个文件 contact ,如果它存在,如何更新它,如果不存在则如何添加?

谢谢你的时间 .

23 回答

  • 4
    User.findByIdAndUpdate(req.param('userId'), req.body, (err, user) => {
        if(err) return res.json(err);
    
        res.json({ success: true });
    });
    
  • 1

    一段时间后我才回到这个问题,并决定根据Aaron Mast的答案发布一个插件 .

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

    将它用作mongoose插件 . 它设置一个静态方法,它将递归合并传入的对象 .

    Model.upsert({unique: 'value'}, updateObject});
    
  • 86
    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以获取更多详细信息 . 这对我来说绝对有用!!!

  • 12

    使用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);
        });
    });
    
  • 8

    我刚刚烧了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请求将有关此信息添加到文档中 .

  • 1

    好吧,我等了很久没有回答 . 最后放弃了整个更新/ 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 功能 .

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

    没有其他解决方案适合我 . 我正在使用发布请求并更新数据,如果找到其他插入它,也_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');
    
    });
    
  • 14
    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);
                }
            });
        }
    });
    
  • 0

    对于到这里的人来说仍然在寻找一个好的解决方案来支持“挂钩”,这是我测试和工作的 . 它仍然需要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);
        });
      });
    }
    
  • 64

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

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

    这是创建/更新的最简单方法,同时还调用中间件和验证器 .

    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

    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不支持使用此方法的这些挂钩:

    • 默认值

    • 二传手

    • 验证器

    • 中间件

  • 21

    阅读上面的帖子后,我决定使用这段代码:

    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为null,我们创建新项目 . 否则,请在更新中使用upsert,因为update不会创建新项 .

  • 1

    我创建了一个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;
    }
    

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

    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之前将旧数组设置为空数组,如果这是你想要做的事情 .

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

  • 1

    这对我有用 .

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

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

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

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

    你很亲近

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

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

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

    以Martin Kuzdowicz上面发布的内容为基础 . 我使用以下内容使用mongoose和json对象的深度合并进行更新 . 与mongoose中的model.save()函数一起,这允许mongoose进行完全验证,即使是依赖于json中的其他值的mongoose . 它确实需要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);
        });
    });
    
  • 0

    这个coffeescript适用于我的Node - 诀窍是_id get在客户端发送和返回时被剥夺了ObjectID包装,因此需要替换更新(当没有提供_id时,save将恢复为插入和添加一) .

    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
    
  • 2

    按照Traveling Tech Guy的答案,已经很棒了,我们可以创建一个插件,并在初始化后将它附加到mongoose,以便 .upsert() 将在所有型号上可用 .

    plugins.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()
      }
    }
    

    db.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 }) 之类的事情 .

  • 3

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

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

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

    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

相关问题