问题 Mongoose.js:嵌套属性的原子更新?


使用Mongoose版本3.6.4

假设我有一个像这样的MongoDB文档:

{
    "_id" : "5187b74e66ee9af96c39d3d6",
    "profile" : {
        "name" : {
            "first" : "Joe",
            "last" : "Pesci",
            "middle" : "Frank"
        }
    }
}

我为用户提供以下架构:

var UserSchema = new mongoose.Schema({
  _id:    { type: String },
  email:  { type: String, required: true, index: { unique: true }},
  active: { type: Boolean, required: true, 'default': false },
  profile: {
    name: {
      first:    { type: String, required: true },
      last:     { type: String, required: true },
      middle:   { type: String }
    }
  }
  created:    { type: Date, required: true, 'default': Date.now},
  updated:    { type: Date, required: true, 'default': Date.now}
);

我提交了一个表单,其中包含一个名为: profile[name][first] 价值为 Joseph

因此我想只更新用户的名字,但留下他的最后和中间人,我想我会这样做:

User.update({email: "joe@foo.com"}, req.body, function(err, result){});

但是,当我这样做时,它会“删除” profile.name.last 和 profile.name.middle 属性和我最终得到一个看起来像这样的文档:

{
    "_id" : "5187b74e66ee9af96c39d3d6",
    "profile" : {
        "name" : {
            "first" : "Joseph"
        }
    }
}

所以它基本上覆盖了所有 profile 同 req.body.profile,我觉得有道理。有没有办法绕过它而不必通过在更新查询中指定我的字段而不是更明确 req.body


12490
2018-05-06 14:28


起源

考虑接受Aichholzer的答案,因为它为问题提供了真正的解决方案。 - zbr


答案:


你是对的,Mongoose将更新转换为 $set 为你。但这并不能解决您的问题。在mongodb shell中尝试一下,你会看到相同的行为。

相反,要更新单个深度嵌套的属性,您需要指定深度属性的完整路径 $set

User.update({ email: 'joe@foo.com' }, { 'profile.name.first': 'Joseph' }, callback)

8
2018-05-08 16:47



是的,但我希望我可以通过 req.body 所以我没有必要指定所有可能的字段。所以我最终做了一个 findById() 首先,删除 __v 和 _id 从找到的文档,使用 _.deepExtend() 合并新的更新属性,然后将我的frankenstein对象传递给Mongoose的 update。 - k00k
@aaronheckmann,无论如何告诉mongoose或mongo“这是我最新的对象,只是更新它”? - Nam Nguyen
@ k00k - 那种方法是原子的吗?它不会再写完整个文档吗?你为什么要删除__v和_id? - Douglas Ferguson
@DouglasFerguson自从这个特定的问题和Mongoose版本已经很长时间了,所以我会试着记住,但默认情况下Mongoose更新是原子的。请参阅aaronheckman关于$ set的答案。至于__v和__id,不知道为什么我这样做,现在似乎不需要,但我确信我在做deepExtend的方式有一个原因。对不起,我记不起那一点了。 - k00k
如何编辑以使用展平库来显示示例,例如 github.com/hughsk/flat - 所以没有必要手动“翻译”嵌套路径? - bardzusny


一个非常简单的方法来解决这个问题 Moongose 4.1 和 flat 包:

var flat = require('flat'),
    Schema = mongoose.Schema,
        schema = new Schema(
            {
                name: {
                    first: {
                        type: String,
                        trim: true
                    },
                    last: {
                        type: String,
                        trim: true
                    }
                }
            }
        );

    schema.pre('findOneAndUpdate', function () {
        this._update = flat(this._update);
    });


    mongoose.model('User', schema);

req.body (例如)现在可以是:

{
    name: {
        first: 'updatedFirstName'
    }
}

因此,在执行实际查询之前,对象将被展平 $set 将仅更新预期的属性而不是整个 name 目的。


8
2017-07-28 17:48



在嵌套ObjectId时要小心。 flat将把它们变成'something._bsontype':'ObjectID','something.id':<Buffer 58 36 ..>' - tdecs


我想你正在寻找$ set

http://docs.mongodb.org/manual/reference/operator/set/

User.update({email: "joe@foo.com"}, { $set : req.body}, function(err, result){});

试试吧


0
2018-05-06 14:45



我认为Mongoose默认是“安全的”所以 update 真的是一个 $set, 我错了吗? - k00k
并补充一点,我只是尝试了它,它不起作用。这似乎是嵌套属性的问题。只是为了澄清,它只是同一级别(兄弟姐妹)的嵌套属性被吹走了。 - k00k
您正在寻找具有特定字段名称的$ set - “name.first”。这与安全与不安全写入无关,只是告诉它是更新整个文档还是单个字段。 - Asya Kamsky
@all,我一直试图找到答案,希望有人知道。无论如何告诉mongoose或mongo“这是我最新的对象,只是更新它”? - Nam Nguyen


也许这是一个很好的解决方案 - 为Model.update添加选项,替换嵌套对象,如:

{field1:1,fields2:{a:1,b:2}} => {'field1':1,'field2.a':1,'field2.b':2}

  nestedToDotNotation: function(obj, keyPrefix) {
    var result;
    if (keyPrefix == null) {
      keyPrefix = '';
    }
    result = {};
    _.each(obj, function(value, key) {
      var nestedObj, result_key;
      result_key = keyPrefix + key;
      if (!_.isArray(value) && _.isObject(value)) {
        result_key += '.';
        nestedObj = module.exports.nestedToDotNotation(value, result_key);
        return _.extend(result, nestedObj);
      } else {
        return result[result_key] = value;
      }
    });
    return result;
  }

});

需要改进循环引用处理,但在使用嵌套对象时这非常有用

我在这里使用underscore.js,但这些功能很容易被其他类似物替换


0
2018-03-20 10:58