首页
/ TypeORM invalidWhereValuesBehavior 深入解析:WHERE 条件中 null 与 undefined 的处理机制

TypeORM invalidWhereValuesBehavior 深入解析:WHERE 条件中 null 与 undefined 的处理机制

2026-09-05 20:41:55作者:管翌锬

本篇指南聚焦 TypeORM 中 invalidWhereValuesBehavior 数据源配置项,讲解它如何决定 findupdatedeletesoftDeleterestore 等高层 API 在遇到 null/undefined 查询条件时的行为(报错、跳过或转为 SQL NULL)。读完本文,你将掌握三种行为的配置方式、适用边界,以及 QueryBuilder 底层 .where() 不受该配置影响时的正确 null 匹配写法,并能对照仓库源码理解其完整实现链路。

背景:WHERE 条件里的 null 与 undefined 是“危险值”

在 SQL 的语义里,NULL 表示“未知”,而 column = NULL 永远为假——没有任何行能满足这个条件。如果把 JavaScript 的 null 直接放进 TypeORM 的 where 条件,到底应该匹配数据库中的 NULL 值、被静默忽略,还是直接报错?TypeORM 认为这是一个应该由用户显式决策的问题,因此:

  • 在 TypeScript 开启 strictNullChecks 时,编译期就禁止显式传入 null
  • 运行期默认行为是抛出 TypeORMError,帮助你尽早发现潜在 bug;
  • 该行为可以通过数据源选项 invalidWhereValuesBehavior 自定义。

这一配置只作用于高层操作find 系列操作、Repository 方法、EntityManager 的 update / delete / softDelete / restore。它影响 QueryBuilder 的 .where().andWhere().orWhere()——那是一层“原样透传”的底层 API(后文有专门章节说明)。

默认行为:抛错 + 使用 IsNull() 匹配 NULL

默认(未配置时,两个子项均按 "throw" 处理)TypeORM 会在 where 条件中出现 nullundefined 时直接抛出错误,而不是默默生成 col = NULL 这样的无效条件:

// 两个查询都会抛出错误
const posts1 = await repository.find({
    where: {
        text: null,
    },
})
// Error: Null value encountered in property 'text' of a where condition.

const posts2 = await repository.find({
    where: {
        text: undefined,
    },
})
// Error: Undefined value encountered in property 'text' of a where condition.

如果确实想匹配数据库中的 NULL 值,请使用 IsNull 操作符(详见 Find Options 文档):

const posts = await repository.find({
    where: {
        text: IsNull(),
    },
})

这一默认策略在 SelectQueryBuilder 的 buildWhere 实现 中可以确认:逐键遍历 where 对象时,先检查 undefined,再检查 null,默认值通过 ?? "throw" 兜底。

配置项:invalidWhereValuesBehavior

在数据源配置中通过 invalidWhereValuesBehavior 自定义 null 与 undefined 的处理方式,两者相互独立:

const dataSource = new DataSource({
    // ... 其他选项
    invalidWhereValuesBehavior: {
        null: "ignore" | "sql-null" | "throw",
        undefined: "ignore" | "throw",
    },
})

其类型定义位于 InvalidFindOptionsWhereBehavior.ts

export type InvalidFindOptionsWhereBehavior = {
    /**
     * How to handle null values in where conditions.
     * - 'ignore': Skip null properties
     * - 'sql-null': Transform null to SQL NULL
     * - 'throw': Throw an error when null is encountered (default)
     */
    readonly null?: "ignore" | "sql-null" | "throw"

    /**
     * How to handle undefined values in where conditions.
     * - 'ignore': Skip undefined properties
     * - 'throw': Throw an error when undefined is encountered (default)
     */
    readonly undefined?: "ignore" | "throw"
}

两个字段都是可选的,因此可以只覆盖其中一个维度。该选项声明在 BaseDataSourceOptions.ts 中,注释明确说明其作用范围:

Controls how null/undefined values in where criteria are handled by find and write methods (update/delete/softDelete/restore). Defaults to "throw".

null 的三种行为

'ignore' —— 跳过该属性

where 条件中的 null 值被忽略,相当于该条件不存在:

const dataSource = new DataSource({
    // ... 其他选项
    invalidWhereValuesBehavior: {
        null: "ignore",
    },
})

// 返回所有 post:text 属性被跳过,没有生成任何过滤条件
const posts = await repository.find({
    where: {
        text: null,
    },
})

'sql-null' —— 转换为 IS NULL 条件

JavaScript null 会被转换为 SQL NULL 条件,只返回该列为 NULL 的行:

const dataSource = new DataSource({
    // ... 其他选项
    invalidWhereValuesBehavior: {
        null: "sql-null",
    },
})

// 只返回 text 列为 NULL 的 post
const posts = await repository.find({
    where: {
        text: null,
    },
})

