只有当字段包含ObjectId时,是否可以让mongoose填充混合字段?

时间:2017-09-22 22:21:48

标签: javascript node.js typescript mongoose mongoose-populate

设置

假设我们有FooBar的模式。 Foo有一个字段bar。此字段可以包含引用文档Bar的ObjectId,也可以包含非ObjectIds的其他任意对象。

const fooSchema = new mongoose.Schema({
  bar: {
    type: mongoose.Schema.Types.Mixed,
    ref: 'Bar'
  }
});
const Foo = <any>mongoose.model<any>('Foo', fooSchema);

const barSchema = new mongoose.Schema({
  name: String
});
const Bar = <any>mongoose.model<any>('Bar', barSchema);

问题

现在假设我们有一堆Foo文件。

我希望能够在bar字段上使用mongoose的populate来自动将Bar文档的引用替换为实际的Bar文档本身。我还想保留所有其他未引用Bar文档的对象。

通常,我会使用这样的内容来获取所有Foo文档,然后填充bar字段:

Foo.find().populate('bar')

但是,当遇到bar字段中不是ObjectIds的对象时,此方法将抛出异常,而不是保持不变。

  

UnhandledPromiseRejectionWarning:未处理的承诺拒绝(拒绝ID:1):CastError:对于模型“Bar”的路径“_id”的值“某个任意对象”,Cast to ObjectId失败

尝试寻找解决方案

我已使用match上的populate选项进行了检查,要求Bar上的字段存在:

Foo.find().populate({
  path: 'bar',
  match: {
    name: {
      $exists: true
    }
  }
}

不幸的是,错误是一样的。

问题

那么我的问题是,如果字段包含ObjectId,有没有办法让mongoose只有populate字段,否则不管它?

1 个答案:

答案 0 :(得分:1)

据我所知,你不能用那种方式填充。选择属性在尝试获取填充值后仍然有效,并且在此之前无法对其进行过滤。

你必须手动完成。你可以手动完成。

let foos = await Foo.find({});
foos = foos.map(function (f) {
  return new Promise(function (resolve) {
    if (condition()) {
        Foo.populate(f, {path: 'bar'}).then(function(populatedF){
            resolve(f);
        });
    } else {
      resolve(f);
    }
  });
});

await Promise.all(foos).then(function (fs) {
  res.status(200).json(fs);
});

优雅地将它包装在模型上的post hook或static方法中。

另一种选择是发送2个查询:

const foosPopulated = Foo.find({ alma: { $type: 2 } }).populate('bar'); // type of string
const foosNotPopulated = Foo.find({ alma: { $type: 3 } }); // type of object

const foos = foosPopulated.concat(foosNotPopulated);

由于2次查询(以及所有人口查询),这当然不是最理想的,但这对您来说可能不是问题。可读性要好得多。当然,您可以更改查找查询以特定地匹配您的案例。