You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Mongoose通过_id(ObjectId)查询返回空数组的问题排查

问题:Mongoose通过_id查询客户返回空数组的排查与解决

我编写了如下findCustomer函数:

export const findCustomer = async (query?: FindCustomerParams) => {
  try {
    await database.connect();
  } catch (err: any) {
    throw new Error("Could not connect to the database. Root error: " + err);
  }

  if (!query) {
    return await customer.find();
  }

  findCustomerParams.parse(query);

  let returnCustomers;
  try {
    const fetchedCustomers: (Omit<Customer, "address"> & { address?: FindOneCustomerParams["address"] })[] | null =
      await customer.find(query ? removeFalsyValues({ ...query, _id: new mongoose.Types.ObjectId(query.id) }) : {});

    if (!fetchedCustomers) {
      throw new Error();
    }

    if (query.address) {
      const fetchedAddresses = await addressMethods.find(query.address);

      for (let i = 0; i < fetchedAddresses.length; i++) {
        if (!fetchedAddresses || !fetchedAddresses[i].customers.includes(fetchedCustomers[i]._id)) {
          throw new Error(`Could find address on customer ${fetchedCustomers[i]._id}`);
        }

        fetchedCustomers[i].address = fetchedAddresses[i];
      }
    }

    returnCustomers = fetchedCustomers;
  } catch (err: any) {
    throw new Error("Failed to find a customer. Root error: " + err);
  }

  await database.close();

  return returnCustomers;
};

该函数除通过_id查询外,其他查询均正常工作。当通过_id查询时,会返回空数组,而实际应返回单个客户数据。

可正常返回结果的查询示例:

await findCustomer({firstName: "Ethan"})

无法返回结果的查询示例:

await findCustomer({_id: id})

我已尝试以下操作:

  • 验证ID正确性
  • 确认已连接Atlas集群
  • 尝试将_id从string类型转为ObjectId类型
  • 分别使用id和_id查询均无效
  • 控制台无报错信息

解决方法

问题根源

核心bug出在查询构造逻辑:当传入{_id: id}时,代码里写的是new mongoose.Types.ObjectId(query.id),但你传入的参数里只有_id字段,query.id是undefined,转成ObjectId后会生成一个无效的空ID,自然查不到数据。

另外还有两个潜在问题:

  1. 用customer.find()查单个ID会返回数组,更适合用customer.findOne(),性能更好且符合单文档查询的预期。
  2. 每次查询都手动执行connect()和close(),Mongoose默认会维护连接池,频繁开闭连接会导致性能损耗甚至连接异常。

修复后的代码

export const findCustomer = async (query?: FindCustomerParams) => {
  // 移除手动连接/关闭逻辑:Mongoose会自动维护连接池,在应用启动时初始化一次即可
  if (!query) {
    return await customer.find();
  }

  findCustomerParams.parse(query);

  let returnCustomers;
  try {
    let queryObj = { ...query };

    // 正确处理ID查询:判断传入的是_id还是id字段,转换为ObjectId
    if (query._id) {
      queryObj._id = new mongoose.Types.ObjectId(query._id);
      delete queryObj.id; // 清理冗余字段
    } else if (query.id) {
      queryObj._id = new mongoose.Types.ObjectId(query.id);
      delete queryObj.id;
    }

    // 清理空值
    queryObj = removeFalsyValues(queryObj);

    // 单个ID查询用findOne,其他条件用find
    if (queryObj._id) {
      const fetchedCustomer = await customer.findOne(queryObj);
      if (!fetchedCustomer) {
        throw new Error("Customer not found");
      }

      // 关联查询地址(按客户ID匹配,而非原逻辑的索引匹配)
      if (query.address) {
        const fetchedAddress = await addressMethods.find({
          ...query.address,
          customers: fetchedCustomer._id
        });
        if (!fetchedAddress?.length) {
          throw new Error(`Could not find address on customer ${fetchedCustomer._id}`);
        }
        fetchedCustomer.address = fetchedAddress[0];
      }
      returnCustomers = [fetchedCustomer]; // 保持返回数组格式兼容原有逻辑
    } else {
      const fetchedCustomers = await customer.find(queryObj);
      if (!fetchedCustomers) {
        throw new Error();
      }

      if (query.address) {
        const fetchedAddresses = await addressMethods.find(query.address);
        // 按客户ID匹配地址,避免原逻辑索引错位的问题
        for (let customer of fetchedCustomers) {
          const matchedAddress = fetchedAddresses.find(addr => addr.customers.includes(customer._id));
          if (!matchedAddress) {
            throw new Error(`Could not find address on customer ${customer._id}`);
          }
          customer.address = matchedAddress;
        }
      }
      returnCustomers = fetchedCustomers;
    }
  } catch (err: any) {
    throw new Error("Failed to find a customer. Root error: " + err);
  }

  return returnCustomers;
};

关键修复点

  • 修正_id转换逻辑:根据传入的字段(_id或id)正确获取ID值并转换为ObjectId
  • 针对单个ID查询使用findOne,优化性能和结果准确性
  • 移除冗余的连接/关闭逻辑,改用Mongoose连接池管理
  • 优化地址关联查询的匹配逻辑,避免原代码中按索引匹配的潜在错误

内容的提问来源于stack exchange,提问作者Ethan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 06:22:29