从源码看(SelectQueryBuilder.ts),"sql-null" 处理分两条路径:

  • 目标属性是普通列时,条件直接生成为 `${aliasPath} IS NULL`
  • 目标属性是关系时(如 where: { category: null }),生成 `${alias}.${propertyPath} IS NULL`,见 关系分支的实现

此外,写路径(update/delete 等)的实现 OrmUtils.normalizeWhereCriteria 在遇到 "sql-null" 时会把 null 值直接替换为 IsNull() 操作符,效果一致。

'throw' —— 抛错(默认)

const dataSource = new DataSource({
    // ... 其他选项
    invalidWhereValuesBehavior: {
        null: "throw",
    },
})

// 抛出错误
const posts = await repository.find({
    where: {
        text: null,
    },
})
// Error: Null value encountered in property 'text' of a where condition.
// To match with SQL NULL, the IsNull() operator must be used.
// Set 'invalidWhereValuesBehavior.null' to 'ignore' or 'sql-null' in data source options to skip or handle null values.

undefined 的两种行为

'ignore' —— 跳过该属性

const dataSource = new DataSource({
    // ... 其他选项
    invalidWhereValuesBehavior: {
        undefined: "ignore",
    },
})

// 返回所有 post:text 属性被跳过
const posts = await repository.find({
    where: {
        text: undefined,
    },
})

注意:这只针对显式赋值undefined;属性本身被省略(不在对象里)是另一种情况,本来就参与不了过滤,二者不要混淆。

'throw' —— 抛错(默认)

const dataSource = new DataSource({
    // ... 其他选项
    invalidWhereValuesBehavior: {
        undefined: "throw",
    },
})

// 抛出错误
const posts = await repository.find({
    where: {
        text: undefined,
    },
})
// Error: Undefined value encountered in property 'text' of a where condition.
// Set 'invalidWhereValuesBehavior.undefined' to 'ignore' in data source options to skip properties with undefined values.

从源码结构看,buildWhere 实现 还覆盖了一个嵌套场景:当关系子条件对象的所有属性都是 undefined 时(例如 { category: { name: undefined } }),若 undefined 行为是 "throw" 也会抛错,避免生成无谓的 join 且误判为有效条件。

两种选项组合使用

两个维度可以独立配置,以获得精细化控制:

const dataSource = new DataSource({
    // ... 其他选项
    invalidWhereValuesBehavior: {
        null: "sql-null",
        undefined: "throw",
    },
})

这一组合的效果是:

  1. where 条件中的 JavaScript null 被转换为 SQL NULLIS NULL);
  2. 遇到任何 undefined 立即抛错;
  3. 未在 where 中提供的属性依然被忽略。

它适合这样的诉求:显式搜索数据库中的 NULL 值,同时把 undefined(通常意味着变量未赋值的编程错误)当作需要立即暴露的 bug。

哪些操作会受该配置影响

invalidWhereValuesBehavior 作用于高层 API,不作用于 QueryBuilder 的直接 .where() 调用。

Find 系列操作

// Repository.find() / findOne() / findBy() / findOneBy()
await repository.find({ where: { text: null } }) // 受 invalidWhereValuesBehavior 控制

// EntityManager.find() / findOne() / findBy() / findOneBy()
await manager.find(Post, { where: { text: null } }) // 受 invalidWhereValuesBehavior 控制

Repository 与 EntityManager 的写操作

// Repository.update()
await repository.update({ text: null }, { title: "Updated" }) // 受 invalidWhereValuesBehavior 控制

// Repository.delete()
await repository.delete({ text: null }) // 受 invalidWhereValuesBehavior 控制

// EntityManager.update()
await manager.update(Post, { text: null }, { title: "Updated" }) // 受 invalidWhereValuesBehavior 控制

// EntityManager.delete()
await manager.delete(Post, { text: null }) // 受 invalidWhereValuesBehavior 控制

// EntityManager.softDelete()
await manager.softDelete(Post, { text: null }) // 受 invalidWhereValuesBehavior 控制

写路径的实现与读路径不同:它不直接走 buildWhere,而是先经过 OrmUtils.normalizeWhereCriteria 归一化。该函数的行为要点(由其源码注释与实现确认):

  • 顶层数组(OR 列表)逐元素归一化;
  • 只对纯对象(plain object)逐键处理:null/undefined 按配置抛错、跳过或转为 IsNull();嵌套纯对象递归处理,全被跳过的空嵌套会被移除;
  • 其他值——实体类实例、FindOperatorDateBuffer、原始 id——原样透传、不做校验。因此实体实例中可空列为 null 时会渲染成 col = NULL(匹配不到任何行),而不会抛错或转换;如需 null 语义,请传带 IsNull() 的纯对象(如 { text: IsNull() })。
  • 未配置行为时同样默认 "throw",与读路径保持一致。

归一化之后的校验由 EntityManager.normalizeAndValidateWhereCriteria 完成,并带来一条重要规则:

空条件会被拒绝。 updatedeletesoftDeleterestore 要求非空条件——空条件会渲染成 WHERE 1=1,波及每一行。由于 "ignore" 会剥离 null/undefined 属性,当条件对象的所有键都被剥离后就会变成空对象,此时操作被拒绝而不是执行一次无过滤的写:

