Mongoose 查找/更新子文档

IT技术 javascript node.js mongodb mongoose mongodb-query
2021-02-12 05:53:06

我有文档文件的以下架构

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

var folderSchema = new Schema({
    name: { type: string },
    permissions: [ permissionSchema ]
});

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

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

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);
    });
}

但问题是烫发总是未定义的我试图以这种方式“静态”获取权限:

var perm = data.permissions[0];

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

任何的想法?

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

4个回答

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

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

    }
);

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

然后当然您正在使用$set运算符,以便只有您指定的元素实际上“通过线路”发送到服务器。您可以使用“点表示法”更进一步,只需指定您实际想要更新的元素。如:

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

    }
);

这就是 MongoDB 提供的灵活性,您可以非常“有针对性”地实际更新文档。

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

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

@DarkoRomanov 你有没有找到一种方法来更新来自用户界面的一些动态字段
2021-03-22 05:53:06
当我使用它来更新整个子文档(而不仅仅是一个字段)时,这似乎正在删除_idMongoDB 为它提供的子文档属性,因为它是数组的一部分。这可能是因为我_id从邮递员请求正文中删除了该属性,所以也许我只需要确保它在请求正文中?MongoDB 让我完全覆盖整个对象,包括删除_id属性,这似乎很奇怪它不会在父对象上这样做(实际上是 PATCH 请求而不是 PUT)。任何解决方法?
2021-04-02 05:53:06
诶!很棒的代码谢谢!但是,如果我必须查询文档、更改字段并再次保存它,这对我来说没问题,我不想保存来自用户界面的整个对象。但是能够查询一个文档并只保存一个字段(甚至只有一个子文档的一个字段)真的很强大!我真的必须深入研究 $ 运算符。
2021-04-10 05:53:06

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

const userSchema = new mongoose.Schema({
  // ...
  addresses: [addressSchema],
});

如果您有一组子文档,则可以使用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));

请注意,您实际上并不需要userId找到 User 文档,您可以通过搜索具有匹配addressId如下地址子文档的文档来获取它

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

请记住,在 MongoDB 中,只有保存父文档时才会保存子文档。

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

这真的很有帮助。经过4个小时的搜索,我终于找到了这个答案。
2021-03-24 05:53:06
这也帮助了我!我会将此标记为正确的解决方案🙌
2021-04-07 05:53:06

如果您不想要单独的集合,只需将permissionSchema 嵌入到folderSchema 中即可。

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

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

你可以有一个权限模型:

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);

以及一个引用许可文件的文件夹模型。您可以像这样引用另一个架构:

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);

然后调用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);
    });
}

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

我将尝试在我的回答中详细介绍这两种方法。
2021-03-21 05:53:06
但如果我这样做了,我应该为权限制作一个单独的集合吗?
2021-03-27 05:53:06
嗯.. 但我不确定我想要一个单独的集合。它会改变什么吗?或者这只是一个逻辑问题?
2021-03-30 05:53:06
是的,mongoose就是这样考虑permissionSchema. 每个模式映射到一个 mongodb 集合。
2021-04-10 05:53:06

你试一试

let doc = await Folder.findOneAndUpdate(
    { "_id": folderId, "permissions._id": permission._id },
    { "permissions.$": permission},
);