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

Posted

技术标签:

【中文标题】Mongoose.js:嵌套属性的原子更新?【英文标题】:Mongoose.js: Atomic update of nested properties? 【发布时间】:2013-04-30 08:02:37 【问题描述】:

使用 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"
        
    

所以它基本上用req.body.profile 覆盖了所有profile,我想这是有道理的。通过在更新查询中指定我的字段而不是req.body,是否有任何解决方法而不必更明确?

【问题讨论】:

考虑接受 Aichholzer 的回答,因为它为问题提供了真正的解决方案。 【参考方案1】:

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

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

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

【讨论】:

对,但我希望我可以传入req.body,这样我就不必指定所有可能的字段。所以我首先做了一个findById(),从找到的文档中删除__v_id,使用_.deepExtend()合并新的更新属性,然后将我的科学怪人对象传递给猫鼬的update。跨度> @aaronheckmann,有没有告诉 mongoose 或 mongo “这是我的最新对象,只需更新它”? @k00k - 这种方法是原子的吗?它不会再次编写整个文档吗?为什么一定要去掉 __v 和 _id? @DouglasFerguson 自从这个特定的问题和版本的 Mongoose 以来已经有很长时间了,所以我会尽量记住,但默认情况下,Mongoose 更新是原子的。请参阅 aaronheckman 关于 $set 的回答。至于 __v 和 __id,不确定我为什么要这样做,现在似乎不需要,但我确信我做 deepExtend 的方式是有原因的。抱歉,我想不起来了。 如何编辑以显示使用扁平库的示例,例如 github.com/hughsk/flat - 这样就无需手动“翻译”嵌套路径?【参考方案2】:

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

【讨论】:

在嵌套 ObjectId 时要小心。 flat 会将它们变成 'something._bsontype': 'ObjectID', 'something.id': '【参考方案3】:

我想你正在寻找 $set

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

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

试试看

【讨论】:

我认为 Mongoose 默认是“安全的”,所以 update 真的是 $set,我错了吗? 补充一下,我刚试了一下,还是不行。嵌套属性似乎有问题。澄清一下,只有同一级别的嵌套属性(兄弟姐妹)才会被吹走。 您正在寻找具有特定字段名称的 $set - “name.first”。这与安全写入与不安全写入无关,只是告诉它更新整个文档还是单个字段。 @all,我一直在努力寻找答案,希望有人知道。无论如何告诉猫鼬或猫鼬“这是我的最新对象,只需更新它”?【参考方案4】:

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

【讨论】:

以上是关于Mongoose.js:嵌套属性的原子更新?的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用 Mongoose JS 删除嵌入式子文档数组的元素不起作用?

Mongoose.js:如何实现创建或更新?

Mongoose.js:如何实现创建或更新?

Java中的原子操作类

从 Mongoose 模型中获取模式属性

2018第22周回顾