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

时间:2013-05-06 14:28:08

标签: node.js mongodb express mongoose

使用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.lastprofile.name.middle属性,最终会得到一个类似以下的文档:

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

所以它基本上用profile覆盖所有req.body.profile,我认为这是有意义的。有什么方法可以通过在更新查询中指定我的字段而不是req.body来更明确吗?

4 个答案:

答案 0 :(得分:12)

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

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

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

答案 1 :(得分:8)

使用Moongose 4.1flat包解决此问题的一种非常简单的方法:

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对象。

答案 2 :(得分:0)

我认为你正在寻找$ set

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

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

试试

答案 3 :(得分:0)

也许这是一个很好的解决方案 - 为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,但这些功能很容易被其他类似物替换