MikroORM 属性校验(Property Validation)完全指南:必填属性、OptionalProps、Opt 与运行时校验
发布时间:2026/9/26 6:42:54 作者:尧图编辑部 阅读量:1,286
完全指南:必填属性、OptionalProps、Opt 与运行时校验)
后端【免费下载链接】mikro-ormTypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.项目地址https://gitcode.com/gh_mirrors/mi/mikro-orm点击查看免费下载属性校验是 MikroORM 中负责“实体属性是否必须提供”的一套机制它在 TypeScript 类型层面编译期与运行时层面flush 阶段双轨工作一方面通过RequiredEntityData、OptionalProps、Opt等类型约束让em.create()/em.assign()在编译期就提示必填项另一方面在flush()执行 INSERT 前由 ChangeSetPersister 逐属性校验缺失值并抛出ValidationError。本篇指南将完整讲解必填/可空属性的声明方式、带默认值属性的类型提示问题、OptionalProps符号与Opt类型的使用场景以及如何通过validateRequired: false关闭运行时校验帮助你写出类型安全且运行时行为可预期的实体定义。必填属性与可空属性在 MikroORM 中实体属性默认被视为必填required。这意味着每个属性都会经历两层校验类型层面编译期em.create()等 API 的参数类型会基于实体元数据推导必填属性必须出现在入参中否则 TypeScript 直接报错运行时层面在flush()发出 INSERT 之前ORM 会检查实体内必填属性是否有值缺失则抛出ValidationError。要让一个属性成为可空的需要在类型层面和元数据层面同时标记。除非你使用ts-morph做元数据反射否则两者缺一不可Property({ nullable: true }) name?: string;如果你希望属性类型是显式的null联合即允许赋null还应提供属性初始化器Property({ type: string, nullable: true }) name: string | null null;注意nullable: true在元数据上的影响不止于校验它同时决定数据库列的 NULL 约束、TypeScript 推导出的可空性以及序列化时的行为。从 typings.ts 的NullifyP, V类型可以看出nullable: true会在推导结果上追加| null。必填校验的运行时实现运行时校验发生在flush()期间、INSERT 查询发出之前。核心实现位于 ChangeSetPersister.ts 的validateRequired方法它遍历实体元数据的所有属性仅在满足以下全部条件时才认为属性“需要值”并执行空值检查属性不是nullable不是自增主键autoincrement没有default/defaultRaw/onCreate值不是生成列generated不是嵌入式属性embedded不是ONE_TO_MANY/MANY_TO_MANY这类集合关系不是全由公式、非持久化或主键组成的嵌入式目标不是继承体系中的判别列discriminatorColumn类型不是ObjectIdpersist ! false。从 errors.ts 可以看到抛出的异常信息非常具体Value for Author2.email is required, undefined found并附带整个实体的inspect快照方便快速定位是哪个实体的哪个属性缺失。校验只在ChangeSetType.CREATE新建场景触发更新UPDATE不会要求全部必填属性都有值if (changeSet.type ChangeSetType.CREATE this.#config.get(validateRequired)) { this.validateRequired(changeSet.entity); }带默认值属性的类型处理运行时校验对“有默认值的必填属性”没有意见——只要默认值存在flush 时该属性一定有值校验自然通过。真正的难点在类型层面属性在 TS 中被定义为必填没有?但因为有默认值它实际上又是可选的调用方可以不传。直接em.create()时 TypeScript 会要求你显式传入该属性这并不理想。MikroORM 提供三种解法方案一把属性定义为可选不推荐Property({ default: 1 }) level?: number 1;这样做虽然类型通过了但副作用是允许外部把该属性显式“置空/取消”unset这可能并不是你想要的行为。方案二使用OptionalProps符号推荐OptionalProps是 MikroORM 导出的一个Symbol定义见 typings.ts专门用于解决“属性有默认值但希望类型上可选”的问题。它的用法是在实体上声明一个可选属性[OptionalProps]?: propA | propB | ...值的类型是你要标记为可选的所有属性名的联合类型import { OptionalProps, Entity, PrimaryKey, Property } from mikro-orm/core; Entity() class User { // getters 也会遇到同样的问题需要一并声明 [OptionalProps]?: foo | bar | fooBar; PrimaryKey() id!: number; Property({ default: 1 }) foo: number 1; Property({ default: 2 }) bar: number 2; Property({ persist: false }) get fooBar() { return foo bar; } }从源码看ExplicitlyOptionalPropsT会同时收集[OptionalProps]声明的键以及所有类型为Opt的属性键随后RequiredEntityDataT在推导em.create()入参时会把这些键归入“可选”分支从而在编译期放行“不传默认值属性”的调用。注意注释中强调getter 属性如示例中的fooBar同样需要列入OptionalProps因为它们在构造实体时不可能由调用方提供。方案三在基类中用泛型扩展 OptionalProps当你把公共的默认值属性下沉到自己的 BaseEntity 时需要借助泛型让子类可以继续追加自己的可选属性Entity() class MyBaseEntityEntity extends object, Optional extends keyof Entity never { [OptionalProps]?: foo | bar | Optional; PrimaryKey() id!: number; Property({ default: 1 }) foo: number 1; Property({ default: 2 }) bar: number 2; } Entity() class User extends MyBaseEntityUser, baz { Property({ default: 3 }) baz: number 3; }这里Optional extends keyof Entity never是关键子类实例化时把自己的类型User作为第一个泛型参数传入并把新增的可选属性名baz作为第二个参数从而把baz并入基类已经声明的foo | bar联合中。方案四Opt类型Opt是另一种更轻量的选择它是一个品牌类型branded type定义于 typings.ts声明为OptT T Opt.Brand。它有两种等价的用法泛型形式middleName: Optstring ;交叉类型形式middleName: string Opt ;两种写法效果相同且可以与OptionalProps符号方案组合使用import { Opt, Entity, PrimaryKey, Property } from mikro-orm/core; Entity() class User { PrimaryKey() id!: number; Property() firstName!: string; Property() middleName: string Opt ; Property() lastName!: string; Property({ persist: false }) get fullName(): Optstring { return ${this.firstName} ${this.middleName} ${this.lastName}; } }Opt尤其适合 getter、persist: false派生属性或无法用[OptionalProps]清晰表达的场景而[OptionalProps]符号则更适合集中声明“一组”默认值属性。二者在类型推导路径上殊途同归——都会进入ProbablyOptionalPropsT的可选判定。运行时校验的开关validateRequired如果你出于某些原因不希望 ORM 在缺失必填属性时抛错可以关闭运行时校验// MikroORM.init 配置 const orm await MikroORM.init({ entities: [...], validateRequired: false, }); // 或在运行时切换 orm.config.set(validateRequired, false);该配置项默认值为true见 Configuration.ts 的默认配置属于全局配置。关闭校验后缺失必填属性的错误将从 ORM 层转移到数据库层——这一点有明确的测试佐证。在 EntityManager.postgre.test.ts 的required fields validation测试中默认开启时flush()抛出Value for Author2.email is required, undefined found关闭validateRequired后同样操作抛出的是数据库的null value in column email of relation author2 violates not-null constraint即NotNullConstraintViolationException。这意味着关闭校验并不能“绕过”必填约束只是把检查时点与报错形态从 ORM 的友好提示换成了数据库的约束异常。生产环境建议保持默认开启以获得更快、更可读的失败反馈。关于可选属性与元数据反射的注意事项定义实体时可选属性需要特别小心这与元数据提供器metadata provider的能力边界有关使用默认的reflect-metadata提供器时属性类型只能通过?后缀可选标记推断。如果你使用联合类型如string | nullreflect-metadata无法解析这种复杂类型此时你必须显式声明类型例如Property({ type: string, nullable: true })这个问题在使用ts-morph提供器时不存在因为它直接读取 TypeScript AST可以理解string | null这类联合类型。这正是本文开头示例中Property({ type: string, nullable: true })需要显式给出type的原因。若你的项目大量使用nullable联合类型且不愿到处手写类型可以考虑切换到ts-morph提供器配置项为metadataProvider: TsMorphMetadataProvider。综合示例完整的必填/可选属性实体将以上要点整合一个兼顾类型安全与运行时行为的实体大致长这样import { Entity, PrimaryKey, Property, OptionalProps, Opt } from mikro-orm/core; Entity() class Account { [OptionalProps]?: createdAt | updatedAt; PrimaryKey() id!: number; Property() email!: string; // 必填类型与运行时双重校验 Property({ nullable: true }) displayName?: string; // 可空 Property({ type: string, nullable: true }) bio: string | null null; // 显式 null 联合 初始化器 Property({ default: 1 }) level: number 1; // 有默认值通过 OptionalProps 标记为类型可选 Property() get label(): Optstring { return ${this.email} (${this.level}); } Property({ onCreate: () new Date() }) createdAt: Date new Date(); // 数据库/ORM 自动生成 Property({ onUpdate: () new Date() }) updatedAt: Date new Date(); }关键设计点回顾必填不加nullable、不给默认值、不列入OptionalProps的属性在em.create()类型检查与 flush 运行时检查中都会被强制要求可空必须同时满足类型层面?或| null 初始化器与元数据层面nullable: true默认值运行时校验自动放行类型层面用[OptionalProps]或Opt声明为可选关闭校验仅当确实需要把错误下推给数据库时才设置validateRequired: false。小结属性校验是 MikroORM“类型安全优先”设计哲学的典型体现编译期由 typings.ts 中的RequiredEntityData、OptionalProps、Opt等类型负责运行时由 ChangeSetPersister.ts 在 INSERT 前兜底配置项validateRequired默认true定义于 Configuration.ts控制总开关。正确使用nullable: true、OptionalProps与Opt可以让实体定义在“允许省略”与“防止遗漏”之间取得最佳平衡而理解reflect-metadata与ts-morph提供器的差异则能避免在可空联合类型上踩坑。相关行为均有测试覆盖例如 EntityManager.postgre.test.ts 演示了开关校验前后的报错差异可作为进一步研究该机制的入口。赞分享后端【免费下载链接】mikro-ormTypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.项目地址https://gitcode.com/gh_mirrors/mi/mikro-orm点击查看免费下载相关推荐WeChatMsg本地导出微信聊天记录并生成年度聊天报告的完整指南WeChatMsg本地导出微信聊天记录并生成年度聊天报告的完整指南 换机前想把微信聊天记录导出到电脑上WeChatMsg 帮你把对话变成可长期保存的文件还后端MikroORM 实体属性校验机制详解类型级可选标记、运行时 validateRequired 与严格类型验证MikroORM 实体属性校验机制详解类型级可选标记、运行时 validateRequired 与严格类型验证 本篇技术指南基于 MikroORM 官方文档后端Gradle 工作校验Work Validation机制全解静态校验与运行时校验的源码级剖析Gradle 工作校验Work Validation机制全解静态校验与运行时校验的源码级剖析 本文以 Work Validation.md 为核心骨架结构建工具开发工具上一篇CherryPy测试策略单元测试、集成测试与性能测试下一篇Rust机器学习生态系统项目教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考