【问题标题】:Mongoose: deleteOne middleware for cascading delete not workingMongoose:用于级联删除的 deleteOne 中间件不起作用
【发布时间】:2020-03-27 13:43:42
【问题描述】:

在 Mongoose 5.7.13 中不推荐使用 remove,我想改用 deleteOne。我需要获取已删除文档的 id,以便我可以在级联中删除其他集合中的更多相关文档。我认为 pre 中间件钩子上下文中的“this”是指已删除的文档,但它只是一个空对象。是否有一个规范的工作示例?我目前仍在使用 5.7.12 - 这会有所不同吗?

这是我目前正在使用的代码。问题是我一开始就无法获取 projectId,因为引用完全为空。在 post 而不是 pre 上执行此操作,或者切换选项以在查询而不是文档上运行都会产生相同的结果。

ProjectSchema.pre("deleteOne", {document:true}, (next) => {
  const projectId = this._id;
  ListModel.find({parentProject:projectId}, (err, lists) => {
    if(err){
      console.log("error cascading project delete to lists", {err});
    }
    lists.map(list => {
      ListModel.deleteOne({_id:list._id}, (err, result) => {
        if(err) {
          console.log("error on project delete cascade", {err});
        }
      });
    });
  });
});

【问题讨论】:

    标签: mongodb mongoose middleware mongoose-deleteone


    【解决方案1】:

    这取决于您是在文档还是模型上调用 deleteOne。后者只是没有将其绑定到的文档。

    前者如您所愿为您提供文档:

    const project = await ProjectModel.findOne();
    project.deleteOne();
    

    后者为您提供查询。查询中没有_id,但例如有this.op,在此中间件中为“deleteOne”:

    await ProjectModel.deleteOne();
    

    在这种情况下获取文档 id 的唯一方法是确保它在查询中提供:

    await ProjectModel.deleteOne({_id: "alex"});
    

    那么就可以从过滤器的中间件中获取:

    const projectId = this.getFilter()["_id"]
    

    您可以在中间件的第二个参数中指定query: false,以确保在模型上调用deleteOne时不会调用它。所以你能做到的最好的:

    ProjectSchema.pre("deleteOne", {document:true, query: false}, (next) => {
        const projectId = this._id;
        ....
    });
    
    ProjectSchema.pre("deleteOne", {document:false, query: true}, (next) => {
        const projectId = this.getFilter()["_id"];
        if (typeof projectId === "undefined") {
            // no way to make cascade deletion since there is no _id
            // in the delete query
            // I would throw an exception, but it's up to you how to deal with it
            // to ensure data integrity
        }
    });
    

    请查看 v5.7.12 上的相应测试:https://github.com/Automattic/mongoose/blob/5.7.12/test/model.middleware.test.js#L436

    【讨论】:

    • 我通过模型和文档都试过了,没有区别。在这两种情况下,“this”都只是一个空对象。你让它工作了吗?
    • 当然。我添加了测试链接。 this 绝不应该是空对象。它是文档或查询。请添加重现问题的代码。仅中间件代码是不够的。正如我所解释的,删除文档的方式很重要。
    【解决方案2】:

    mongoose docs 中它说“Model.deleteOne() 不会触发 pre('remove') 或 post('remove') 挂钩。”

    如果您可以使用findByIdAndDelete 重构删除操作,则有解决方案,它会触发findOneAndDelete 中间件,

    所以我们可以将这个中间件添加到项目架构中。

    项目模型:

    const mongoose = require("mongoose");
    const ProjectChild = require("./projectChild");
    
    const ProjectSchema = new mongoose.Schema({
      name: String
    });
    
    ProjectSchema.post("findOneAndDelete", async function(doc) {
      console.log(doc);
    
      if (doc) {
        const deleteResult = await ProjectChild.deleteMany({
          parentProject: doc._id
        });
    
        console.log("Child delete result: ", deleteResult);
      }
    });
    
    module.exports = mongoose.model("Project", ProjectSchema);
    

    ProjectChild 模型:

    const mongoose = require("mongoose");
    
    const projectChildSchema = new mongoose.Schema({
      name: String,
      parentProject: {
        type: mongoose.Schema.Types.ObjectId,
        ref: "Project"
      }
    });
    
    module.exports = mongoose.model("ProjectChild", projectChildSchema);
    

    我创建了一个这样的项目:

    {
        "_id": "5dea699cb10c442260245abf",
        "name": "Project 1",
        "__v": 0
    }
    

    并为此项目创建了 2 个子项目:

    孩子 1

    {
        "_id": "5dea69c7b10c442260245ac0",
        "name": "Child 1 (project 1)",
        "parentProject": "5dea699cb10c442260245abf",
        "__v": 0
    }
    

    孩子 2

    {
        "_id": "5dea69e8b10c442260245ac1",
        "name": "Child 2 (project 1)",
        "parentProject": "5dea699cb10c442260245abf",
        "__v": 0
    }
    

    我创建了一个示例路由来通过它的 id 删除一个项目,如下所示:

    router.delete("/project/:id", async (req, res) => {
      const result = await Project.findByIdAndDelete(req.params.id);
    
      res.send(result);
    });
    

    当我向该路由发送 DELETE 请求时,我们会在控制台中看到以下信息:

    console.log(doc);

    { _id: 5dea699cb10c442260245abf, name: 'Project 1', __v: 0 }
    

    console.log("Child delete result: ", deleteResult);

    Child delete result:  { n: 2, ok: 1, deletedCount: 2 }
    

    所以我们可以在删除项目时删除项目的 2 个子项。

    作为替代你也可以使用findOneAndRemove,它会触发findOneAndRemove post中间件。

    所以在 ProjectSchema 中我们像这样替换 post 中间件:

    ProjectSchema.post("findOneAndRemove", async function(doc) {
      console.log(doc);
    
      if (doc) {
        const deleteResult = await ProjectChild.deleteMany({
          parentProject: doc._id
        });
    
        console.log("Child delete result: ", deleteResult);
      }
    });
    

    当我们使用 findOneAndRemove 操作时,结果将与第一种选择相同:

    const result = await Project.findOneAndRemove({ _id: req.params.id });
    

    【讨论】:

    • 那么这是否意味着还没有完全支持 deleteOne 的使用?
    • @notnot 实际上我没有尝试过。但是使用 findByIdAndDelete 它可以工作。明天我会检查。
    • @notnot 在文档中说 deleteOne 不会触发 pre('remove') 或 post('remove') 挂钩。 mongoosejs.com/docs/api/model.html#model_Model.deleteOne
    • 这里的问题是钩子正在被触发,它只是不包括我可以找到的对已删除文档的任何引用。
    • @notnot 我们可以确定文档在中间件中是否不为空。我会更新答案。
    猜你喜欢
    • 2020-08-05
    • 2013-06-22
    • 2013-03-03
    • 2015-07-27
    • 2012-11-18
    • 2011-06-11
    • 2018-06-04
    相关资源
    最近更新 更多