Mongoose:如何基于关联文档的数组长度验证数组长度
解决Mongoose中跨文档数组长度验证的问题
问题描述
需要在UserLevelProgress Schema中验证progress数组的长度必须与关联的Level文档中level数组的长度一致,但Schema内的验证器无法直接访问关联的Level对象,原代码中level.level.length的写法无效。
解决方案
由于Schema定义时仅存储了关联文档的ObjectId,无法直接获取关联文档的内容,需要通过异步验证器或预保存中间件查询数据库获取对应Level文档后再进行验证。
方法一:使用异步验证器
将验证器改为异步函数,通过this.level获取关联的Level文档ID,查询数据库拿到完整文档后对比数组长度:
const mongoose = require('mongoose'); const Level = mongoose.Schema({ level: { type: Array, required: true, items: { exercise: { type: mongoose.Schema.Types.ObjectId, ref: "Exercise", required: true, }, accuracyThreshold: { type: Number, required: true }, timeThreshold: { type: Number, required: true }, }, }, }); const UserLevelProgress = mongoose.Schema({ user: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true, }, level: { type: mongoose.Schema.Types.ObjectId, ref: "Level", required: true, }, progress: { type: Array, required: true, validate: { // 使用普通函数而非箭头函数,保留this指向当前文档 validator: async function(progress) { // 查询关联的Level文档 const levelDoc = await mongoose.model('Level').findById(this.level); // 若Level文档不存在,直接验证失败(因level字段为required,此情况理论上不会出现) if (!levelDoc) return false; // 对比数组长度 return progress.length === levelDoc.level.length; }, message: '进度数组长度与对应关卡的练习数量不匹配!' }, items: { accuracyScore: { type: Number, required: true }, timeScore: { type: Number, required: true }, }, }, }); // 先导出Level模型,避免查询时找不到 module.exports.Level = mongoose.model("Level", Level); module.exports.UserLevelProgress = mongoose.model("UserLevelProgress", UserLevelProgress);
方法二:使用预保存中间件
在UserLevelProgress Schema上添加pre('save')中间件,在保存前查询Level文档并验证长度:
const mongoose = require('mongoose'); const Level = mongoose.Schema({ level: { type: Array, required: true, items: { exercise: { type: mongoose.Schema.Types.ObjectId, ref: "Exercise", required: true, }, accuracyThreshold: { type: Number, required: true }, timeThreshold: { type: Number, required: true }, }, }, }); const UserLevelProgress = mongoose.Schema({ user: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true, }, level: { type: mongoose.Schema.Types.ObjectId, ref: "Level", required: true, }, progress: { type: Array, required: true, items: { accuracyScore: { type: Number, required: true }, timeScore: { type: Number, required: true }, }, }, }); // 添加预保存中间件 UserLevelProgress.pre('save', async function(next) { const levelDoc = await mongoose.model('Level').findById(this.level); if (this.progress.length !== levelDoc.level.length) { // 验证失败,抛出错误 return next(new Error('进度数组长度与对应关卡的练习数量不匹配!')); } // 验证通过,继续保存流程 next(); }); module.exports.Level = mongoose.model("Level", Level); module.exports.UserLevelProgress = mongoose.model("UserLevelProgress", UserLevelProgress);
注意事项
- 必须使用普通函数而非箭头函数,确保
this指向当前要保存的UserLevelProgress文档,才能获取this.level的值。 - 注意模型导出顺序,先导出
Level模型,或使用mongoose.model('Level')动态获取模型,避免循环引用问题。 - 两种方法均为异步操作,需使用
async/await处理数据库查询。
内容的提问来源于stack exchange,提问作者Lucas Rahn
相关产品推荐
相关产品推荐

