假设你也在使用 Mongoose,类似这样的 Schema 也许是很常见的吧!
// Todo Schema
{
name: String,
user: { type: mongoose.Types.ObjectId, ref: 'User' },
parent: { type: mongoose.Types.ObjectId, ref: 'Todo' },
}
// User Schema
{
name: String,
}
那么问题来了,我要找用户名字是张三的 Todo 该怎么找?
你可能直觉反应是这么写:
Todo.find({'user.name':'张三'})
Todo.find({ user:{ name:'张三' }})
但是你很快就会发现不对劲。
Todo.find({'user.name':'张三'})返回的是空数组,Todo.find({ user:{ name:'张三' }}) 直接报错了!
CastError: Cast to ObjectId failed for value "{ name: '张三' }" (type Object) at path "user" for model "Todo"
这是因为,'user.name' 的形式 Mongoose 读取到 Schema 里面没有这样的字段,就忽略了; user:{ name:'张三' } 的形式 Mongoose 读取到 user 是 ObjectId,就把 { name:'张三' } 当 ObjectId 解析了,所以就会报错。
哪怕你设置了自动填充也没用,因为填充是 Mongoose 层面的操作,而不是 MongoDB 的。
那么我们怎么解决这个问题呢?
假设你只是对单个 Model 的少数 Ref 路径有这个需求,那么你可以给每个路径创建一个对于的方法:
schema.statics.findByUserName = async function(name){
const { _id } = await User.findOne({name},'_id').exec()
return await Todo.find({ user:_id })
}
原理就是事先把符合条件的 ObjectId 拿到手,再判断 Todo 的 user 在不在里面。
但是你要是嫌麻烦,或者干脆就是想要一个通用的自动搜索功能,那么你可以安装我编写的 mongoose-find-by-reference 插件:
npm i -S mongoose-find-by-reference
接着使用它:
...
// Require mongoose-find-by-reference
const { MongooseFindByReference } = require('mongoose-find-by-reference');
...
// 把插件应用在对应的 Schema 上
TodoSchema.plugin(MongooseFindByReference);
接着,你可以这么用:
const result = await todoModel
.find({
$and: {
parent: {
'user.name': '李四',
},
user: {
name : '张三',
}
},
})
.exec();
搜索条件就会被插件自动替换成:
const newConditions = {
$and: {
parent: {
$in: [/* 符合条件的 Todo 的 ObjectId 数组 */],
},
user: {
$in: [/* 符合条件的 User 的 ObjectId 数组 */],
},
},
};
就此,大功告成!