升级MongoDB 5.0与Mongoose 6.0.12后executeLegacyOperation报错排查
问题:升级MongoDB 5.0与Mongoose 6.0.12后Jest测试报错
Final argument to executeLegacyOperation must be a callback 问题背景
我们正在升级至MongoDB 5.0,同步将Mongoose版本更新至6.0.12。运行Jest批量测试时触发错误,提示:Final argument to executeLegacyOperation must be a callback,完整错误信息为包含GraphQLError的数组,报错路径为upsertBatchRole。
当前环境参数:
- GraphQL版本:15.5.1(曾尝试升级至15.8.0,更高版本会引发其他错误)
- Node版本:14.21.3
失败测试用例示例
test('should update existing items and return UPDATED status', async () => { const { data: { upsertBatchRole }, } = await gqlClient.fetch<{ upsertBatchRole: GQLUpsertBatchRoleOutput }>(upsertBatchRoleMutation, { condition: {}, input: [ { name: 'new', id: '57', }, ], }); expect(upsertBatchRole).not.toBeNull(); const { status, data } = upsertBatchRole as GQLUpsertBatchRoleOutput; const unitedData = uniteCategorizedArrays<GQLRole>({ ...data }); expect(status).toBe(ms.UPDATED); expect(unitedData.length).toBe(1); expect(unitedData[0]).toHaveProperty('name'); expect(unitedData[0]?.name).toBe('new'); _.forEach(unitedData, (item) => { expect(item).toHaveProperty('id'); expect(item).toHaveProperty('name'); }); const { data: { roleList }, } = await gqlClient.fetch(` roleList { name } `); const orderedList = _.sortBy(roleList, 'name'); expect(orderedList).toHaveLength(1); expect(orderedList[0].name).toBe('new'); });
问题代码片段(upsertBatchEntity.js)
async function updateBatchEntity(Collection, entitiesToUpdate, gqlInfo) { if (_.isEmpty(entitiesToUpdate)) return { status: ms.UPDATED, data: [] }; const transactionSession = getCurrentTransactionSessionFromGQLInfo(gqlInfo); const bulkUpdate = Collection.collection.initializeOrderedBulkOp({ session: transactionSession, }); _.forEach(entitiesToUpdate, (entity) => { const _id = new mongoose.Types.ObjectId(entity._id); const entityProperties = _.omit(entity, ['id', '_id']); const convertedEntityProperties = convertEntityPropertiesToObjectIdWhereNeeded(Collection, entityProperties); bulkUpdate.find({ _id }).updateOne({ $set: { ...convertedEntityProperties, updatedAt: new Date() } }); }); await bulkUpdate.execute(null, { session: transactionSession }); clearEntityLoadersCache(Collection.modelName, gqlInfo); return { status: ms.UPDATED, data: entitiesToUpdate, }; }
解决方案
错误原因
这个报错和GraphQL版本无关,是Mongoose 6.x升级后,底层MongoDB驱动对批量操作API的调用规则发生变化导致的:
initializeOrderedBulkOp属于遗留API,其返回的Bulk操作的execute方法不再支持传入非回调类型的参数作为第一个参数- 代码中
await bulkUpdate.execute(null, { session: transactionSession });的null被驱动识别为回调参数,但它不是一个函数,因此触发错误
修复方案
推荐使用Mongoose官方推荐的现代Promise风格API bulkWrite替代遗留的initializeOrderedBulkOp,修改后的代码如下:
async function updateBatchEntity(Collection, entitiesToUpdate, gqlInfo) { if (_.isEmpty(entitiesToUpdate)) return { status: ms.UPDATED, data: [] }; const transactionSession = getCurrentTransactionSessionFromGQLInfo(gqlInfo); // 构建bulkWrite所需的操作数组 const updateOperations = entitiesToUpdate.map(entity => { const _id = new mongoose.Types.ObjectId(entity._id); const entityProperties = _.omit(entity, ['id', '_id']); const convertedEntityProperties = convertEntityPropertiesToObjectIdWhereNeeded(Collection, entityProperties); return { updateOne: { filter: { _id }, update: { $set: { ...convertedEntityProperties, updatedAt: new Date() } } } }; }); // 调用bulkWrite并传入会话参数 await Collection.bulkWrite(updateOperations, { session: transactionSession }); clearEntityLoadersCache(Collection.modelName, gqlInfo); return { status: ms.UPDATED, data: entitiesToUpdate, }; }
备选修复方案(保留遗留API)
如果暂时不想替换API,可修改execute的调用方式——删除多余的null参数,且会话已在初始化Bulk操作时传入,无需重复传递:
// 原代码 // await bulkUpdate.execute(null, { session: transactionSession }); // 修改为 await bulkUpdate.execute();
内容的提问来源于stack exchange,提问作者Greg Norkett
相关产品推荐
相关产品推荐

