也许是时间的问题,也许是我淹没在稀疏的文档中,无法理解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
});

现在问题来了:

If I call contact.save(function(err){...}) I'll receive an error if the contact with the same phone number already exists (as expected - unique) I can't call update() on contact, since that method does not exist on a document If I call update on the model: Contact.update({phone:request.phone}, contact, {upsert: true}, function(err{...}) I get into an infinite loop of some sorts, since the Mongoose update implementation clearly doesn't want an object as the second parameter. If I do the same, but in the second parameter I pass an associative array of the request properties {status: request.status, phone: request.phone ...} it works - but then I have no reference to the specific contact and cannot find out its createdAt and updatedAt properties.

因此,我的底线是:给定一个文档联系人,如果它存在,我如何更新它,如果它不存在,我如何添加它?

谢谢你的时间。


当前回答

如果生成器可用,这将变得更加简单:

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

其他回答

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

只要记住,目标对象将总是覆盖源,即使源有一个现有的值

同样,对于数组,如果现有对象的数组比替换它的数组长,那么旧数组末尾的值将保持不变。上插入整个数组的一个简单方法是在上插入之前将旧数组设置为空数组(如果这是您打算做的)。

更新- 2016年1月16日 我添加了一个额外的条件,如果有一个原始值的数组,Mongoose不会意识到数组在不使用“set”函数的情况下被更新。

如果生成器可用,这将变得更加简单:

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

按照旅行技术人员的回答,这已经很棒了,我们可以创建一个插件,并在初始化后将其附加到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})。

我只是在一段时间后回到这个问题上,并决定基于Aaron Mast的答案发布一个插件。

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

将其作为猫鼬插件使用。它设置一个静态方法,递归地合并传入的对象。

Model.upsert({unique: 'value'}, updateObject});
User.findByIdAndUpdate(req.param('userId'), req.body, (err, user) => {
    if(err) return res.json(err);

    res.json({ success: true });
});