仅当该字段包含 ObjectId 时,是否可以让 mongoose 填充混合字段?
Is it possible to have mongoose populate a Mixed field only if the field contains an ObjectId?
设置
假设我们有 Foo
和 Bar
的模式。 Foo
有一个字段 bar
。该字段可以包含引用文档的 ObjectId Bar
,也可以包含不是 ObjectId 的其他任意对象。
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
字段上使用猫鼬的 populate
自动将对 Bar
文档的引用替换为实际的 Bar
文档本身。我还想保留所有其他未引用 Bar
文档的对象不变。
通常,我会使用类似这样的方法获取所有 Foo
文档,然后填充 bar
字段:
Foo.find().populate('bar')
但是,当遇到 bar
字段中不是 ObjectId 的对象时,此方法将抛出异常,而不是让它们保持不变。
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): CastError: Cast to ObjectId failed for value "Some arbitrary object" at path "_id" for model "Bar"
尝试寻找解决方案
我检查了在 populate
上使用 match
选项,要求 Bar
上存在一个字段:
Foo.find().populate({
path: 'bar',
match: {
name: {
$exists: true
}
}
}
不幸的是,错误是一样的。
问题
所以我的问题是,如果字段包含 ObjectId,是否有任何方法可以让 mongoose 仅 populate
一个字段,否则就不管它?
据我所知,您不能以这种方式使用填充。 Select 属性 在尝试获取人口值后起作用,在此之前无法对其进行过滤。
您必须手动完成。您可以手动完成。
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 钩子或静态方法中。
另一种选择是发送 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 个查询(以及所有人口查询),但也许这对您来说不是问题。可读性要好得多。当然,您随后可以更改查找查询以专门匹配您的情况。
设置
假设我们有 Foo
和 Bar
的模式。 Foo
有一个字段 bar
。该字段可以包含引用文档的 ObjectId Bar
,也可以包含不是 ObjectId 的其他任意对象。
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
字段上使用猫鼬的 populate
自动将对 Bar
文档的引用替换为实际的 Bar
文档本身。我还想保留所有其他未引用 Bar
文档的对象不变。
通常,我会使用类似这样的方法获取所有 Foo
文档,然后填充 bar
字段:
Foo.find().populate('bar')
但是,当遇到 bar
字段中不是 ObjectId 的对象时,此方法将抛出异常,而不是让它们保持不变。
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): CastError: Cast to ObjectId failed for value "Some arbitrary object" at path "_id" for model "Bar"
尝试寻找解决方案
我检查了在 populate
上使用 match
选项,要求 Bar
上存在一个字段:
Foo.find().populate({
path: 'bar',
match: {
name: {
$exists: true
}
}
}
不幸的是,错误是一样的。
问题
所以我的问题是,如果字段包含 ObjectId,是否有任何方法可以让 mongoose 仅 populate
一个字段,否则就不管它?
据我所知,您不能以这种方式使用填充。 Select 属性 在尝试获取人口值后起作用,在此之前无法对其进行过滤。
您必须手动完成。您可以手动完成。
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 钩子或静态方法中。
另一种选择是发送 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 个查询(以及所有人口查询),但也许这对您来说不是问题。可读性要好得多。当然,您随后可以更改查找查询以专门匹配您的情况。