TypeORM泛型基类使用FindOperator报'_type'未知列错误如何解决
TypeORM 0.2.x 泛型基类FindOperator失效问题解决方案
环境信息
- 运行时:Node v14.19.3
- 依赖版本:
@nestjs/typeorm@~8.0.3、typeorm@~0.2.45
问题现象
封装通用CRUD泛型基类CrudService<T>时,在基类内部直接使用Between()、Like()等FindOperator编写查询,会生成错误SQL,抛出Unknown column '_type' in 'where clause'错误;完全相同的查询逻辑移动到继承该基类的子类服务中即可正常运行,两种场景使用的是同一个Repository实例。
异常基类代码
export class CrudService<T> { protected repository: Repository<T>; constructor(repository: Repository<T>) { this.repository = repository; } // 其余通用CRUD方法已省略 async find() { return this.repository.find({ where: { created: Between("2022-06-21T14:18:00.000Z", "2022-06-21T14:19:00.000Z") } }); } }
错误输出
基类调用find方法时生成的错误SQL将FindOperator作为普通值做等值比较:
SELECT ... FROM `carts` `CartEntity` WHERE `CartEntity`.`created` = ?
绑定参数为FindOperator的内部属性对象:
{ "_type":"between", "_value":[ "2022-06-21T14:18:00.000Z", "2022-06-21T14:19:00.000Z" ], "_useParameter":true, "_multipleParameters":true }
正常子类实现
相同逻辑写在子类中可正常生成BETWEEN查询语句:
@Injectable() export class CartsService extends CrudService<CartEntity> { constructor( @InjectRepository(CartsRepository) repository: CartsRepository ) { super(repository); } // 其余方法已省略 async find() { return this.repository.find({ where: { created: Between("2022-06-21T14:18:00.000Z", "2022-06-21T14:19:00.000Z") } }); } }
根因
这是TypeORM 0.2.x版本的已知设计缺陷:
框架判断where条件中的值是否为FindOperator类型,依赖instanceof FindOperator校验。JS中instanceof判断的核心是校验对象原型链上是否存在对应构造函数的prototype,如果项目中存在多份TypeORM模块副本,会出现两边构造函数引用不一致的问题:
- 基类文件中导入的
Between等操作符来自A份TypeORM包,生成的FindOperator实例原型指向A包的FindOperator.prototype - Nest注入的Repository实例由B份TypeORM包创建,校验时使用的是B包的
FindOperator构造函数
两边引用不匹配导致instanceof校验返回false,框架会将FindOperator实例当做普通对象处理,直接序列化做等值比较。
子类中代码正常运行的原因是:子类文件与Nest TypeORM模块注册逻辑在同一个依赖解析上下文,导入的操作符和Repository实例来自同一份TypeORM包,instanceof校验可以通过。
解决方案
可根据项目实际情况选择以下任意一种方案修复:
- 方案1:消除TypeORM多副本
执行包管理工具的依赖查询命令检查项目中存在的TypeORM版本:- npm用户执行
npm ls typeorm - yarn用户执行
yarn why typeorm - pnpm用户执行
pnpm ls typeorm
将所有依赖引用的TypeORM版本对齐到0.2.45,删除lock文件和node_modules目录后重新安装依赖,保证整个项目中只存在一份TypeORM包,所有位置导入的FindOperator引用保持一致。
- npm用户执行
- 方案2:使用QueryBuilder替代find选项写法
QueryBuilder不会触发FindOperator的instanceof校验,不需要修改依赖即可直接修复问题,适合不想调整依赖结构的场景:async find() { return this.repository.createQueryBuilder('entity') .where('entity.created BETWEEN :start AND :end', { start: "2022-06-21T14:18:00.000Z", end: "2022-06-21T14:19:00.000Z" }) .getMany(); } - 方案3:配置TS路径映射强制单例
在项目tsconfig.json中添加路径映射规则,强制所有文件导入TypeORM时都解析到项目根目录下的同一个包入口:
Monorepo项目注意调整路径指向工作区根目录的TypeORM包,避免子包独立安装依赖产生副本。{ "compilerOptions": { "baseUrl": "./", "paths": { "typeorm": ["node_modules/typeorm"] } } } - 方案4:升级依赖版本
TypeORM 0.3.x版本重构了FindOperator的实现逻辑,不再依赖instanceof做类型校验,从根源上消除了该问题。对应需要将@nestjs/typeorm升级到9.x及以上版本适配TypeORM 0.3.x。
内容的提问来源于stack exchange,提问作者Maxime Asselin
相关产品推荐
相关产品推荐

