【问题标题】:Cascade style delete in MongooseMongoose中的级联样式删除
【发布时间】:2012-12-30 04:30:39
【问题描述】:

有没有办法在 Mongoose 中删除父级的所有子级,类似于使用 MySQL 的外键?

例如,在 MySQL 中,我会分配一个外键并将其设置为删除时级联。因此,如果我要删除客户端,所有应用程序和关联用户也会被删除。

从顶层:

  1. 删除客户端
  2. 删除抽奖活动
  3. 删除提交内容

抽奖和提交都有一个 client_id 字段。 Submissions 有一个用于sweepstakes_id 和client_id 的字段。

现在,我正在使用以下代码,我觉得必须有更好的方法。

Client.findById(req.params.client_id, function(err, client) {

    if (err)
        return next(new restify.InternalError(err));
    else if (!client)
        return next(new restify.ResourceNotFoundError('The resource you requested could not be found.'));

    // find and remove all associated sweepstakes
    Sweepstakes.find({client_id: client._id}).remove();

    // find and remove all submissions
    Submission.find({client_id: client._id}).remove();

    client.remove();

    res.send({id: req.params.client_id});

});

【问题讨论】:

    标签: mongodb mongoose


    【解决方案1】:

    这是 Mongoose 的 'remove' middleware 的主要用例之一。

    clientSchema.pre('remove', function(next) {
        // 'this' is the client being removed. Provide callbacks here if you want
        // to be notified of the calls' result.
        Sweepstakes.remove({client_id: this._id}).exec();
        Submission.remove({client_id: this._id}).exec();
        next();
    });
    

    这样,当您调用client.remove() 时,会自动调用此中间件来清理依赖关系。

    【讨论】:

    • 不,在通过mongoose.modeldb.model 调用创建模型之前,您会在用于定义Client 模型的Schema 对象上调用它。
    • 当我将它移到我的架构定义文件(模型)中时效果很好。不过,我必须将 .exec() 添加到删除调用的末尾。没有坏处,对吧?
    • 您是对的,当您不提供回调时,您需要使用remove 执行此操作。我更新了答案。
    • @JohnnyHK:只有当我们调用 client.remove() 时才会调用这个方法。在我的应用程序中,我使用以下语句将其删除:Client.findByIdAndRemove(client_id,function(err){}) 这个 pre hook 不会被调用吗?
    • @Inquisitive 对,在这种情况下不会调用钩子。仅当您在文档实例上调用 remove 时才会调用它。
    【解决方案2】:

    如果您的引用以其他方式存储,例如,client 有一个 submission_ids 数组,那么以与接受的答案类似的方式,您可以在 submissionSchema 上定义以下内容:

    submissionSchema.pre('remove', function(next) {
        Client.update(
            { submission_ids : this._id}, 
            { $pull: { submission_ids: this._id } },
            { multi: true })  //if reference exists in multiple documents 
        .exec();
        next();
    });
    

    这将从submission.remove()上的clients'引用数组中删除提交的 id。

    【讨论】:

    • 请您举一个使用 OneToMany 的例子吗?如果此代码删除所有子级并更新引用和父级,我是不对的。
    • 不确定我是否理解您的评论。但让我用例子来解释我的答案。假设有 3 个客户端文档:C1、C2、C3。并且有许多提交文件,其中一个是S273。现在 S273 的 id 存在于 C1 和 C3 的 submission_ids 中(OneToMany,即客户端有很多提交)。我上面的回答解决了您要删除 S273 的情况。当您这样做时,S273 的 id 将自动从 C1 和 C3 的submission_ids 中删除。
    • 嗯.. 我知道submissionSchemaClient schema 上引用的子集合对吗?
    • 是的。这是“hasMany”的方式。客户端拥有许多提交 ID 的数组。另一种是“belongsTo”方式,每个提交文档都有一个属于它的客户端id。
    • @FrancisRodrigues 因为它与 NoSql 而非 Mongoose 相关(它只是 mongodb 的一个节点库/包装器,以帮助简化开发)。我不确定 mongodb 有一个部分来解释这一点,但 couchbase(另一个 NoSql db)有一个超级信息文档详细解释它。 developer.couchbase.com/documentation/server/4.6/data-modeling/…, developer.couchbase.com/documentation/server/3.x/developer/…, developer.couchbase.com/documentation/server/3.x/developer/…
    【解决方案3】:

    这是我找到的另一种方式

    submissionSchema.pre('remove', function(next) {
        this.model('Client').remove({ submission_ids: this._id }, next);
        next();
    });
    

    【讨论】:

    • 删除回调中的next 是因为您无论如何都会在它下面的行中调用next 对吗?
    【解决方案4】:

    我注意到这里的所有答案都有一个pre 分配给架构,而不是post

    我的解决方案是:(使用 mongoose 6+)

    ClientSchema.post("remove", async function(res, next) { 
        await Sweepstakes.deleteMany({ client_id: this._id });
        await Submission.deleteMany({ client_id: this._id });
        next();
    });
    

    根据定义,post 在流程结束后执行pre => process => post

    现在,您可能想知道这与此处提供的其他解决方案有何不同。 如果找不到服务器错误或该客户端的 id 怎么办? 在预先,它将在删除过程开始之前删除所有sweeptakessubmissions client。因此,如果出现错误,最好在client 或主文档被删除后级联删除其他文档。

    async 和 await 在这里是可选的。但是,它对大数据很重要。这样如果删除进度仍在进行,用户就不会得到那些“将被删除”的级联文档数据。

    最后,我可能是错的,希望这对他们的代码有所帮助。

    【讨论】:

    • userSchema.post('findOneAndDelete', async id => { await Comments.deleteMany({ user: id }); }); findOneAndDelete 方法在这里也有效,似乎删除方法已被弃用
    猜你喜欢
    • 2015-09-16
    • 2015-07-27
    • 2016-01-31
    • 1970-01-01
    • 2011-10-12
    • 2020-03-27
    相关资源
    最近更新 更多