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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 07:50:31