Mongoose:如何在预保存钩子中先创建关联URL再保存文档
这个问题我之前也遇到过,核心原因是Mongoose对Schema定义的类型校验过滤了你传入的原始URL对象,导致预保存钩子里拿不到对应的属性。下面给你两种可行的解决方案:
方法一:用预保存钩子配合临时属性
这种方式不需要修改调用逻辑,只需要在传入文档时把原始URL数据放到一个临时属性里,再通过钩子处理:
// 保持你的Schema定义不变 const DocumentSchema = new Schema({ text: String, file_type: String, name: String, urls: [{ type: Schema.Types.ObjectId, ref: 'URL' }] }); const URLSchema = new Schema({ path: String, display_url: String }); const URL = mongoose.model('URL', URLSchema); const Document = mongoose.model('Document', DocumentSchema); // 编写预保存钩子,处理临时URL数据 Document.pre('save', async function(next) { // 检查是否有需要处理的临时URL数据 if (this._doc._tempUrls && this._doc._tempUrls.length > 0) { try { // 批量创建URL文档 const createdUrls = await URL.insertMany(this._doc._tempUrls); // 将创建好的URL的ID赋值给documents的urls字段 this.urls = createdUrls.map(url => url._id); // 删除临时属性,避免存入数据库 delete this._doc._tempUrls; next(); } catch (err) { next(err); } } else { next(); } }); // 调用create时,把原始urls放到_tempUrls属性里 const documents = [ { name: 'doc1', text: 'text1', file_type: 'txt', _tempUrls: [{ path: 'path1', display_url: 'path1' }] }, // 更多文档... ]; Document.create(documents) .then(createdDocs => console.log('创建完成:', createdDocs)) .catch(err => console.error('创建失败:', err));
方法二:自定义静态方法批量处理
如果你不想用临时属性,可以给Document模型添加一个静态方法,先统一处理所有URL再创建文档:
// 保持Schema定义不变 const DocumentSchema = new Schema({ text: String, file_type: String, name: String, urls: [{ type: Schema.Types.ObjectId, ref: 'URL' }] }); const URLSchema = new Schema({ path: String, display_url: String }); const URL = mongoose.model('URL', URLSchema); const Document = mongoose.model('Document', DocumentSchema); // 给Document模型添加自定义静态方法 DocumentSchema.statics.createWithUrls = async function(documents) { // 收集所有需要创建的URL,并记录每个文档对应的URL索引 const allUrls = []; documents.forEach(doc => { if (doc.urls && doc.urls.length > 0) { doc.urlIndices = doc.urls.map(_ => allUrls.length); allUrls.push(...doc.urls); } }); // 批量创建所有URL const createdUrls = await URL.insertMany(allUrls); // 将每个文档的urls替换为对应的URL ID documents.forEach(doc => { if (doc.urlIndices) { doc.urls = doc.urlIndices.map(idx => createdUrls[idx]._id); delete doc.urlIndices; } }); // 最后批量创建文档 return this.create(documents); }; // 使用自定义方法创建文档 Document.createWithUrls(documents) .then(createdDocs => console.log('创建完成:', createdDocs)) .catch(err => console.error('创建失败:', err));
为什么你的原钩子拿不到urls?
因为你的DocumentSchema里urls定义的是ObjectId数组类型,但你传入的是包含path和display_url的普通对象数组,Mongoose在初始化文档实例时会做类型校验,类型不匹配的属性会被过滤掉,所以钩子里的this自然拿不到urls。而临时属性_tempUrls因为Schema里没有定义,Mongoose会把它保留在this._doc中,所以能正常读取。
内容的提问来源于stack exchange,提问作者mdmb
相关产品推荐
相关产品推荐

