如何在Mongoose嵌套Schema中实现工厂+项目名的复合唯一约束
问题原因分析
你之前的自定义验证失效是因为把验证器加在了factory字段上,此时validator函数的参数是factory字段的取值(即{ factory: "工厂名称" }这个对象),而非整个Project文档。所以代码里的project.projectTitle根本取不到值,查询条件中的projectTitle为undefined,自然匹配不到已存在的重复数据,导致验证逻辑完全失效。
解决方案
推荐两种方案,优先选择数据库层面的复合唯一索引,这比自定义验证更可靠、性能更好。
方案一:使用复合唯一索引(最优解)
利用MongoDB的复合唯一索引,直接在数据库层面约束projectTitle和factory.factory的组合唯一性,这是最安全高效的方式:
const mongoose = require('mongoose'); const factorySchema = new mongoose.Schema({ factory: { type: String, required: true, }, }); const projectSchema = new mongoose.Schema({ projectTitle: { type: String, required: true, }, projectDescription: { type: String, required: true, }, updatedAt: { type: Date, default: Date.now, }, factory: { type: factorySchema, required: true, }, }); // 添加复合唯一索引:同一factory下的projectTitle必须唯一 projectSchema.index({ projectTitle: 1, 'factory.factory': 1 }, { unique: true }); module.exports = mongoose.model('project', projectSchema);
注意事项:
- 如果你的数据库中已经存在重复数据,需要先清理这些重复项,否则索引创建会失败。
- 索引会自动作用于新增和更新操作,当出现重复时,Mongoose会抛出
MongoError: E11000 duplicate key error collection错误,你可以在业务代码中捕获并处理这个错误,返回自定义提示。
方案二:修正自定义验证逻辑
如果你更倾向于使用自定义验证,需要将验证逻辑移到能获取整个Project文档的位置,比如projectTitle字段的验证器,或者Schema级别的pre-save钩子:
方式1:在projectTitle字段上添加验证器
const projectSchema = new mongoose.Schema({ projectTitle: { type: String, required: true, validate: { validator: async function(value) { // this指向当前Project文档实例 const count = await this.constructor.countDocuments({ _id: { $ne: this._id }, // 更新时排除自身 'factory.factory': this.factory.factory, projectTitle: value }); return count === 0; }, message: 'Another project with the same projectTitle already exists in this factory.' } }, // 其他字段定义... });
方式2:使用pre-save钩子
projectSchema.pre('save', async function(next) { const existingProject = await this.constructor.findOne({ _id: { $ne: this._id }, 'factory.factory': this.factory.factory, projectTitle: this.projectTitle }); if (existingProject) { // 抛出错误阻止保存 return next(new Error('Another project with the same projectTitle already exists in this factory.')); } next(); });
内容的提问来源于stack exchange,提问作者lolu
相关产品推荐
相关产品推荐