const dataSource = new DataSource({
    // ... 其他选项
    invalidWhereValuesBehavior: { null: "ignore", undefined: "ignore" },
})

// { text: null } 剥离后变成 {} -> 被拒绝,表不会被清空
await manager.delete(Post, { text: null })
// Error: Empty criteria(s) are not allowed for the delete method.

源码中 rendersNoPredicate 判定还覆盖了更多形态:空对象 {}、空数组 []、OR 数组中的空分支或裸原始值(如 [1, { id: 2 }] 中的 1,因为它不会生成谓词)。如果你确实要影响所有行,应显式使用专用的 updateAll() / deleteAll() 方法。

QueryBuilder.setFindOptions 走 find-options 路径

// setFindOptions 走 find-options 路径,因此受该配置控制
await dataSource
    .createQueryBuilder(Post, "post")
    .setFindOptions({ where: { text: null } }) // 受 invalidWhereValuesBehavior 控制
    .getMany()

不受影响:QueryBuilder 的 .where()

QueryBuilder 的 .where().andWhere().orWhere() 是底层 API,invalidWhereValuesBehavior 影响,null/undefined 原样透传:

// 此处不遵循 invalidWhereValuesBehavior —— null 原样透传
await dataSource
    .createQueryBuilder()
    .update(Post)
    .set({ title: "Updated" })
    .where({ text: null })
    .execute()

QueryBuilder .where() 中 null / undefined 的实际行为

正因为 QueryBuilder 不做校验和转换,理解其行为对避免“查不到数据”的坑很关键。

null 传入对象式 .where()

null 会生成针对 NULL 的等值比较:

await dataSource
    .createQueryBuilder(Post, "post")
    .where({ text: null })
    .getMany()
// 生成: WHERE post.text = NULL

而 SQL 中 column = NULL 恒为假,这条查询必然返回 0 行,通常不是你想要的。要匹配 NULL 请使用 IsNull()

import { IsNull } from "typeorm"

await dataSource
    .createQueryBuilder(Post, "post")
    .where({ text: IsNull() })
    .getMany()
// 生成: WHERE post.text IS NULL

或者直接用字符串条件:

await dataSource
    .createQueryBuilder(Post, "post")
    .where("post.text IS NULL")
    .getMany()

undefined 传入 .where()

同样生成 WHERE column = NULL,恒为假:

await dataSource
    .createQueryBuilder(Post, "post")
    .where({ text: undefined })
    .getMany()
// 生成: WHERE post.text = NULL
// 返回: 0 行

行为对照表

高层 API(find/repository/manager) QueryBuilder .where()
null + "ignore" 属性被跳过,无过滤 WHERE col = NULL —— 0 行结果
null + "sql-null" WHERE col IS NULL WHERE col = NULL —— 0 行结果
null + "throw"(默认) 抛出错误 WHERE col = NULL —— 0 行结果
undefined + "ignore" 属性被跳过,无过滤 WHERE col = NULL —— 0 行结果
undefined + "throw"(默认) 抛出错误 WHERE col = NULL —— 0 行结果
IsNull() WHERE col IS NULL WHERE col IS NULL

无论使用哪一层 API,只要想匹配数据库中的 NULL 值,都应该使用 IsNull() 操作符——它在高层和 QueryBuilder 上下文中都能正确工作。

仓库中的功能测试佐证

仓库在 test/functional/null-undefined-handling/ 目录下为这一机制提供了完整的测试覆盖,可作为行为验证与回归参考:

  • find-options.test.ts:验证 repository.findsetFindOptions 在默认行为下对 null/undefined 抛错,以及 ignoresql-null 行为下的实际查询结果;
  • query-builders.test.ts:验证 EntityManager.update()delete()softDelete() 等写操作在 null/undefined 下遵循 invalidWhereValuesBehavior(例如配置 null: "throw"update(Post, { text: null }, ...) 抛出包含 “Null value encountered” 的 TypeORMError);
  • parameter-types.test.ts:覆盖实体实例、DateBuffer 等非纯对象条件的透传边界。

测试中的 Post/Category 实体与 Post 实体定义 位于同目录 entity/ 子目录,可配合阅读。

小结

  • invalidWhereValuesBehavior 是数据源级配置,独立控制 null"ignore" / "sql-null" / "throw")与 undefined"ignore" / "throw"),未配置时两者默认 "throw"
  • 它覆盖 find 系列、Repository 以及 EntityManager 的 update / delete / softDelete / restore;写路径经 OrmUtils.normalizeWhereCriteria 归一化,且空条件会被拒绝以防误伤全表;
  • QueryBuilder 的 .where() 系列不受影响,null/undefined 会渲染成恒假的 col = NULL
  • 需要匹配 NULL 值时,无论哪一层 API,IsNull() 都是唯一可靠的写法。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384