问题
Setup
Let's say we have schemas for Foo
and Bar
. Foo
has a field bar
. This field can either contain an ObjectId that reference a document Bar
, or it could contain other arbitrary objects that are not 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);
Problem
Now suppose we have a bunch of Foo
documents.
I would like to be able to use mongoose's populate on the bar
field to automatically replace references to a Bar
document with the actual Bar
document itself. I would also like to leave all other objects that are not references to a Bar
document unchanged.
Normally, I would use something like this to get all the Foo
documents and then populate the bar
field:
Foo.find().populate('bar')
However, this method will throw an exception when it encounters objects in the bar
field that are not ObjectIds, as opposed to leaving them untouched.
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): CastError: Cast to ObjectId failed for value "Some arbitrary object" at path "_id" for model "Bar"
Attempt at finding a solution
I have examined using the match
option on populate
, by requiring that a field on Bar
exists:
Foo.find().populate({
path: 'bar',
match: {
name: {
$exists: true
}
}
}
Unfortunately, the error is the same.
Question
So my question is then, is there any way to get mongoose to only populate
a field if the field contains an ObjectId, and leave it alone otherwise?
回答1:
As far as I know you cannot use populate that way. Select property works after trying to get values for population and there's no way to filter it before that.
You would have to do it manually. You could do it manually.
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);
});
Elegantly would be to wrap it in post hook or static method on your Model.
Another option would be to send 2 queries:
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);
This is of course suboptimal because of 2 queries (and all population queries) but maybe this will not be a problem for you. Readability is much better. Of course you could then change find queries to match your case specifically.
来源:https://stackoverflow.com/questions/46373989/is-it-possible-to-have-mongoose-populate-a-mixed-field-only-if-the-field-contain