开发者问题收集

当类型不兼容时,如何让 mongoose 查找查询返回 null 而不是抛出 CastError?

2018-03-10
878

问题

当使用 mongoose 通过特定字段查找文档时,如果字段的类型与查询值的类型不同,则 mongoose 将尝试将查询值转换为与字段相同的类型。

但是,如果无法转换该值,则 mongoose 将抛出 CastError

此行为可以在以下示例中看到,其中 mongoose 在尝试通过 bar 字段查询 Foo 文档时,将尝试将字符串 'invalid object id' 转换为 ObjectId

const fooSchema = new mongoose.Schema({
  bar: {
    type: mongoose.Schema.Types.ObjectId,
  }
});
const Foo = <any>mongoose.model<any>('Foo', fooSchema);
await Foo.findOne({ bar: 'invalid object id' });

UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): CastError: Cast to ObjectId failed for value "invalid object id" at path "bar" for model "Foo"

(如果 bar 是其他类型,例如 NumberBoolean ,也会发生类似的错误) )

但是,我不喜欢这种行为。我非常希望返回的文档只是变为 null ,而不会引发错误。毕竟,如果查询中的值无法转换,那么从逻辑上讲,该文档也不能存在于给定的查询下。

现在,我知道我可以在构建查询之前简单地进行检查,以确保我不会传入无法转换的类型。但是,我不喜欢这种方法,因为它会导致代码重复。通常,处理类型无效时发生的情况的代码与处理文档不存在时发生的情况的代码完全相同。

此外,我也不想完全禁用类型检查。因此,将字段类型更改为 Mixed 并不是解决方案,因为我希望在创建新文档时仍能进行类型检查。

尝试的解决方案

因此,我尝试通过创建自定义 查询助手 来检查类型,如果无效则返回 null,如果有效则执行查询,从而解决此问题。

fooSchema.query.byBar = function(id) {
  if (!mongoose.Types.ObjectId.isValid(id)) {
    return null;
  }

  return this.where('bar').equals(id);
};

然后我可以将其与 Foo.findOne().byBar('invalid object id') 一起使用。

但是,此解决方案的问题在于它不再可链接。这意味着如果我尝试诸如 Foo.findOne().byBar('invalid object id').lean() 之类的操作,如果类型检查失败,它将抛出错误(但通过类型检查后将正​​常工作):

UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property 'lean' of null

尝试使用静态方法会遇到同样的问题。

fooSchema.statics.findOneByBar = function(id: any) {
  if (!mongoose.Types.ObjectId.isValid(id)) {
    return null;
  }

  return this.findOne({ bar: id });
};

问题

因此,本质上,我希望能够让查询在类型检查失败时返回 null ,但仍然可以链接。我希望查询是可链接的,因为我仍然希望能够在类型有效的情况下指定其他查询条件。

现在,如果我尝试的解决方案是正确的,那么我需要做的不是在类型检查失败时直接返回 null ,而是从可链接查询构建器 api 返回一些内容,这将导致查询的最终结果为 null 。我本质上需要某种 1=0 查询条件 (但是,链接的问题没有为 1=0 条件提出保证答案)。所以我的问题是, 如果我尝试的解决方案是正确的,那么我应该返回一个可链接的 mongoose 查询,但会导致查询在执行查询时始终导致 null

或者, 如果我尝试的解决方案走错了方向,是否有其他方法可以让 mongoose 查找查询结果为 null ,而不是在类型不兼容时抛出 CastError

2个回答

也许我的回答有点愚蠢和直白。但我想这样做。

fooSchema.statics.findOneByBar = function(id: any) {
  if (!mongoose.Types.ObjectId.isValid(id)) {
    id = "111111111111111111111111"; // other values are also possible
  }

  return this.findOne({ bar: id });
};

这是一个有效的 objectId ,它可能永远不会由 Mongodb 生成。因此,这样查询将有效,并且所有可链接功能都将完好无损。如果它对您有帮助,请告诉我。

Saikat Hajra
2018-03-12

基于尝试的解决方案,我发现两个查询条件在功能上似乎等同于 1=0

1. 将一个空数组传递给 $in 查询条件。

fooSchema.query.byBar = function(id) {
  if (!mongoose.Types.ObjectId.isValid(id)) {
    return this.where('bar').in([]);
  }

  return this.where('bar').equals(id);
};

2. 使用返回 false$where 查询条件。

fooSchema.query.byBar = function(id) {
  if (!mongoose.Types.ObjectId.isValid(id)) {
    return this.$where('false');
  }

  return this.where('bar').equals(id);
};

这两个解决方案的优点是,只要类型检查正确完成,它们就可以工作,而不管 bar 的类型及其可能包含的值如何。这意味着我们不需要事先知道字段中可以存储或不存储什么值,因此这些解决方案也支持任意数字和布尔值。

这两种解决方案的缺点是,需要为每个需要返回 null 的字段添加这样的查询助手,如果类型不匹配,则需要对每个模型重复此操作。因此,我们不再具有原始 mongoose 查询生成器 api 的灵活性,并且会导致大量重复代码。

此外,我不确定这两种情况是否会带来性能损失。

如果需要 mongoose 查询生成器 api 的灵活性,则解决方法可能是使用 try-catch 语句,因为它将捕获抛出的 CastError 。虽然写起来有点麻烦。

let foo;
try {
  foo = await Foo.findOne().where('bar').equals('invalid object id');
} catch (err) {
  foo = null;
}

不过,如果有人有更好的解决方案,我很想听听。

Zsw
2018-03-19