Mongoose查找/更新子文档

Dar*_*nov 44 javascript mongoose mongodb node.js mongodb-query

我有文档文件的以下模式:

var permissionSchema = new Schema({
    role: { type: String },
    create_folders: { type: Boolean },
    create_contents: { type: Boolean }
});

var folderSchema = new Schema({
    name: { type: string },
    permissions: [ permissionSchema ]
});
Run Code Online (Sandbox Code Playgroud)

因此,对于每个页面,我可以拥有许多权限.在我的CMS中有一个面板,我列出了所有文件夹及其权限.管理员可以编辑单个权限并保存.

我可以使用其权限数组轻松保存整个Folder文档,其中只修改了一个权限.但我不想保存所有文档(真正的架构有更多字段)所以我这样做:

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);
    });
}
Run Code Online (Sandbox Code Playgroud)

但问题是烫发总是未定义的!我尝试以这种方式"静态地"获取权限:

var perm = data.permissions[0];
Run Code Online (Sandbox Code Playgroud)

并且它工作得很好,所以问题是Underscore库无法查询权限数组.所以我想有一种更好的(和工作方式)方法来获取已获取文档的子文档.

任何的想法?

PS:我解决了使用"for"循环检查data.permission数组中的每个项目并检查data.permissions [i] ._ id == permission._id但我想要一个更聪明的解决方案,我知道有一个!

Nei*_*unn 95

正如您所注意到的,mongoose中的默认值是,当您将数据"嵌入"到这样的数组中时,您将获得_id每个数组条目的值,作为其自己的子文档属性的一部分.您实际上可以使用此值来确定要更新的项目的索引.MongoDB这样做的方法是位置$运算符变量,它保存数组中的"匹配"位置:

Folder.findOneAndUpdate(
    { "_id": folderId, "permissions._id": permission._id },
    { 
        "$set": {
            "permissions.$": permission
        }
    },
    function(err,doc) {

    }
);
Run Code Online (Sandbox Code Playgroud)

.findOneAndUpdate()方法将返回修改后的文档,否则,.update()如果您不需要返回文档,则可以将其用作方法.主要部分是"匹配"数组的元素以更新和"识别"与前面提到的位置$匹配.

当然,您正在使用$set运算符,以便只有您指定的元素实际上是"通过线路"发送到服务器.您可以使用"点表示法"进一步完成此操作,并指定实际要更新的元素.如:

Folder.findOneAndUpdate(
    { "_id": folderId, "permissions._id": permission._id },
    { 
        "$set": {
            "permissions.$.role": permission.role
        }
    },
    function(err,doc) {

    }
);
Run Code Online (Sandbox Code Playgroud)

因此,这是MongoDB提供的灵活性,您可以非常"有针对性"地实际更新文档.

然而,它的作用是"绕过"你可能已经构建到"mongoose"模式中的任何逻辑,例如"验证"或其他"预保存挂钩".这是因为"最佳"方式是MongoDB"功能"及其设计方式.Mongoose本身试图成为这个逻辑的"便利"包装器.但是,如果您准备自己采取一些控制措施,那么可以以最佳方式进行更新.

因此,在可能的情况下,请保持数据"嵌入"并且不要使用引用的模型.它允许在简单更新中对"父"和"子"项进行原子更新,而不需要担心并发.可能是你应该首先选择MongoDB的原因之一.

  • 当我使用它来更新整个子文档(而不仅仅是一个字段)时,这似乎删除了 MongoDB 赋予它作为数组一部分的子文档的“_id”属性。这可能是因为我从邮递员请求正文中删除了“_id”属性,所以也许我只需要确保它位于请求正文中?MongoDB 让我完全覆盖整个对象,包括删除“_id”属性,这似乎很奇怪。它不会对父对象执行此操作(实际上是 PATCH 请求而不是 PUT)。有什么解决办法吗? (2认同)

Ari*_*sta 15

为了在Mongoose中更新时验证子文档,您必须将其"加载"为Schema对象,然后Mongoose将自动触发验证和挂钩.

const userSchema = new mongoose.Schema({
  // ...
  addresses: [addressSchema],
});
Run Code Online (Sandbox Code Playgroud)

如果您有一个子文档数组,您可以使用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));
Run Code Online (Sandbox Code Playgroud)

请注意,您确实不需要userId查找用户文档,您可以通过搜索具有匹配的地址子文档的文档来获取它,addressId如下所示:

User.findOne({
  'addresses._id': addressId,
})
// .then() ... the same as the example above
Run Code Online (Sandbox Code Playgroud)

请记住,在MongoDB中,在保存父文档时才保存子文档.

阅读有关官方文档主题的更多信息.


Rap*_*hDG 6

如果您不想单独收集,只需将permissionSchema嵌入到folderSchema中.

var folderSchema = new Schema({
    name: { type: string },
    permissions: [ {
        role: { type: String },
        create_folders: { type: Boolean },
        create_contents: { type: Boolean }
    } ]
});
Run Code Online (Sandbox Code Playgroud)

如果您需要单独的集合,这是最好的方法:

你可以有一个Permission模型:

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);
Run Code Online (Sandbox Code Playgroud)

以及引用权限文档的Folder模型.您可以引用另一个这样的架构:

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);
Run Code Online (Sandbox Code Playgroud)

然后调用Folder.findOne().populate('permissions')以询问mongoose填充字段权限.

现在,以下内容:

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);
    });
}
Run Code Online (Sandbox Code Playgroud)

perm字段不会被定义(如果permission._id实际上在权限数组中),因为它已被Mongoose填充.