You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Mongoose中post deleteMany/deleteOne中间件未触发问题求助

解决Mongoose中Model.deleteMany()无法触发post中间件的问题

问题现象

批量删除文档时,调用document.deleteOne()能正常触发post中间件,但调用Model.deleteMany()(如Lecture.deleteMany(...)和Chapter.deleteMany(...))时中间件完全不触发,不过文档本身确实被成功删除。

相关代码

路由处理代码

export const DELETE = catchAsync(async function (req, { params }) {
  // Check if course exists
  const course = await Course.findById(params.id).populate({
    path: "chapters",
    select: { id: 1 },
  });
  if (!course)
    return new AppError("No courses found with the provided id", 404);

  const { chapters } = course;
  const chapterIds = chapters.map((chapter) => chapter.id);

  // Delete course's lectures
  await Lecture.deleteMany({ chapter: { $in: chapterIds } });

  // Delete course's chapters
  await Chapter.deleteMany({ _id: { $in: chapterIds } }); // Specifically this one

  // Delete course
  await course.deleteOne();
});

Chapter模型的post中间件

chapterSchema.post(
  ["deleteOne", "deleteMany"],
  { document: true },
  async function (doc, next) {
    console.log("POST DELETE ONE CHAPTER");

    const course = await Course.findById(this.course);

    // Find and remove chapter from course
    const index = course.chapters.findIndex((ch) => ch._id === doc._id);
    course.chapters.splice(index, 1);
    await course.save();

    next();
  }
);

原因分析

Mongoose的中间件分为文档中间件和查询中间件:

  • 你设置了{ document: true },这会让中间件仅对文档实例的方法生效(比如doc.deleteOne())。
  • Model.deleteMany()属于查询级别的批量操作,不会触发文档中间件;同时Mongoose本身不支持deleteMany的文档中间件,因为批量操作没有单个文档实例传递给中间件。

修复方案

方案一:改用查询中间件适配deleteMany

将deleteMany对应的中间件改为查询中间件({ query: true }),同时调整逻辑以适配查询操作:

// 单独处理deleteMany的查询中间件
chapterSchema.post("deleteMany", { query: true }, async function (result, next) {
  // 获取本次删除的所有章节数据(包含关联的course字段)
  const deletedChapters = await Chapter.find(this.getQuery()).select('course');
  
  // 遍历更新每个关联的Course
  for (const chapter of deletedChapters) {
    const course = await Course.findById(chapter.course);
    if (course) {
      // 过滤掉已删除的章节ID
      course.chapters = course.chapters.filter(ch => ch._id.toString() !== chapter._id.toString());
      await course.save();
    }
  }

  next();
});

// 保留原有的deleteOne文档中间件
chapterSchema.post("deleteOne", { document: true }, async function (doc, next) {
  console.log("POST DELETE ONE CHAPTER");

  const course = await Course.findById(this.course);
  if (course) {
    const index = course.chapters.findIndex((ch) => ch._id.toString() === doc._id.toString());
    if (index !== -1) {
      course.chapters.splice(index, 1);
      await course.save();
    }
  }

  next();
});

方案二:遍历文档逐个调用deleteOne(小数据量场景)

如果批量删除的数据量不大,可以先查询出所有目标文档,再逐个调用deleteOne(),这样就能复用原有文档中间件:

修改路由中的删除逻辑:

// 替换原Chapter.deleteMany逻辑
const chaptersToDelete = await Chapter.find({ _id: { $in: chapterIds } });
for (const chapter of chaptersToDelete) {
  await chapter.deleteOne();
}

// 同理修改Lecture的删除逻辑
const lecturesToDelete = await Lecture.find({ chapter: { $in: chapterIds } });
for (const lecture of lecturesToDelete) {
  await lecture.deleteOne();
}

方案对比

  • 方案一:适合大数据量场景,性能更优,但需要单独编写查询中间件逻辑。
  • 方案二:逻辑简单,无需修改中间件,但数据量大时会产生多次数据库请求,性能较差。

内容的提问来源于stack exchange,提问作者Tidris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 17:37:13