遍历对象数组并更新每个对象的Mongoose实现问题
问题:无法通过Mongoose批量更新文档的category字段
我需要根据两个自定义函数isLessThanOneDay(post.deadline)和isAchieved(post.milestones)的判断结果,批量更新Post集合中所有文档的category字段。尝试用Mongoose的match特性未实现需求,于是编写了查询所有文档后遍历更新的代码,但无论是使用findOneAndUpdate、update方法,还是直接修改post.category后调用save,都无法完成更新。
原代码示例
(async () => { let posts = await Post.find() posts.map(async (post) => { if (isLessThanOneDay(post.deadline)) { console.log(post.id, post._id) await Post.findOneAndUpdate(post.id, { $set: { category: 'deadline' }}) } else if (isAchieved(post.milestones)) { await Post.findOneAndUpdate(post.id, { $set: { category: 'achieved' }}) } else { await Post.findOneAndUpdate(post.id, { $set: { category: 'newGoals' }}) } }) })()
PostSchema定义
const { type } = require("express/lib/response") const mongoose = require("mongoose") const PostSchema = new mongoose.Schema({ // 关联用户 user: { type: mongoose.Schema.Types.ObjectId, ref: "user", }, title: { type: String, required: true }, desc: { type: String }, milestones: [], likes: [ { user: { type: mongoose.Schema.Types.ObjectId, ref: "user" }, fName: { type: String, required: true }, date: { type: String, required: true } } ], comments: [ { user: { type: mongoose.Schema.Types.ObjectId, ref: "user" }, text: { type: String, required: true }, fName: { type: String, }, date: { type: String, } } ], rewards: [ { user: { type: mongoose.Schema.Types.ObjectId, ref: "user" }, fName: { type: String, required: true }, date: { type: String, required: true }, reward: { type: String, required: true }, price: { type: Number, required: true } } ], date: { type: String, required: true }, shares: [ { user: { type: mongoose.Schema.Types.ObjectId, ref: "user" }, fName: { type: String, required: true }, date: { type: String, required: true } } ], deadline: { type: String, required: true }, category: { type: String, } }) module.exports = Post = mongoose.model('post', PostSchema)
问题原因与修复方案
1. 核心问题分析
map与async/await的异步陷阱:map会立即返回一个包含Promise的数组,但原代码没有等待这些Promise执行完成,导致程序提前结束,更新操作可能还未执行。findOneAndUpdate查询条件错误:直接传递post.id作为查询条件,Mongoose会将其解析为{ id: post.id },但你的Schema中没有id字段,无法匹配到目标文档,正确的查询条件应该是{ _id: post._id }。- 低效的遍历更新:逐个文档单独发起更新请求,数据库IO开销大,建议优先用批量更新操作(若业务逻辑允许)。
2. 修复后的遍历更新代码
方式一:使用findOneAndUpdate
(async () => { const posts = await Post.find(); // 用Promise.all等待所有更新操作完成 await Promise.all(posts.map(async (post) => { let category; if (isLessThanOneDay(post.deadline)) { category = 'deadline'; } else if (isAchieved(post.milestones)) { category = 'achieved'; } else { category = 'newGoals'; } // 用_id作为查询条件,确保匹配到目标文档 await Post.findOneAndUpdate( { _id: post._id }, { $set: { category } }, { new: true } // 可选,返回更新后的文档 ); })); console.log('所有文档更新完成'); })();
方式二:直接修改文档后save
(async () => { const posts = await Post.find(); await Promise.all(posts.map(async (post) => { if (isLessThanOneDay(post.deadline)) { post.category = 'deadline'; } else if (isAchieved(post.milestones)) { post.category = 'achieved'; } else { post.category = 'newGoals'; } await post.save(); })); console.log('所有文档更新完成'); })();
3. 更高效的批量更新方案(若业务逻辑可转换为MongoDB查询)
如果isLessThanOneDay和isAchieved的逻辑可以转换为MongoDB支持的查询条件(比如deadline是日期类型时,可通过时间范围判断),可以使用updateMany减少数据库请求:
(async () => { // 假设isLessThanOneDay是指deadline距离当前时间不足1天,先转换为MongoDB可识别的时间条件 const oneDayFromNow = new Date(Date.now() + 24 * 60 * 60 * 1000); // 第一步:更新临近截止日期的文档 await Post.updateMany( // 这里需要根据deadline的格式调整,若deadline是字符串需先转Date { deadline: { $lte: oneDayFromNow.toISOString() } }, { $set: { category: 'deadline' } } ); // 第二步:更新已完成里程碑的文档(需根据isAchieved的实际逻辑调整查询条件) await Post.updateMany( { deadline: { $gt: oneDayFromNow.toISOString() }, // 假设milestones中的每个元素有completed字段,全部为true则视为已完成 milestones: { $not: { $elemMatch: { completed: false } } } }, { $set: { category: 'achieved' } } ); // 第三步:更新剩余未分类的文档 await Post.updateMany( { category: { $exists: false } }, { $set: { category: 'newGoals' } } ); console.log('批量更新完成'); })();
注:若
isLessThanOneDay和isAchieved是只能在客户端计算的复杂逻辑,仍需使用遍历更新的方式,但必须解决异步等待和查询条件的问题。
内容的提问来源于stack exchange,提问作者Zakaria Bennane
相关产品推荐
相关产品推荐

