MongoDB报错:Cannot do inclusion on field 'guides' in exclusion projection 求助
Mongoose报错解决:Cannot do inclusion on field guides in exclusion projection
问题场景
在Mongoose开发中,tourSchema定义guides字段为引用User的ObjectId数组,并通过pre(/^find/)中间件自动填充该字段。调用getAllTours接口(使用APIFeatures做字段限制)时触发报错,但调用getTour接口(通过findById查询单条数据)无异常。
报错原因
getAllTours中APIFeatures的limitFields方法默认添加了select('-__v')(排除式投影),而pre(/^find/)中间件会对所有find类查询自动执行populate('guides')。Mongoose不允许在排除式投影中单独包含某个字段,两者操作冲突导致报错。
getTour接口未经过APIFeatures的字段限制处理,无投影规则,因此populate可以正常执行。
相关代码
1. Schema定义
const tourSchema = new mongoose.Schema({ // ...其他字段 guides: [ { type: mongoose.Schema.ObjectId, ref: 'User' } ] }); tourSchema.pre(/^find/, function(next) { this.populate({ path: 'guides' }); next(); });
2. 触发错误的接口
exports.getAllTours = catchAsync(async (req, res, next) => { const features = new APIFeatures(Tour.find(), req.query) .filter() .sort() .limitFields() .paginate(); const tours = await features.query; res.status(200).json({ status: 'success', length: tours.length, data: { tours } }); });
3. 无错误的接口
exports.getTour = catchAsync(async (req, res, next) => { const tour = await Tour.findById(req.params.id); if (!tour) { return next( new AppError('No tour found! please check the ID correctly', 404) ); } res.status(200).json({ status: 'success', requestedAt: req.requestTime, data: { tour } }); });
解决方案
以下提供三种可行方案,可根据业务需求选择:
方案一:统一投影规则,在populate中排除相同字段
修改pre(/^find/)中间件,给populate添加与默认投影一致的排除字段,避免冲突:
tourSchema.pre(/^find/, function(next) { this.populate({ path: 'guides', select: '-__v' // 和limitFields默认排除的__v保持一致 }); next(); });
方案二:动态处理投影与populate的冲突
在中间件中检查当前查询的投影类型,动态调整populate的字段选择:
tourSchema.pre(/^find/, function(next) { const selectOption = this.getOptions().select; // 判断是否为排除式投影 if (selectOption && typeof selectOption === 'string' && selectOption.startsWith('-')) { // 提取所有排除字段,应用到populate const excludeFields = selectOption.split(' ') .filter(f => f.startsWith('-')) .map(f => f.slice(1)); this.populate({ path: 'guides', select: excludeFields.map(f => `-${f}`).join(' ') }); } else { // 非排除式投影,正常执行populate this.populate({ path: 'guides' }); } next(); });
方案三:修改APIFeatures的limitFields方法,避免默认排除式投影
将limitFields的默认投影从排除式改为包含式(需明确指定需要的字段),或调整逻辑避免冲突:
class APIFeatures { // ...其他方法 limitFields() { if (this.queryString.fields) { const fields = this.queryString.fields.split(',').join(' '); this.query = this.query.select(fields); } else { // 默认包含所有字段,仅排除__v(改为包含式写法,避免冲突) this.query = this.query.select('+guides -__v'); } return this; } }
内容的提问来源于stack exchange,提问作者sahacing
相关产品推荐
相关产品推荐

