Mongoose中如何在保存文档前获取该文档的_id值
我有一个简单的控制器用于为用户创建帖子,该模型关联了另一个Schema。当我尝试新建帖子时,需要获取该帖子的id以关联其他Schema。
Schema代码如下:
const mongoose = require("mongoose"); const User = require("./User"); const View = require("./View"); const ArticleSchema = new mongoose.Schema({ title: { type: String, required: true, trim: true, }, body: { type: String, required: true, }, status: { type: String, default: "public", enum: ["public", "private"], }, user: { type: mongoose.Schema.Types.ObjectId, ref: "User", }, views: { type: mongoose.Schema.Types.ObjectId, ref: "View", }, createdAt: { type: Date, default: Date.now, }, }); module.exports = mongoose.model("Article", ArticleSchema);
关联user字段没有问题,因为该值已经存储在内存中。但views字段需要对应文章文档的postId,不先创建文档就无法获取到该id。
我的创建帖子控制器代码如下:
module.exports.createArticleController = async function (req, res) { try { req.body.user = req.User._id; const article = await Article.create(req.body).exec() res.redirect(`/${article.title}/${article._id}`); } catch (e) { console.error(e); } };
我的问题是:如何在执行model.create()的过程中获取生成的id,从而将view关联到该id,能否通过this运算符实现?我不希望在创建完成后再执行更新操作。
实现方案
有两种方案可以避免创建完成后再执行更新操作:
方案1:预生成文档ID,一次性写入
Mongoose 构造模型实例时就会自动生成_id,不需要等待数据库写入完成,你可以先构造实例、拿到ID处理关联后再统一存入数据库:
const mongoose = require("mongoose"); const View = require("./View"); module.exports.createArticleController = async function (req, res) { try { // 构造Article实例,此时_id已经自动生成 const article = new Article({ ...req.body, user: req.User._id }); // 创建View文档,关联当前Article的ID const view = await View.create({ article: article._id, // 补充View的其他初始字段 }); // 给Article的views字段赋值View的ID article.views = view._id; // 一次性保存Article到数据库 await article.save(); res.redirect(`/${article.title}/${article._id}`); } catch (e) { console.error(e); res.status(500).send('创建失败'); } };
该方式全程仅两次写入操作(一次View、一次Article),不需要对已创建的Article执行更新。
方案2:通过Schema pre中间件自动处理关联
如果所有Article创建时都需要自动关联View,可以把逻辑封装到Schema的pre('save')中间件中,控制器无需额外处理关联逻辑:
首先在Article的Schema文件中添加中间件:
const View = require("./View"); ArticleSchema.pre('save', async function(next) { // 仅新建文档时创建关联View if (this.isNew) { const view = await View.create({ article: this._id, // this指向待保存的Article实例,可直接获取预生成的_id // 补充View的其他初始字段 }); this.views = view._id; } next(); });
之后控制器逻辑可以保持简洁:
module.exports.createArticleController = async function (req, res) { try { req.body.user = req.User._id; // create方法执行时会自动触发中间件处理关联 const article = await Article.create(req.body); res.redirect(`/${article.title}/${article._id}`); } catch (e) { console.error(e); res.status(500).send('创建失败'); } };
注:你原代码中Article.create().exec()属于错误用法,create方法本身返回Promise,不需要调用exec(),exec仅适用于Query对象(如find()返回的查询对象)。
关于this运算符的问题
在Schema的pre save中间件中,this确实指向待保存的文档实例,可以直接拿到预生成的_id,也就是方案2的实现基础,完全可以满足你的需求。
内容的提问来源于stack exchange,提问作者Saswat

