Mongoose v6中findOne()查询完整对象字段返回null,如何兼容v5的查询写法?
嗨,我之前升级Mongoose的时候也踩过这个坑,正好帮你理清楚问题出在哪,以及怎么兼容原来的写法:
问题根源
其实MongoDB原生的完整对象匹配(比如{ identifier: { id: '1', team: 'test' } })是要求键的顺序和值完全和文档一致的。Mongoose v5的时候,内部悄悄做了一层处理,会自动把这种完整对象查询转换成逐个字段的匹配(也就是你Method2的写法),所以不管键顺序如何都能查到。但v6开始,Mongoose严格遵循MongoDB的原生查询规则了,不再做这个自动转换,所以如果文档里identifier的字段顺序和你查询的不一样,就会返回null。
解决办法
这里给你两个实用的方案,按需选择:
方案1:使用$expr+$eq做内容匹配
直接用MongoDB的表达式操作符,让它忽略键顺序只比较对象内容:
const doc = await TeamsModel.findOne({ $expr: { $eq: ["$identifier", { id: '1', team: 'test' }] } });
这个写法会告诉MongoDB,把文档里的identifier字段和你传入的对象做内容相等性比对,不管键的顺序如何,只要内容一致就能匹配到。
方案2:自定义插件自动转换查询(完全兼容v5写法)
如果你的项目里很多地方都用了Method1的写法,不想一个个改,可以写个Mongoose插件,自动把完整对象查询转换成点符号的字段查询:
import mongoose from 'mongoose'; // 定义插件 function nestedObjectQueryPlugin(schema) { // 给find、findOne等查询钩子添加处理逻辑 schema.pre(['find', 'findOne', 'findById'], function() { const transformQuery = (queryObj) => { for (const key in queryObj) { const value = queryObj[key]; // 判断是否是需要转换的嵌套对象(排除数组、ObjectId等特殊类型) if (typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof mongoose.Types.ObjectId)) { // 展开嵌套字段为点符号格式 for (const nestedKey in value) { queryObj[`${key}.${nestedKey}`] = value[nestedKey]; } // 删除原来的完整对象查询条件 delete queryObj[key]; } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) { // 递归处理更深层次的嵌套对象 transformQuery(value); } } }; // 处理当前查询的条件 transformQuery(this.getQuery()); }); } // 在你的schema上注册插件 teamsSchema.plugin(nestedObjectQueryPlugin);
这样之后,你再用Method1的写法查询,插件会自动把查询条件转换成Method2的格式,完全兼容v5的代码,不用修改任何业务查询逻辑。
额外提醒
如果你的文档里identifier字段的键顺序始终和查询的完全一致,其实Method1是可以正常工作的,但实际开发中,文档的字段顺序可能因为更新、插入时的操作(比如新增字段后重新保存)发生变化,所以还是上面的方案更稳妥。
备注:内容来源于stack exchange,提问作者Arpit

