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

问题描述:

使用猫鼬版本3.6.4Mongoose.js:嵌套属性的原子更新?

说我有一个像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

因此我想只更新用户的fi第一个名字,但保留他最后的和中间的孤独,我以为我会只是做:

User.update({email: "[email protected]"}, req.body, function(err, result){}); 

但是,当我做到这一点,“删除”的profile.name.lastprofile.name.middle属性和我结束了一个文档,看起来像:

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

所以基本上覆盖所有的profilereq.body.profile,我的猜测是有道理的。有没有办法解决这个问题,而不必通过在更新查询中指定我的字段来代替req.body

+0

考虑接受Aichholzer的答案,因为它提供了一个真正的解决方案。 – zbr 2015-12-10 19:06:33

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

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

User.update({ email: '[email protected]' }, { 'profile.name.first': 'Joseph' }, callback) 
+3

没错,但我希望能传递'req.body',所以我不必指定所有可能的字段。所以我最终做了一个'findById()',从找到的文档中删除'__v'和'_id',使用_.deepExtend()'合并到新的更新属性中,然后将我的frankenstein对象传递给Mongoose 'update'。 – k00k 2013-05-08 18:29:09

+1

@aaronheckmann,无论如何告诉猫鼬或mongo“这里是我的最新对象,只是更新它”? – 2013-09-17 23:27:34

+0

@ k00k - 这种方法是原子吗?它不会再写整个文档吗?为什么你必须删除__v和_id? – 2015-02-15 21:54:27

我认为你正在寻找$ SET

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

User.update({email: "[email protected]"}, { $set : req.body}, function(err, result){}); 

试一下

+0

我以为Mongoose在默认情况下是“安全的”,所以'update'确实是'$ set',我错了吗? – k00k 2013-05-06 14:59:24

+0

并添加,我只是试了一下,它不起作用。这似乎是嵌套属性的问题。只是为了澄清,它只是在同一级别(兄弟姐妹)的嵌套属性被吹走。 – k00k 2013-05-06 15:12:48

+0

您正在寻找带有特定字段名称的$ set - “name.first”。这与安全与不安全写入无关,只是告诉它更新整个文档还是单个字段。 – 2013-05-06 15:27:27

也许这是一个很好的解决方案 - 添加选项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; 
    } 

});

需要改进的循环引用的处理,但这种嵌套对象

我使用下划线工作时是非常有用的。js的这里,但是这些功能可以轻松地与其它类似物来代替

一个非常容易的方法与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对象。

+0

在嵌套ObjectId时要小心。 flat会将这些变成'something._bsontype':'ObjectID','something.id':' – tdecs 2016-11-24 09:58:51