MikroORM 类型安全指南:实体引用与类型安全访问
引言
在现代ORM框架中,类型安全是一个非常重要的特性。MikroORM通过一系列精心设计的类型系统和包装器,为开发者提供了强大的类型安全保障。本文将深入探讨MikroORM中的类型安全机制,包括实体引用、Reference包装器、Loaded类型等核心概念。
实体引用与初始化状态
在MikroORM中,实体关系被映射为实体引用。这些引用是具有至少主键可用的实体实例,它们被存储在Identity Map中,确保从数据库获取相同文档时会得到相同的对象引用。
@ManyToOne(() => User)
author!: User; // 值始终是User实体的实例
检查与初始化实体
我们可以通过以下方式检查实体是否已初始化:
const user = em.getReference(User, 123);
console.log(wrap(user).isInitialized()); // false,它只是一个引用
console.log(user.name); // undefined
await wrap(user).init(); // 触发数据库查询
console.log(wrap(user).isInitialized()); // true
console.log(user.name); // 已定义
虽然isInitialized()
方法可用于运行时检查,但手动检查实体状态可能会变得繁琐。MikroORM提供了更好的解决方案——Reference
包装器。
Reference包装器
基本用法
当定义@ManyToOne
和@OneToOne
属性时,TypeScript编译器会认为所需实体总是已加载。使用Reference
包装器可以解决这个问题:
@Entity()
export class Article {
@PrimaryKey()
id!: number;
@ManyToOne()
author: Ref<User>;
constructor(author: User) {
this.author = ref(author);
}
}
使用示例
const article1 = await em.findOne(Article, 1);
article1.author instanceof Reference; // true
article1.author.name; // 类型错误,没有name属性
article1.author.unwrap().name; // 不安全同步访问,未加载时为undefined
const article2 = await em.findOne(Article, 1, { populate: ['author'] });
article2.author.$.name; // 类型安全的同步访问
同步访问方法
Reference
包装器提供了几个同步访问方法:
const article = await em.findOne(Article, 1);
console.log(article.author.getEntity()); // 错误:未初始化
console.log(await article.author.load('name')); // 先加载作者
console.log(article.author.getProperty('name')); // 已加载,安全访问
ScalarReference包装器
类似于Reference
包装器,MikroORM还提供了ScalarReference
包装器用于包装标量值:
@Property({ lazy: true, ref: true })
passwordHash!: Ref<string>;
使用示例:
const user = await em.findOne(User, 1);
const passwordHash = await user.passwordHash.load();
对于对象类型的标量属性,需要显式使用ScalarRef
类型:
@Property({ type: 'json', nullable: true, lazy: true, ref: true })
reportParameters!: ScalarRef<ReportParameters | null>;
Loaded类型
MikroORM的查询方法返回的不是简单的实体类型,而是Loaded
类型,它表示哪些关系已被加载:
// res1类型为Loaded<User, never>[]
const res1 = await em.find(User, {});
// res2类型为Loaded<User, 'identity' | 'friends'>[]
const res2 = await em.find(User, {}, { populate: ['identity', 'friends'] });
类型安全同步访问
Loaded
类型添加了特殊的$
符号,允许对已加载属性进行类型安全的同步访问:
const user = await em.findOneOrFail(User, 1, { populate: ['identity'] });
console.log(user.identity.$.email); // 类型安全的同步访问
在自定义方法中使用
可以在自定义方法中使用Loaded
类型来要求某些关系必须被加载:
function checkIdentity(user: Loaded<User, 'identity'>) {
if (!user.identity.$.email.includes('@')) {
throw new Error(`无效的电子邮件格式`);
}
}
严格部分加载
Loaded
类型还支持部分加载提示(fields
选项),返回类型将只允许访问选定的属性:
const article = await em.findOneOrFail(Article, 1, {
fields: ['title', 'author.email'],
populate: ['author'],
});
const title = article.title; // 允许
const publisher = article.publisher; // 类型错误,未选择
const email = article.author.email; // 允许
const name = article.author.name; // 类型错误,未选择
总结
MikroORM通过以下机制提供了强大的类型安全保障:
- Reference包装器:处理实体引用的延迟加载和类型安全访问
- ScalarReference:处理标量属性的延迟加载
- Loaded类型:精确描述查询返回的实体状态
- 严格部分加载:确保只访问明确请求的字段
这些特性共同构成了MikroORM强大的类型系统,帮助开发者在开发过程中捕获更多潜在错误,提高代码质量和开发效率。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考