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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 13:52:50