【问题标题】:Mongoose find/update subdocumentMongoose 查找/更新子文档
【发布时间】:2014-11-27 04:45:12
【问题描述】:

我对文档文件夹有以下架构:

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

但问题是 perm 总是 undefined!我尝试以这种方式“静态”获取权限:

var perm = data.permissions[0];

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

有什么想法吗?

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

【问题讨论】:

    标签: javascript node.js mongodb mongoose mongodb-query


    【解决方案1】:

    试试吧

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

    【讨论】:

      【解决方案2】:

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

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

      如果您有一组子文档,您可以使用 Mongoose 提供的 id() 方法获取所需的一个。然后您可以单独更新其字段,或者如果您想一次更新多个字段,请使用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 来查找用户文档,您可以通过搜索具有与addressId 匹配的地址子文档的那个来获取它,如下所示:

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

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

      official documentation 上阅读有关该主题的更多信息。

      【讨论】:

      • 这对我也有帮助!我会将此标记为正确的解决方案?
      • 这真的很有帮助。找了4个小时,终于找到了这个答案。
      • 我们是否得到所有地址收集 throw User.findById(userId) 然后尝试使用 user.address.id(addressId) 过滤它们?恐怕如果地址子文档数组很大,我们加载很多不必要的项目只是为了更新一个。如果我错了,请纠正。
      【解决方案3】:

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

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

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

      那么您当然使用$set 运算符,以便您指定的元素实际上“通过网络”发送到服务器。您可以使用"dot notation" 进一步了解这一点,只需指定您实际要更新的元素。如:

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

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

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

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

      【讨论】:

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

      如果您不想要单独的集合,只需将权限架构嵌入到文件夹架构中即可。

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

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

      你可以有一个 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);
      

      和一个文件夹模型与权限文档的引用。 您可以像这样引用另一个架构:

      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 填充的。

      【讨论】:

      • 但如果我这样做了,我应该单独收集权限吗?
      • 是的,这就是猫鼬会如何看待permissionSchema。每个模式都映射到一个 mongodb 集合。
      • 嗯.. 但我不确定我是否想要一个单独的集合。它会改变什么吗?还是只是合乎逻辑的事情?
      • 我将尝试在我的回答中详细说明这两种方法。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-11
      • 2017-03-17
      • 2021-12-23
      • 2015-07-12
      • 2012-10-27
      相关资源
      最近更新 更多