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.last
和 profile.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.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
对象。
【讨论】:
在嵌套 ObjectId 时要小心。 flat 会将它们变成 'something._bsontype': 'ObjectID', 'something.id':我想你正在寻找 $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:嵌套属性的原子更新?的主要内容,如果未能解决你的问题,请参考以下文章