如何使猫鼬查找查询返回 null,而不是在类型不兼容时抛出 CastError



>问题

使用 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' });
未处理的承诺

拒绝警告:未处理的承诺拒绝 (拒绝 ID: 1):强制转换错误:值强制转换为对象 ID 失败 模型"Foo"的路径"bar"处的"无效对象 ID">

(如果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()之类的东西,如果类型检查失败,它将抛出错误(但在通过类型检查时可以正常工作):

未处理的承诺

拒绝警告:未处理的承诺拒绝 (拒绝 ID: 1): 类型错误: 无法读取 null 的属性"lean">

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

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条件提出保证的答案)。所以我的问题是,如果我尝试的解决方案在正确的轨道上,那么我返回的是一个可链接的猫鼬查询,但会导致查询在执行查询时总是导致null

或者,如果我尝试的解决方案在错误的轨道上,是否有另一种方法可以使猫鼬查找查询结果null而不是在类型不兼容时抛出CastError

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

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生成。因此,这样查询将是有效的,并且所有可链接的功能都将保持不变。让我知道它是否对您有帮助。

在尝试的解决方案的基础上,我发现了两个查询条件,它们似乎在功能上等同于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的每个字段添加这样的查询帮助程序,并且需要为每个模型重复此操作。因此,我们不再具有原始猫鼬查询生成器 API 的灵活性,并且会导致大量重复代码。

此外,我不确定这两个条件中的任何一个是否会降低性能。

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

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

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

最新更新