如何在Mongoose中更新/上传文档?

也许是时候了,也许是我淹没在稀疏的文档中,无法将自己的头围在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,如何更新它(如果存在),或者如何添加它(如果不存在)?

谢谢你的时间。

泡芙Stafan2020/03/18 15:31:32

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

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);
    });
})
梅Harry米亚2020/03/18 15:31:31

我是猫鼬的维护者。更新文档的更现代方法是使用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 });

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

这是有关使用Mongoose升级文档的教程

路易神奇猪猪2020/03/18 15:31:31

我创建了一个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()
  • 您可以在更新调用中向上插入深层嵌套的对象而无需手动枚举,因此,如果模型发生更改,则不必担心更新代码

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

Also, for arrays, if the existing object has a longer array than the one replacing it then the values at the end of the old array will remain. An easy way to upsert the entire array is to set the old array to be an empty array before the upsert if that is what you are intending on doing.

UPDATE - 01/16/2016 I added an extra condition for if there is an array of primitive values, Mongoose does not realize the array becomes updated without using the "set" function.

西门ItachiL2020/03/18 15:31:31

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

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 }

我正在使用猫鼬版本3.6.16

逆天前端2020/03/18 15:31:31

你很亲近

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

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

Contact.update({phone:request.phone}, {$set: { phone: request.phone }}, {upsert: true}, function(err){...})
斯丁Tony泡芙2020/03/18 15:31:31

好吧,我等待了足够长的时间,没有答案。最后放弃了整个更新/更新方法,并进行了以下操作:

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