
后端【免费下载链接】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点击查看免费下载defineEntity是 MikroORM 7.x 提供的推荐实体定义方式它让你完全摆脱装饰器以纯 TypeScript 对象字面量 链式属性构建器p声明实体并借助类型推断自动生成实体类型。本文以 define-entity.md 为主线结合 defineEntity 源码 与仓库内测试用例带你掌握defineEntity class、纯defineEntity、属性复用、Hook 注册与底层EntitySchema的全部细节读完后可以直接在项目里落地这套无装饰器的建模方案。一、为什么需要defineEntityMikroORM 支持三种实体定义方式装饰器Entity()等、EntitySchema手写元数据、以及defineEntity。defineEntity是官方推荐的无装饰器方案它构建在EntitySchema之上核心优势在于利用 TypeScript 的类型推断自动生成实体类型——你不需要像手写EntitySchema那样同时维护接口IBook与元数据两份内容定义一次属性实体类型随之而来。从源码看defineEntity的返回类型是EntitySchemaWithMetadefineEntity 函数签名它本质上是EntitySchema实例但携带了从属性定义中推断出的完整类型信息因此既可以被 ORM 发现MikroORM.init({ entities: [...] })也能在类型层面直接作为实体类使用。二、快速上手第一个defineEntity从mikro-orm/core导入defineEntity与属性构建器p声明实体import { defineEntity, p } from mikro-orm/core; const BookSchema defineEntity({ name: Book, properties: { id: p.integer().primary(), title: p.string(), author: () p.manyToOne(Author).inversedBy(books), tags: () p.manyToMany(BookTag).inversedBy(books).fixedOrder(), }, }); export class Book extends BookSchema.class {} BookSchema.setClass(Book);几点说明p是defineEntity.properties的别名两者完全等价源码中defineEntity.properties propertyBuilders; export { propertyBuilders as p };见 defineEntity.ts#L1592-L1594。关系属性使用函数返回构建器() p.manyToOne(Author)这是为了打破循环引用——Author和Book互相引用时惰性求值保证定义顺序无关。defineEntity返回的 schema 自带一个自动生成的匿名类BookSchema.class你可以直接继承它得到实体类然后通过setClass()注册回去。三、defineEntity class模式推荐3.1 为什么推荐扩展类并setClass当实体需要自定义方法、更干净的 hover 类型或更好的性能时官方推荐在自动生成类的基础上扩展并注册干净的 hover 类型鼠标悬停在Book变量上显示的是Book而不是一长串带泛型与 symbol 的交叉类型更好的性能真实命名的类比纯方案中动态生成的匿名类更高效自定义方法可以直接在实体实例上加领域逻辑无需任何 workaround无属性重复属性只在 schema 中定义一次自动被类继承。const AuthorSchema defineEntity({ name: Author, properties: { id: p.integer().primary(), firstName: p.string(), lastName: p.string(), books: () p.oneToMany(Book).mappedBy(author), }, }); class Author extends AuthorSchema.class { fullName() { return ${this.firstName} ${this.lastName}; } } AuthorSchema.setClass(Author); // Usage: const author em.create(Author, { firstName: John, lastName: Doe }); console.log(author.fullName()); // John Doe重要setClass()必须在 ORM 发现实体之前即MikroORM.init()之前调用。请在模块加载阶段、定义扩展类之后立即调用。仓库中的 define-entity-setclass.sqlite.test.ts 完整演示了这一模式先defineEntity两个 schema再扩展自动生成类添加fullName()方法最后在beforeAll里MikroORM.init({ entities: [AuthorSchema, BookSchema] })。setClass的内部行为见 EntitySchema.setClass它会更新实体的class、prototype、className重新推导构造参数并把自定义类注册进EntitySchema.REGISTRY这是 ORM 发现阶段按类查找 schema 的登记表同时自动检测父类只有父类不是当前实体的自动生成类时才将其设为extends。3.2 纯defineEntity不扩展类简单实体也可以不扩展类写法更紧凑但 hover 时会出现复杂的计算类型。此时用InferEntity提取实体类型import { type InferEntity, defineEntity, p } from mikro-orm/core; export const Book defineEntity({ name: Book, properties: { id: p.integer().primary(), title: p.string(), author: () p.manyToOne(Author).inversedBy(books), tags: () p.manyToMany(BookTag).inversedBy(books).fixedOrder(), }, }); // Use InferEntity to extract the entity type export type IBook InferEntitytypeof Book;无类时创建实体实例要用em.create()它会在内部生成类的实例const book em.create(Book, { title: My Book, author }); await em.flush();四、复用公共属性多实体共享基础字段有两种方式各有适用场景。4.1 组合式复用共享属性对象最简单的方式把共享属性对象展开spread进每个实体的propertiesconst p defineEntity.properties; const baseProperties { id: p.integer().primary(), createdAt: p.datetime().onCreate(() new Date()), updatedAt: p.datetime() .onCreate(() new Date()) .onUpdate(() new Date()), }; const BookSchema defineEntity({ name: Book, properties: { ...baseProperties, title: p.string(), author: () p.manyToOne(Author), }, }); export class Book extends BookSchema.class {} BookSchema.setClass(Book);这里的onCreate/onUpdate是属性级生命周期回调分别在 flush 创建/更新时自动执行源码注释见 defineEntity.ts#L480-L499。4.2 通过extends 属性初始化器使用defineEntity class模式时自动生成的子类在 JavaScript 层面继承父类因此父类上定义的属性初始化器如id v4()、createdAt new Date()会在new构造子实体时自动执行const BaseSchema defineEntity({ name: BaseEntity, abstract: true, properties: { id: p.string().primary(), createdAt: p.datetime(), updatedAt: p.datetime(), }, }); export class Base extends BaseSchema.class { id v4(); createdAt new Date(); updatedAt new Date(); } BaseSchema.setClass(Base); const UserSchema defineEntity({ name: User, extends: BaseSchema, properties: { email: p.string().unique(), name: p.string(), }, }); export class User extends UserSchema.class { name ; } UserSchema.setClass(User); // id, createdAt, updatedAt are initialized from Bases property initializers const user new User(); console.log(user.id); // a UUID string console.log(user.createdAt); // current Date该方式适合需要构造器级默认值、且不依赖EntityManager上下文即直接用new的场景。如果只需要持久化时的默认值用onCreate钩子即可参见 继承映射。4.3 继承基类方法运行时 vs 类型层面基类上声明的方法运行时一定被继承——自动生成的子类 extends 了基类所以子实例instanceof基类为真也能调用其方法。但类型层面extends: BaseSchema只携带已映射的属性TypeScript 看不到你之后通过setClass挂到 schema 上的方法。要让子实体类型上能看到这些方法把extends指向基类而非 schemaexport class Base extends BaseSchema.class { id v4(); createdAt new Date(); updatedAt new Date(); wasUpdated(): boolean { return this.updatedAt this.createdAt; } } BaseSchema.setClass(Base); const UserSchema defineEntity({ name: User, extends: Base, // the class, not BaseSchema — exposes wasUpdated() on the child type properties: { email: p.string().unique(), }, }); export class User extends UserSchema.class { describe() { return this.wasUpdated() ? ${this.email} (edited) : this.email; } } UserSchema.setClass(User);两种写法运行时行为完全一致传类只是让 TypeScript 传播继承的方法签名。若基类只贡献列extends: BaseSchema仍然继承全部映射属性是正确选择。五、属性类型内置类型、自定义类型与链式方法5.1 内置类型与自定义类型p覆盖 MikroORM 全部内置类型完整列表见 内置类型文档。要使用自定义类型用p.type()const properties { string: p.string(), float: p.float(), boolean: p.boolean(), json: p.json{ foo: string; bar: number }().nullable(), stringArray: p.type(ArrayTypestring).nullable(), numericArray: p.type(new ArrayType(i i)).nullable(), point: p.type(PointType).nullable(), };从 propertyBuilders 实现 看构建器工厂包括bigint、array、decimal、json、string、text、formula、datetime、time、type、enum、embedded、manyToMany、manyToOne、oneToMany、oneToOne。其中bigint/decimal可传模式参数bigint | number | stringdatetime/time可传长度array可自定义toJsValue/toDbValue转换函数。5.2 字符串规范化p.string()与p.text()额外支持trim()、lowercase()、uppercase()三个字符串规范化方法见 StringPropertyOptionsBuilder它们会生成带选项的StringType。仓库测试 defineEntity.test.ts#L73-L120 验证了这些能力及其类型推断结果const Foo defineEntity({ name: Foo, properties: { plain: p.string(), normalized: p.string().trim().uppercase().length(50), nullable: p.text().lowercase().nullable(), optional: p.string().trim().onCreate(() ), aliases: p.string().trim().lowercase().array(), }, });5.3 通用链式方法与 kind 约束PropertyChain接口定义提供了大量可链式调用的方法例如标量属性nullable()、strictNullable()、primary()、hidden()、autoincrement()、persist(false)、version()、lazy()、fieldName()、onCreate()、onUpdate()、default()、defaultRaw()、formula()、generated()、check()、columnType()、length()、precision()、scale()、unsigned()、returning()、index()、unique()、serializedPrimaryKey()、serializer()、groups()、comment()、collation()、setter()/getter()等关系属性cascade()、eager()、strategy()、filters()、mappedBy()、inversedBy()、owner()、mapToPk()、orphanRemoval()、orderBy()、where()、joinColumn()、deleteRule()、updateRule()、deferMode()、targetKey()、through()以及 m:n 专属的pivotTable()、pivotEntity()、fixedOrder()、fixedOrderColumn()嵌入与枚举embedded的prefix()/prefixMode()/object()enum的nativeEnumName()、.array()。源码通过HasKind条件类型做kind 约束关系专属方法在标量/嵌入/枚举属性上调用时返回never从编译期杜绝误用defineEntity.ts#L84-L91。5.4 关系修饰符.ref()与.lazyRef()对于m:1/1:1关系可以选用编译期的填充状态安全机制.ref()把运行时值包装进Reference暴露.$/.get()/.load()参见 Ref 包装器.lazyRef()纯类型层面的标记——运行时保持普通实体不包装但 TypeScript 在Loaded收窄之前会隐藏非主键访问参见 LazyRef 类型。const BookSchema defineEntity({ name: Book, properties: { id: p.integer().primary(), author: () p.manyToOne(AuthorSchema).ref(), // RefAuthor publisher: () p.manyToOne(PublisherSchema).lazyRef(), // LazyRefPublisher }, });源码注释明确说明lazyRef()是purely type-level — no runtime metadata changedefineEntity.ts#L623-L636且与.ref()、.mapToPk()互斥互斥组合会在编译期被拒绝。六、MongoDB 示例MongoDB 实体同样支持两种写法_id用p.type(ObjectId).primary()序列化主键用serializedPrimaryKey()defineEntity classconst BookTagSchema defineEntity({ name: BookTag, properties: { _id: p.type(ObjectId).primary(), id: p.string().serializedPrimaryKey(), name: p.string(), books: () p.manyToMany(Book).mappedBy(tags), }, }); export class BookTag extends BookTagSchema.class {} BookTagSchema.setClass(BookTag);纯 defineEntityexport const BookTag defineEntity({ name: BookTag, properties: { _id: p.type(ObjectId).primary(), id: p.string().serializedPrimaryKey(), name: p.string(), books: () p.manyToMany(Book).mappedBy(tags), }, }); export type IBookTag InferEntitytypeof BookTag;七、生命周期 Hook 注册Hook 有两种注册方式都接受普通函数、箭头函数与 async 函数实体实例通过args.entity获取完整事件列表与EventArgs详见 事件与生命周期钩子hooks属性在defineEntity调用中直接传入 handler 对象数组addHook方法实体定义之后再注册。推荐用addHook因为内联hooks属性时实体类型尚不可知args.entity会被推断为any手动标注EventArgsBookTag又会造成循环引用。defineEntity class 模式完整类型安全const BookTagSchema defineEntity({ name: BookTag, properties: { _id: p.type(ObjectId).primary(), id: p.string().serializedPrimaryKey(), name: p.string(), version: p.integer(), books: () p.manyToMany(Book).mappedBy(tags), }, }); export class BookTag extends BookTagSchema.class {} BookTagSchema.setClass(BookTag); BookTagSchema.addHook(beforeCreate, (args: EventArgsBookTag) { args.entity.version 1; }); BookTagSchema.addHook(beforeUpdate, (args: EventArgsBookTag) { args.entity.version; });纯 defineEntity 模式export const BookTag defineEntity({ name: BookTag, properties: { _id: p.type(ObjectId).primary(), id: p.string().serializedPrimaryKey(), name: p.string(), version: p.integer(), books: () p.manyToMany(Book).mappedBy(tags), }, }); export type IBookTag InferEntitytypeof BookTag; BookTag.addHook(beforeCreate, (args: EventArgsIBookTag) { args.entity.version 1; }); BookTag.addHook(beforeUpdate, (args: EventArgsIBookTag) { args.entity.version; });addHook的底层实现见 EntitySchema.addHook按事件名把 handler 追加进_meta.hooks[event]数组。hooks属性支持的完整事件集合DefineEntityHooks为onInit、onLoad、beforeCreate、afterCreate、beforeUpdate、afterUpdate、beforeUpsert、afterUpsert、beforeDelete、afterDeletedefineEntity.ts#L1596-L1610。八、底层 API直接使用EntitySchemadefineEntity返回的就是EntitySchema实例——同一个类你也可以直接实例化。对大多数场景defineEntity更省事带完整类型推断但EntitySchema仍可用于高级用法或纯 JavaScript 项目。8.1 经典写法接口 schemaexport interface IBook { title: string; author: Author; publisher: Publisher; tags: CollectionBookTag; } export const BookSchema new EntitySchemaIBook({ name: Book, extends: CustomBaseEntitySchema, properties: { title: { type: string }, author: { kind: m:1, entity: () Author, inversedBy: books }, publisher: { kind: m:1, entity: () Publisher, inversedBy: books }, tags: { kind: m:n, entity: () BookTag, inversedBy: books, fixedOrder: true }, }, });8.2 传入 class 的写法用class选项替代nameexport class Author extends CustomBaseEntity { name: string; email: string; constructor(name: string, email: string) { super(); this.name name; this.email email; } } export const AuthorSchema new EntitySchema({ class: Author, extends: CustomBaseEntitySchema, properties: { name: { type: string }, email: { type: string, unique: true }, }, });8.3 配置参考Configuration ReferenceEntitySchema的参数要求name或class二选一使用class时extends会自动推断。其余参数name: string; class: ConstructorT; extends: string; tableName: string; // alias for collection: string properties: { [K in keyof T string]: EntityPropertyT[K] }; indexes: { properties: string | string[]; name?: string; type?: string }[]; uniques: { properties: string | string[]; name?: string }[]; repository: () ConstructorEntityRepositoryT; hooks: PartialRecordkeyof typeof EventType, ((string keyof T) | NonNullableEventSubscriber[keyof EventSubscriber])[]; abstract: boolean; orderBy: QueryOrderMapT | QueryOrderMapT[]; // default ordering for the entity作为type的值也可以直接使用String/Number/Boolean/Date这些原生构造器。九、进阶元数据索引、仓库、过滤器与继承defineEntity的入参类型EntityMetadataWithPropertiesdefineEntity.ts#L1356-L1474在上一节基础上还支持primaryKeys、filters、forceObject、embeddable、versionProperty、concurrencyCheckKeys、serializedPrimaryKey、discriminator/discriminatorColumn/discriminatorValue、indexes支持expression、where、columns、include、fillFactor等、uniques、triggers、inheritance: tpt表级继承以及orderBy。仓库测试 table-per-type-inheritance-define-entity.test.ts 演示了用defineEntity声明 TPT 继承层级const VehicleDef defineEntity({ name: VehicleDef, abstract: true, inheritance: tpt, properties: { id: p.integer().primary(), brand: p.string(), model: p.string(), }, }); const CarDef defineEntity({ name: CarDef, extends: VehicleDef, properties: { numDoors: p.integer(), }, });另外properties除了对象字面量还支持函数形式properties: p ({ ... })defineEntity 实现 会按需调用该函数拿到构建器这在需要根据条件动态拼装属性时很有用。十、源码实现印证defineEntity到底做了什么最后从实现层面串联一下整个机制。defineEntity的运行时逻辑defineEntity.ts#L1552-L1590非常简洁解构properties与其余选项若properties是函数则先调用它拿到属性对象遍历每个属性键值为函数的属性用Object.defineProperty定义为 getter并通过Map缓存首次求值结果这就是关系属性() p.manyToOne(...)惰性求值的实现同时避免重复实例化构建器值为普通构建器的属性直接提取~options元数据return new EntitySchema({ properties, ...options })。每个构建器的~options是携带kind、entity、type等选项的元数据对象getBuilderOptions负责提取见 defineEntity.ts#L1340-L1342最终全部汇入EntitySchema的元数据与手写EntitySchema完全同构——这也是为何测试 defineEntity.test.ts#L122-L164 可以断言Foo.meta与等价手写new EntitySchema({...})的meta快照完全一致。类型层面defineEntity提供了重载defineEntity.ts#L1477-L1550传name时从属性推断出完整实体类型InferEntityFromProperties会合并基类属性、主键约束、仓库类型、判别器收窄与IndexHints传class时为既有类补充属性定义。两种重载共同支撑了本文介绍的纯defineEntity与defineEntity class两种开发模式使实体定义真正做到写一遍属性类型与运行时元数据同时就位。本文对应的最新版文档位于 docs/docs/define-entity.md7.2 版本快照位于 docs/versioned_docs/version-7.2/define-entity.md核心实现可继续阅读 packages/core/src/entity/defineEntity.ts 与 packages/core/src/metadata/EntitySchema.ts类型级与集成级验证则分别参考 tests/defineEntity.test.ts 与 tests/features/define-entity-setclass.sqlite.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点击查看免费下载相关推荐MikroORM 7.0 中无装饰器实体定义defineEntity 完整实践指南MikroORM 7.0 中无装饰器实体定义defineEntity 完整实践指南 MikroORM 7.0 推荐用 defineEntity 以纯程序化方式后端MikroORM 7 实体定义完全指南defineEntity、装饰器与 MetadataProvider 实战解析MikroORM 7 实体定义完全指南defineEntity、装饰器与 MetadataProvider 实战解析 本文是 MikroORM 7 系列中实体后端MikroORM 实体定义完全指南从 defineEntity 到装饰器元数据MikroORM 实体定义完全指南从 defineEntity 到装饰器元数据 实体Entity是 MikroORM 一切数据操作的核心载体。本文将以仓库后端上一篇Apple推送通知类型完全指南Alert、Background、VoIP和Complication详解下一篇最完整Okio使用指南从Buffer到FileSystem的核心API全解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考