【问题标题】:Automatically remove referencing objects on deletion in MongoDB在 MongoDB 中删除时自动删除引用对象
【发布时间】:2012-08-07 21:40:19
【问题描述】:

假设我有这样的架构:

var Person = new Schema({
    name: String
});

var Assignment = new Schema({
    name: String,
    person: ObjectID
});

如果我删除了一个人,仍然会留下引用不存在的人的孤立分配,这会在数据库中造成多余的混乱。

有没有一种简单的方法可以确保当一个人被删除时,对该人的所有相应引用也将被删除?

【问题讨论】:

    标签: node.js mongodb mongoose


    【解决方案1】:

    如果“简单”是指“内置”,那么不是。 MongoDB 毕竟不是关系型数据库。您需要实现自己的清理机制。

    【讨论】:

      【解决方案2】:

      您可以在Person 架构上添加自己的'remove' Mongoose middleware,以将该人从引用它的所有其他文档中删除。在您的中间件函数中,this 是要删除的 Person 文档。

      Person.pre('remove', function(next) {
          // Remove all the assignment docs that reference the removed person.
          this.model('Assignment').remove({ person: this._id }, next);
      });
      

      【讨论】:

      • 如果它包含更深的嵌套对象 ID 数组怎么办?喜欢data:{persons:[{ObjectID}]}
      • @vhflat 可能最好发布一个关于所有细节的新问题。
      • @JohnnyHK,但是并发呢?如果在删除人员时正在创建分配怎么办?我试图在回答这个问题stackoverflow.com/q/42521550 时解决这个问题,但我觉得一定有更好的方法!可以看看吗?
      • @cute_ptr 是的,您仍然必须确保您的应用程序可以处理孤立的分配情况,然后定期删除孤立的对象以保持整洁。这就是您使用非关系数据库所采取的方式。
      • @JohnnyHK,您认为使用post remove hooks 而不是pre remove hooks 来处理这个问题(正如我对该问题的回答所建议的那样)?
      【解决方案3】:

      您可以使用软删除。不要从 Person Collection 中删除人员,而是使用 isDelete 布尔标志设置为 true。

      【讨论】:

      • 这是一个很好的做法,但是它复杂化了很多(特别是在这种情况下),因为在您所做的每个请求中,您都必须过滤 isDeleted 字段
      【解决方案4】:

      remove() 方法已弃用。

      因此,在 Mongoose 中间件中使用“删除”可能不再是最佳实践。

      Mongoose has created updates to provide hooks for deleteMany() and deleteOne()。 你可以用那些代替。

      Person.pre('deleteMany', function(next) {
          var person = this;
          person.model('Assignment').deleteOne({ person: person._id }, next);
      });
      

      【讨论】:

      • 谢谢,我有几个问题:如果 remove 已被弃用,您为什么要使用它来删除作业?另外,如果只删除一个人,会调用deleteMany 钩子吗?
      • 哈哈,我用remove是因为我是个懒惰的傻瓜! :) 复制粘贴约翰尼的版本,并调整我在自己的代码中更改的部分。感谢@PeterOlson 提出这个问题!鉴于您的经验,您可能已经知道了!我会尝试回答只是为了学习!鉴于钩子似乎与这些函数相关联,我推断deleteMany 总是在运行deleteMany 时被调用,而不是在通过任何其他函数(例如deleteOne)删除一个人时调用。谢谢你教育我! :)
      • 我尝试通过查看源代码来确定,但代码库很大,所以我无法仔细阅读它。不管怎样,谢谢你,你启发了我去查看源代码。我会看看我是否可以稍后对此进行测试,因为这将是另一种确定的方法。
      • 说实话我也不知道答案(我很久没用猫鼬了),我只是好奇。无论如何,我要 +1 以保持信息最新!
      • @PeterOlson,我明白了!谢谢!
      【解决方案5】:

      如果有人在寻找 pre hook 但对于 deleteOnedeleteMany 功能,这是一个适合我的解决方案:

      const mongoose = require('mongoose');
      ... 
      
      const PersonSchema = new mongoose.Schema({
        name: {type: String},
        assignments: [{type: mongoose.Schema.Types.ObjectId, ref: 'Assignment'}]
      });
      
      mongoose.model('Person', PersonSchema);
      
      .... 
      
      const AssignmentSchema = new mongoose.Schema({
        name: {type: String},
        person: {type: mongoose.Schema.Types.ObjectId, ref: 'Person'}
      });
      
      mongoose.model('Assignment', AssignmentSchema)
      ...
      
      PersonSchema.pre('deleteOne', function (next) {
        const personId = this.getQuery()["_id"];
        mongoose.model("Assignment").deleteMany({'person': personId}, function (err, result) {
          if (err) {
            console.log(`[error] ${err}`);
            next(err);
          } else {
            console.log('success');
            next();
          }
        });
      });
      

      在服务的某处调用deleteOne函数:

      try {
        const deleted = await Person.deleteOne({_id: id});
      } catch(e) {
        console.error(`[error] ${e}`);
        throw Error('Error occurred while deleting Person');
      }
      

      【讨论】:

      • 你能解释一下,为什么需要使用this.getQuery()["_id"]而不是this._id
      • stackoverflow.com/a/60018284/9399066 这在删除的情况下也对我有用(并且更精简)
      【解决方案6】:

      即使删除了引用的个人文档,您也可以保留文档原样。 Mongodb 清除指向不存在文档的引用,这不会在删除引用的文档后立即发生。相反,当您对文档执行操作时,例如更新。而且,即使在引用被清除之前查询数据库,返回也是空的,而不是空值。

      第二个选项是使用$unset 运算符,如下所示。

      { $unset: { person: "<person id>"} }
      

      注意在查询中使用 person id 来表示引用的值。

      【讨论】:

        【解决方案7】:

        您可以像这样简单地调用需要删除的模型并删除该文档:

        PS:此答案并非特定于问题架构。

        const Profiles = require('./profile');
        
        userModal.pre('deleteOne', function (next) {
          const userId = this.getQuery()['_id'];
          try {
            Profiles.deleteOne({ user: userId }, next);
          } catch (err) {
            next(err);
          }
        });
        

        //在用户删除路由中

        exports.deleteParticularUser = async (req, res, next) => {
          try {
            await User.deleteOne({
              _id: req.params.id,
            });
        
            return res.status(200).json('user deleted');
          } catch (error) {
            console.log(`error`, error);
            return next(error);
          }
        };
        

        【讨论】:

        • 欢迎来到 Stack Overflow!如果您解释了它的工作原理/原因和/或为什么此解决方案比已经存在一段时间的现有答案更好,您的答案可能对其他人更有帮助:)
        猜你喜欢
        • 2021-02-19
        • 1970-01-01
        • 2019-11-25
        • 1970-01-01
        • 2016-10-28
        • 2021-11-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多