Javascript Mongoose 查找/更新子文档
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/26156687/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Mongoose find/update subdocument
提问by Darko Romanov
I have the following schemas for the document Folder:
我有文档文件夹的以下架构:
var permissionSchema = new Schema({
role: { type: String },
create_folders: { type: Boolean },
create_contents: { type: Boolean }
});
var folderSchema = new Schema({
name: { type: string },
permissions: [ permissionSchema ]
});
So, for each Page I can have many permissions. In my CMS there's a panel where I list all the folders and their permissions. The admin can edit a single permission and save it.
因此,对于每个页面,我可以拥有许多权限。在我的 CMS 中有一个面板,我在其中列出了所有文件夹及其权限。管理员可以编辑单个权限并保存。
I could easily save the whole Folderdocument with its permissions array, where only one permission was modified. But I don't want to save all the document (the real schema has much more fields) so I did this:
我可以轻松地保存整个文件夹文档及其权限数组,其中只修改了一个权限。但我不想保存所有文档(真正的模式有更多的字段)所以我这样做了:
savePermission: function (folderId, permission, callback) {
Folder.findOne({ _id: folderId }, function (err, data) {
var perm = _.findWhere(data.permissions, { _id: permission._id });
_.extend(perm, permission);
data.markModified("permissions");
data.save(callback);
});
}
but the problem is that permis always undefined! I tried to "statically" fetch the permission in this way:
但问题是烫发总是未定义的!我试图以这种方式“静态”获取权限:
var perm = data.permissions[0];
and it works great, so the problem is that Underscore library is not able to query the permissions array. So I guess that there's a better (and workgin) way to get the subdocument of a fetched document.
并且效果很好,所以问题是 Underscore 库无法查询权限数组。所以我想有一种更好的(和工作的)方法来获取所获取文档的子文档。
Any idea?
任何的想法?
P.S.: I solved checking each item in the data.permission array using a "for" loop and checking data.permissions[i]._id == permission._id but I'd like a smarter solution, I know there's one!
PS:我解决了使用“for”循环检查 data.permission 数组中的每个项目并检查 data.permissions[i]._id == permission._id 但我想要一个更聪明的解决方案,我知道有一个!
回答by Neil Lunn
So as you note, the default in mongoose is that when you "embed" data in an array like this you get an _idvalue for each array entry as part of it's own sub-document properties. You can actually use this value in order to determine the index of the item which you intend to update. The MongoDB way of doing this is the positional $operator variable, which holds the "matched" position in the array:
因此,正如您所注意到的,mongoose 中的默认设置是,当您将数据“嵌入”到这样的数组中时,您会_id为每个数组条目获取一个值,作为其自己的子文档属性的一部分。您实际上可以使用此值来确定您打算更新的项目的索引。MongoDB 这样做的方法是位置$运算符变量,它保存数组中的“匹配”位置:
Folder.findOneAndUpdate(
{ "_id": folderId, "permissions._id": permission._id },
{
"$set": {
"permissions.$": permission
}
},
function(err,doc) {
}
);
That .findOneAndUpdate()method will return the modified document or otherwise you can just use .update()as a method if you don't need the document returned. The main parts are "matching" the element of the array to update and "identifying" that match with the positional $as mentioned earlier.
该.findOneAndUpdate()方法将返回修改后的文档,否则.update()如果您不需要返回的文档,您可以将其用作方法。主要部分是“匹配”要更新的数组元素和“识别”与前面提到的位置$匹配的元素。
Then of course you are using the $setoperator so that onlythe elements you specify are actually sent "over the wire" to the server. You can take this further with "dot notation"and just specify the elements you actually want to update. As in:
那么当然您正在使用$set运算符,以便只有您指定的元素实际上“通过线路”发送到服务器。您可以使用“点表示法”更进一步,只需指定您实际想要更新的元素。如:
Folder.findOneAndUpdate(
{ "_id": folderId, "permissions._id": permission._id },
{
"$set": {
"permissions.$.role": permission.role
}
},
function(err,doc) {
}
);
So this is the flexibility that MongoDB provides, where you can be very "targeted" in how you actually update a document.
这就是 MongoDB 提供的灵活性,您可以非常“有针对性”地实际更新文档。
What this does do however is "bypass" any logic you might have built into your "mongoose" schema, such as "validation" or other "pre-save hooks". That is because the "optimal" way is a MongoDB "feature" and how it is designed. Mongoose itself tries to be a "convenience" wrapper over this logic. But if you are prepared to take some control yourself, then the updates can be made in the most optimal way.
然而,这样做的作用是“绕过”您可能已内置到“猫鼬”模式中的任何逻辑,例如“验证”或其他“预保存挂钩”。那是因为“最佳”方式是 MongoDB 的“功能”及其设计方式。Mongoose 本身试图成为这个逻辑的“方便”包装器。但是,如果您准备自己进行一些控制,那么可以以最佳方式进行更新。
So where possible to do so, keep your data "embedded" and don't use referenced models. It allows the atomic update of both "parent" and "child" items in simple updates where you don't need to worry about concurrency. Probably is one of the reasons you should have selected MongoDB in the first place.
因此,在可能的情况下,请保持数据“嵌入”并且不要使用引用模型。它允许在不需要担心并发性的简单更新中对“父”和“子”项进行原子更新。可能是您应该首先选择 MongoDB 的原因之一。
回答by Arian Acosta
In order to validate subdocuments when updating in Mongoose, you have to 'load' it as a Schema object, and then Mongoose will automatically trigger validation and hooks.
为了在 Mongoose 中更新时验证子文档,您必须将其作为 Schema 对象“加载”,然后 Mongoose 将自动触发验证和钩子。
const userSchema = new mongoose.Schema({
// ...
addresses: [addressSchema],
});
If you have an array of subdocuments, you can fetch the desired one with the id()method provided by Mongoose. Then you can update its fields individually, or if you want to update multiple fields at once then use the set()method.
如果您有一组子文档,则可以使用id()Mongoose 提供的方法获取所需的子文档。然后您可以单独更新其字段,或者如果您想一次更新多个字段,则使用该set()方法。
User.findById(userId)
.then((user) => {
const address = user.addresses.id(addressId); // returns a matching subdocument
address.set(req.body); // updates the address while keeping its schema
// address.zipCode = req.body.zipCode; // individual fields can be set directly
return user.save(); // saves document with subdocuments and triggers validation
})
.then((user) => {
res.send({ user });
})
.catch(e => res.status(400).send(e));
Note that you don't really need the userIdto find the User document, you can get it by searching for the one that has an address subdocument that matches addressIdas follows:
请注意,您实际上并不需要userId找到 User 文档,您可以通过搜索具有匹配addressId如下地址子文档的文档来获取它:
User.findOne({
'addresses._id': addressId,
})
// .then() ... the same as the example above
Remember that in MongoDB the subdocument is saved onlywhen the parent document is saved.
请记住,在 MongoDB 中,只有在保存父文档时才会保存子文档。
Read more on the topic on the official documentation.
回答by RaphDG
If you don't want separate collection, just embed the permissionSchema into the folderSchema.
如果您不想要单独的集合,只需将permissionSchema 嵌入到folderSchema 中。
var folderSchema = new Schema({
name: { type: string },
permissions: [ {
role: { type: String },
create_folders: { type: Boolean },
create_contents: { type: Boolean }
} ]
});
If you need separate collections, this is the best approach:
如果您需要单独的集合,这是最好的方法:
You could have a Permission model:
你可以有一个权限模型:
var mongoose = require('mongoose');
var PermissionSchema = new Schema({
role: { type: String },
create_folders: { type: Boolean },
create_contents: { type: Boolean }
});
module.exports = mongoose.model('Permission', PermissionSchema);
And a Folder model with a reference to the permission document. You can reference another schema like this:
以及一个引用许可文件的文件夹模型。您可以像这样引用另一个架构:
var mongoose = require('mongoose');
var FolderSchema = new Schema({
name: { type: string },
permissions: [ { type: mongoose.Schema.Types.ObjectId, ref: 'Permission' } ]
});
module.exports = mongoose.model('Folder', FolderSchema);
And then call Folder.findOne().populate('permissions')to ask mongoose to populate the field permissions.
然后调用Folder.findOne().populate('permissions')mongoose 填充字段权限。
Now, the following:
现在,以下内容:
savePermission: function (folderId, permission, callback) {
Folder.findOne({ _id: folderId }).populate('permissions').exec(function (err, data) {
var perm = _.findWhere(data.permissions, { _id: permission._id });
_.extend(perm, permission);
data.markModified("permissions");
data.save(callback);
});
}
The permfield will not be undefined (if the permission._id is actually in the permissions array), since it's been populated by Mongoose.
该perm字段不会是未定义的(如果 permission._id 实际上在权限数组中),因为它已由 Mongoose 填充。

