定义实体
实体是简单的 javascript 对象(所谓的 POJO),没有限制,也不需要扩展基类。使用 实体构造函数 也有效 - 它们永远不会为托管实体(从数据库加载)执行。每个实体都需要有一个主键。
¥Entities are simple javascript objects (so called POJO) without restrictions and without the need to extend base classes. Using entity constructors works as well - they are never executed for managed entities (loaded from database). Every entity is required to have a primary key.
实体可以通过两种方式定义:
¥Entities can be defined in two ways:
-
装饰类 - 实体的属性以及每个属性都是通过装饰器提供的。我们在类上使用
@Entity()
装饰器。实体属性可以用@Property
装饰器或引用装饰器之一进行装饰:@ManyToOne
、@OneToMany
、@OneToOne
和@ManyToMany
。查看完整的 装饰器参考。¥Decorated classes - the attributes of the entity, as well as each property are provided via decorators. We use
@Entity()
decorator on the class. Entity properties are decorated either with@Property
decorator, or with one of reference decorators:@ManyToOne
,@OneToMany
,@OneToOne
and@ManyToMany
. Check out the full decorator reference. -
EntitySchema
助手 - 使用EntitySchema
助手,我们以编程方式定义模式。我们可以使用常规类以及接口。这种方法还允许重用部分实体定义(例如特性/混合)。在 通过 EntitySchema 定义实体部分 中阅读更多相关内容。¥
EntitySchema
helper - WithEntitySchema
helper we define the schema programmatically. We can use regular classes as well as interfaces. This approach also allows to re-use partial entity definitions (e.g. traits/mixins). Read more about this in Defining Entities via EntitySchema section.
此外,从装饰器中提取元数据的方式是通过 MetadataProvider
控制的。两个主要的元数据提供程序是:
¥Moreover, how the metadata extraction from decorators happens is controlled via MetadataProvider
. Two main metadata providers are:
-
ReflectMetadataProvider
- 使用reflect-metadata
读取属性类型。更快但更简单且更冗长。¥
ReflectMetadataProvider
- usesreflect-metadata
to read the property types. Faster but simpler and more verbose. -
TsMorphMetadataProvider
- 使用ts-morph
从 TypeScript 编译的 API 中读取类型信息。更重(需要完整的 TS 作为依赖),但允许 DRY 实体定义。使用ts-morph
,我们能够提取代码中定义的类型,包括接口名称以及属性的可选性。¥
TsMorphMetadataProvider
- usests-morph
to read the type information from the TypeScript compiled API. Heavier (requires full TS as a dependency), but allows DRY entity definition. Withts-morph
we are able to extract the type as it is defined in the code, including interface names, as well as optionality of properties.
在 元数据提供程序部分 中阅读有关它们的更多信息。
¥Read more about them in the Metadata Providers section.
MikroORM 中的当前装饰器集旨在与
tsc
配合使用。也可以使用babel
和swc
,但需要一些额外的设置。在 此处 中阅读有关它的更多信息。有关webpack
的注释,请阅读 部署部分。¥Current set of decorators in MikroORM is designed to work with the
tsc
. Usingbabel
andswc
is also possible, but requires some additional setup. Read more about it here. For notes aboutwebpack
, read the deployment section.
ts-morph
仅与tsc
方法兼容。¥
ts-morph
is compatible only with thetsc
approach.
从 v3 开始,我们还可以在定义实体时使用默认导出。
¥From v3 we can also use default exports when defining your entity.
以下是 Book
实体的示例定义。我们可以切换选项卡以查看各种方式的差异:
¥Example definition of a Book
entity follows. We can switch the tabs to see the difference for various ways:
- reflect-metadata
- ts-morph
- EntitySchema
@Entity()
export class Book extends CustomBaseEntity {
@Property()
title!: string;
@ManyToOne(() => Author)
author!: Author;
@ManyToOne(() => Publisher, { ref: true, nullable: true })
publisher?: Ref<Publisher>;
@ManyToMany({ entity: 'BookTag', fixedOrder: true })
tags = new Collection<BookTag>(this);
}
@Entity()
export class Book extends CustomBaseEntity {
@Property()
title!: string;
@ManyToOne()
author!: Author;
@ManyToOne()
publisher?: Ref<Publisher>;
@ManyToMany({ fixedOrder: true })
tags = new Collection<BookTag>(this);
}
export interface IBook extends CustomBaseEntity {
title: string;
author: Author;
publisher?: Ref<Publisher>;
tags: Collection<BookTag>;
}
export const Book = new EntitySchema<IBook, CustomBaseEntity>({
name: 'Book',
extends: 'CustomBaseEntity',
properties: {
title: { type: 'string' },
author: { kind: 'm:1', entity: 'Author' },
publisher: { kind: 'm:1', entity: 'Publisher', ref: true, nullable: true },
tags: { kind: 'm:n', entity: 'BookTag', fixedOrder: true },
},
});
在你的
Ref
属性定义中包含{ ref: true }
将封装引用,提供对辅助方法(如.load
和.unwrap
)的访问,这有助于加载数据并更改你计划使用它们的引用类型。¥Including
{ ref: true }
in yourRef
property definitions will wrap the reference, providing access to helper methods like.load
and.unwrap
, which can be helpful for loading data and changing the type of your references where you plan to use them.
以下是 Author
实体的另一个示例,该实体是从 Book
实体引用的,这次是为 mongo 定义的:
¥Here is another example of Author
entity, that was referenced from the Book
one, this time defined for mongo:
- reflect-metadata
- ts-morph
- EntitySchema
@Entity()
export class Author {
@PrimaryKey()
_id!: ObjectId;
@SerializedPrimaryKey()
id!: string;
@Property()
createdAt = new Date();
@Property({ onUpdate: () => new Date() })
updatedAt = new Date();
@Property()
name!: string;
@Property()
email!: string;
@Property({ nullable: true })
age?: number;
@Property()
termsAccepted: boolean = false;
@Property({ nullable: true })
identities?: string[];
@Property({ nullable: true })
born?: string;
@OneToMany(() => Book, book => book.author)
books = new Collection<Book>(this);
@ManyToMany(() => Author)
friends = new Collection<Author>(this);
@ManyToOne(() => Book, { nullable: true })
favouriteBook?: Book;
@Property({ version: true })
version!: number;
constructor(name: string, email: string) {
this.name = name;
this.email = email;
}
}
@Entity()
export class Author {
@PrimaryKey()
_id!: ObjectId;
@SerializedPrimaryKey()
id!: string;
@Property()
createdAt = new Date();
@Property({ onUpdate: () => new Date() })
updatedAt = new Date();
@Property()
name!: string;
@Property()
email!: string;
@Property()
age?: number;
@Property()
termsAccepted = false;
@Property()
identities?: string[];
@Property()
born?: string;
@OneToMany(() => Book, book => book.author)
books = new Collection<Book>(this);
@ManyToMany()
friends = new Collection<Author>(this);
@ManyToOne()
favouriteBook?: Book;
@Property({ version: true })
version!: number;
constructor(name: string, email: string) {
this.name = name;
this.email = email;
}
}
export class Author {
_id!: ObjectId;
id!: string;
createdAt = new Date();
updatedAt = new Date();
name!: string;
email!: string;
age?: number;
termsAccepted = false;
identities?: string[];
born?: string;
books = new Collection<Book>(this);
friends = new Collection<Author>(this);
favouriteBook?: Book;
version!: number;
constructor(name: string, email: string) {
this.name = name;
this.email = email;
}
}
export const AuthorSchema = new EntitySchema({
class: Author,
properties: {
_id: { type: 'ObjectId', primary: true },
id: { type: String, serializedPrimaryKey: true },
createdAt: { type: Date },
updatedAt: { type: Date, onUpdate: () => new Date() },
name: { type: String },
email: { type: String },
age: { type: Number, nullable: true },
termsAccepted: { type: Boolean },
identities: { type: 'string[]', nullable: true },
born: { type: 'date', nullable: true },
books: { kind: '1:m', entity: () => Book, mappedBy: book => book.author },
friends: { kind: 'm:n', entity: () => Author },
favouriteBook: { kind: 'm:1', entity: () => Book, nullable: true },
version: { type: Number, version: true },
},
});
有关建模关系的更多信息,请参阅 建模关系页面。
¥More information about modelling relationships can be found on modelling relationships page.
有关 Vanilla JavaScript 使用的示例,请查看 此处。
¥For an example of Vanilla JavaScript usage, take a look here.
可选属性
¥Optional Properties
使用默认的 reflect-metadata
提供程序,我们需要将每个可选属性标记为 nullable: true
。使用 ts-morph
时,如果将属性定义为可选(标记为 ?
),则将自动将其视为可空属性(主要用于 SQL 模式生成器)。
¥With the default reflect-metadata
provider, we need to mark each optional property as nullable: true
. When using ts-morph
, if you define the property as optional (marked with ?
), this will be automatically considered as nullable property (mainly for SQL schema generator).
- reflect-metadata
- ts-morph
- EntitySchema
@ManyToOne(() => Book, { nullable: true })
favouriteBook?: Book;
@ManyToOne()
favouriteBook?: Book;
properties: {
favouriteBook: { kind: 'm:1', entity: () => Book, nullable: true },
},
默认值
¥Default values
我们可以通过两种方式设置属性的默认值:
¥We can set default value of a property in 2 ways:
-
使用属性的运行时默认值。只要我们不使用任何原生数据库函数(如
now()
),就应该首选这种方法。通过这种方法,我们的实体将在实际持久化到数据库之前设置默认值(例如,当我们通过new Author()
或em.create(Author, { ... })
实例化新实体时)。¥Use runtime default value of the property. This approach should be preferred as long as we are not using any native database function like
now()
. With this approach our entities will have the default value set even before it is actually persisted into the database (e.g. when we instantiate new entity vianew Author()
orem.create(Author, { ... })
).
- reflect-metadata
- ts-morph
- EntitySchema
@Property()
foo: number & Opt = 1;
@Property()
bar: string & Opt = 'abc';
@Property()
baz: Date & Opt = new Date();
@Property()
foo: number & Opt = 1;
@Property()
bar: string & Opt = 'abc';
@Property()
baz: Date & Opt = new Date();
properties: {
foo: { type: Number, onCreate: () => 1 },
bar: { type: String, onCreate: () => 'abc' },
baz: { type: Date, onCreate: () => new Date() },
},
-
使用
@Property
装饰器的default
参数。这样,实际的默认值将由数据库提供,并在持久化(刷新后)后自动映射到实体属性。要使用像now()
这样的 SQL 函数,请使用defaultRaw
。¥Use
default
parameter of@Property
decorator. This way the actual default value will be provided by the database, and automatically mapped to the entity property after it is being persisted (after flush). To use SQL functions likenow()
, usedefaultRaw
.
自 v4 以来,我们还可以使用
defaultRaw
,它还将处理异常的日志记录和映射。¥Since v4 you should use
defaultRaw
for SQL functions, asdefault
with string values will be automatically quoted.
- reflect-metadata
- ts-morph
- EntitySchema
@Property({ default: 1 })
foo!: number & Opt;
@Property({ default: 'abc' })
bar!: string & Opt;
@Property({ defaultRaw: 'now' })
baz!: Date & Opt;
@Property({ default: 1 })
foo!: number & Opt;
@Property({ default: 'abc' })
bar!: string & Opt;
@Property({ defaultRaw: 'now' })
baz!: Date & Opt;
properties: {
foo: { type: Number, default: 1 },
bar: { type: String, default: 'abc' },
baz: { type: Date, defaultRaw: 'now' },
},
请注意,我们使用 Opt
类型与属性类型相交,以告诉 ORM(在类型级别上),该属性对于输入类型(例如在 em.create()
中)应被视为可选的,但对于托管实体(例如 EntityDTO
类型)将存在。
¥Note that we use the Opt
type to intersect with the property type to tell the ORM (on type level) that the property should be considered optional for input types (e.g. in em.create()
), but will be present for managed entities (e.g. EntityDTO
type).
枚举
¥Enums
要定义枚举属性,请使用 @Enum()
装饰器。枚举可以是数字或字符串值。
¥To define an enum property, use @Enum()
decorator. Enums can be either numeric or string values.
为了使模式生成器在字符串枚举的情况下正常工作,我们需要在使用它的同一文件中定义枚举,以便可以自动发现其值。如果我们想在另一个文件中定义枚举,我们也应该在使用它的地方重新导出它。
¥For schema generator to work properly in case of string enums, we need to define the enum in the same file as where it is used, so its values can be automatically discovered. If we want to define the enum in another file, we should re-export it also in place where we use it.
另一种可能性是通过 @Enum(() => UserRole)
在装饰器中提供对枚举实现的引用。
¥Another possibility is to provide the reference to the enum implementation in the decorator via @Enum(() => UserRole)
.
我们还可以通过
items: string[]
属性手动设置枚举项。¥We can also set enum items manually via
items: string[]
attribute.
- reflect-metadata
- ts-morph
- EntitySchema
import { OutsideEnum } from './OutsideEnum.ts';
@Entity()
export class User {
@Enum(() => UserRole)
role!: UserRole; // string enum
@Enum(() => UserStatus)
status!: UserStatus; // numeric/const enum
@Enum(() => OutsideEnum)
outside!: OutsideEnum; // string enum defined outside of this file
@Enum({ items: () => OutsideNullableEnum, nullable: true })
outsideNullable?: OutsideNullableEnum; // string enum defined outside of this file, may be null
}
export enum UserRole {
ADMIN = 'admin',
MODERATOR = 'moderator',
USER = 'user',
}
export const enum UserStatus {
DISABLED,
ACTIVE,
}
// or we could reexport OutsideEnum
// export { OutsideEnum } from './OutsideEnum.ts';
import { OutsideEnum } from './OutsideEnum.ts';
@Entity()
export class User {
@Enum(() => UserRole)
role!: UserRole; // string enum
@Enum(() => UserStatus)
status!: UserStatus; // numeric enum
@Enum(() => OutsideEnum)
outside!: OutsideEnum; // string enum defined outside of this file
@Enum({ items: () => OutsideNullableEnum })
outsideNullable?: OutsideNullableEnum; // string enum defined outside of this file, may be null
}
export enum UserRole {
ADMIN = 'admin',
MODERATOR = 'moderator',
USER = 'user',
}
export const enum UserStatus {
DISABLED,
ACTIVE,
}
// or we could reexport OutsideEnum
// export { OutsideEnum } from './OutsideEnum.ts';
properties: {
// string enum
role: { enum: true, items: () => UserRole },
// numeric enum
status: { enum: true, items: () => UserStatus },
// string enum defined outside of this file
outside: { enum: true, items: () => OutsideEnum },
// string enum defined outside of this file, may be null
outsideNullable: { enum: true, items: () => OutsideNullableEnum, nullable: true },
},
PostgreSQL 原生枚举
¥PostgreSQL native enums
默认情况下,PostgreSQL 驱动程序将枚举表示为具有检查约束的文本列。从 v6 开始,你可以通过设置 nativeEnumName
选项来选择原生枚举。
¥By default, the PostgreSQL driver, represents enums as a text columns with check constraints. Since v6, you can opt in for a native enums by setting the nativeEnumName
option.
- reflect-metadata
- ts-morph
- EntitySchema
@Entity()
export class User {
@Enum({ items: () => UserRole, nativeEnumName: 'user_role' })
role!: UserRole;
}
export enum UserRole {
ADMIN = 'admin',
MODERATOR = 'moderator',
USER = 'user',
}
@Entity()
export class User {
@Enum({ items: () => UserRole, nativeEnumName: 'user_role' })
role!: UserRole;
}
export enum UserRole {
ADMIN = 'admin',
MODERATOR = 'moderator',
USER = 'user',
}
export enum UserRole {
ADMIN = 'admin',
MODERATOR = 'moderator',
USER = 'user',
}
properties: {
role: { enum: true, nativeEnumName: 'user_role', items: () => UserRole },
},
枚举数组
¥Enum arrays
我们还可以使用枚举值数组,在这种情况下,EnumArrayType
类型将自动使用,这将在刷新时验证项目。
¥We can also use array of values for enum, in that case, EnumArrayType
type will be used automatically, that will validate items on flush.
- reflect-metadata
- ts-morph
- EntitySchema
enum Role {
User = 'user',
Admin = 'admin',
}
@Enum({ items: () => Role, array: true, default: [Role.User] })
roles = [Role.User];
enum Role {
User = 'user',
Admin = 'admin',
}
@Enum({ default: [Role.User] })
roles = [Role.User];
enum Role {
User = 'user',
Admin = 'admin',
}
properties: {
roles: { enum: true, array: true, default: [Role.User], items: () => Role },
},
直接映射到主键
¥Mapping directly to primary keys
有时我们可能只想使用关系的主键。为此,我们可以在 M:1 和 1:1 关系上使用 mapToPk
选项:
¥Sometimes we might want to work only with the primary key of a relation. To do that, we can use mapToPk
option on M:1 and 1:1 relations:
- reflect-metadata
- ts-morph
- EntitySchema
@ManyToOne(() => User, { mapToPk: true })
user: number;
@ManyToOne(() => User, { mapToPk: true })
user: number;
properties: {
user: { entity: () => User, mapToPk: true },
},
对于复合键,这将为我们提供表示原始 PK 的有序元组,这是复合 PK 的内部格式:
¥For composite keys, this will give us ordered tuple representing the raw PKs, which is the internal format of composite PK:
- reflect-metadata
- ts-morph
- EntitySchema
@ManyToOne(() => User, { mapToPk: true })
user: [string, string]; // [first_name, last_name]
@ManyToOne(() => User, { mapToPk: true })
user: [string, string]; // [first_name, last_name]
properties: {
user: { entity: () => User, mapToPk: true },
},
公式
¥Formulas
@Formula()
装饰器可用于将一些 SQL 代码片段映射到你的实体。SQL 片段可以尽可能复杂,甚至包括子选择。
¥@Formula()
decorator can be used to map some SQL snippet to your entity. The SQL fragment can be as complex as you want and even include subselects.
- reflect-metadata
- ts-morph
- EntitySchema
@Formula('obj_length * obj_height * obj_width')
objectVolume?: number;
@Formula('obj_length * obj_height * obj_width')
objectVolume?: number;
properties: {
objectVolume: { formula: 'obj_length * obj_height * obj_width' },
},
公式将自动添加到 select 子句中。如果你在使用 NonUniqueFieldNameException
时遇到问题,你可以将公式定义为回调,该回调将在参数中接收实体别名:
¥Formulas will be added to the select clause automatically. In case you are facing problems with NonUniqueFieldNameException
, you can define the formula as a callback that will receive the entity alias in the parameter:
- reflect-metadata
- ts-morph
- EntitySchema
@Formula(alias => `${alias}.obj_length * ${alias}.obj_height * ${alias}.obj_width`)
objectVolume?: number;
@Formula(alias => `${alias}.obj_length * ${alias}.obj_height * ${alias}.obj_width`)
objectVolume?: number;
properties: {
objectVolume: { formula: alias => `${alias}.obj_length * ${alias}.obj_height * ${alias}.obj_width` },
},
索引
¥Indexes
我们可以通过 @Index()
装饰器定义索引,对于唯一索引,我们可以使用 @Unique()
装饰器。我们可以在实体类或实体属性上使用它。
¥We can define indexes via @Index()
decorator, for unique indexes, we can use @Unique()
decorator. We can use it either on entity class, or on entity property.
要定义复杂索引,我们可以使用索引表达式。它们允许我们指定最终的 create index
查询和索引名称 - 然后,此名称用于索引差异,因此模式生成器只会在它尚未存在时尝试创建它,或者在它不再在实体中定义时将其删除。索引表达式不绑定到任何属性,而是绑定到实体本身(我们仍然可以在实体和属性级别定义它们)。
¥To define complex indexes, we can use index expressions. They allow us to specify the final create index
query and an index name - this name is then used for index diffing, so the schema generator will only try to create it if it's not there yet, or remove it, if it's no longer defined in the entity. Index expressions are not bound to any property, rather to the entity itself (we can still define them on both entity and property level).
- reflect-metadata
- ts-morph
- EntitySchema
@Entity()
@Index({ properties: ['name', 'age'] }) // compound index, with generated name
@Index({ name: 'custom_idx_name', properties: ['name'] }) // simple index, with custom name
@Unique({ properties: ['name', 'email'] })
export class Author {
@Property()
@Unique()
email!: string;
@Property()
@Index() // generated name
age?: number;
@Index({ name: 'born_index' })
@Property()
born?: string;
@Index({ name: 'custom_index_expr', expression: 'alter table `author` add index `custom_index_expr`(`title`)' })
@Property()
title!: string;
}
@Entity()
@Index({ properties: ['name', 'age'] }) // compound index, with generated name
@Index({ name: 'custom_idx_name', properties: ['name'] }) // simple index, with custom name
@Unique({ properties: ['name', 'email'] })
export class Author {
@Property()
@Unique()
email!: string;
@Property()
@Index() // generated name
age?: number;
@Index({ name: 'born_index' })
@Property()
born?: string;
@Index({ name: 'custom_index_expr', expression: 'alter table `author` add index `custom_index_expr`(`title`)' })
@Property()
title!: string;
}
export const AuthorSchema = new EntitySchema<Author, CustomBaseEntity>({
class: Author,
indexes: [
{ properties: ['name', 'age'] }, // compound index, with generated name
{ name: 'custom_idx_name', properties: ['name'] }, // simple index, with custom name
{ name: 'custom_index_expr', expression: 'alter table `author` add index `custom_index_expr`(`title`)' },
],
uniques: [
{ properties: ['name', 'email'] },
],
properties: {
email: { type: 'string', unique: true }, // generated name
age: { type: 'number', nullable: true, index: true }, // generated name
born: { type: 'date', nullable: true, index: 'born_index' },
title: { type: 'string' },
},
});
检查约束
¥Check constraints
我们可以通过 @Check()
装饰器定义检查约束。我们可以在实体类或实体属性上使用它。它有一个必需的 expression
属性,可以是字符串或回调,用于接收属性名称到列名称的映射。请注意,如果我们想要 TypeScript 建议属性名称,我们需要使用泛型类型参数。
¥We can define check constraints via @Check()
decorator. We can use it either on entity class, or on entity property. It has a required expression
property, that can be either a string or a callback, that receives map of property names to column names. Note that we need to use the generic type argument if we want TypeScript suggestions for the property names.
目前仅在 postgres 驱动程序中支持检查约束。
¥Check constraints are currently supported only in postgres driver.
- reflect-metadata
- ts-morph
- EntitySchema
@Entity()
// with generated name based on the table name
@Check({ expression: 'price1 >= 0' })
// with explicit name
@Check({ name: 'foo', expression: columns => `${columns.price1} >= 0` })
// with explicit type argument we get autocomplete on `columns`
@Check<FooEntity>({ expression: columns => `${columns.price1} >= 0` })
export class Book {
@PrimaryKey()
id!: number;
@Property()
price1!: number;
@Property()
@Check({ expression: 'price2 >= 0' })
price2!: number;
@Property({ check: columns => `${columns.price3} >= 0` })
price3!: number;
}
@Entity()
// with generated name based on the table name
@Check({ expression: 'price1 >= 0' })
// with explicit name
@Check({ name: 'foo', expression: columns => `${columns.price1} >= 0` })
// with explicit type argument we get autocomplete on `columns`
@Check<FooEntity>({ expression: columns => `${columns.price1} >= 0` })
export class Book {
@PrimaryKey()
id!: number;
@Property()
price1!: number;
@Property()
@Check({ expression: 'price2 >= 0' })
price2!: number;
@Property({ check: columns => `${columns.price3} >= 0` })
price3!: number;
}
export const BookSchema = new EntitySchema({
class: Book,
checks: [
{ expression: 'price1 >= 0' },
{ name: 'foo', expression: columns => `${columns.price1} >= 0` },
{ expression: columns => `${columns.price1} >= 0` },
{ propertyName: 'price2', expression: 'price2 >= 0' },
{ propertyName: 'price3', expression: columns => `${columns.price3} >= 0` },
],
properties: {
id: { type: 'number', primary: true },
price1: { type: 'number' },
price2: { type: 'number' },
price3: { type: 'number' },
},
});
自定义类型
¥Custom Types
我们可以通过扩展 Type
抽象类来定义自定义类型。它有 4 种可选方法:
¥We can define custom types by extending Type
abstract class. It has 4 optional methods:
-
convertToDatabaseValue(value: any, platform: Platform): any
将值从其 JS 表示转换为此类型的数据库表示。
¥Converts a value from its JS representation to its database representation of this type.
-
convertToJSValue(value: any, platform: Platform): any
将值从其数据库表示转换为此类型的 JS 表示。
¥Converts a value from its database representation to its JS representation of this type.
-
toJSON(value: any, platform: Platform): any
将值从其 JS 表示转换为此类型的序列化 JSON 形式。默认情况下,转换为数据库值。
¥Converts a value from its JS representation to its serialized JSON form of this type. By default, converts to the database value.
-
getColumnType(prop: EntityProperty, platform: Platform): string
获取此类型字段的 SQL 声明片段。
¥Gets the SQL declaration snippet for a field of this type.
有关 自定义类型 部分的更多信息,请参阅。
¥More information can be found in Custom Types section.
惰性标量属性
¥Lazy scalar properties
我们可以将任何属性标记为 lazy: true
以将其从 select 子句中省略。这对于太大的属性很方便,并且你希望它们只在某些时候可用,例如文章的全文。
¥We can mark any property as lazy: true
to omit it from the select clause. This can be handy for properties that are too large, and you want to have them available only sometimes, like a full text of an article.
- reflect-metadata
- ts-morph
- EntitySchema
@Property({ columnType: 'text', lazy: true })
text: string;
@Property({ columnType: 'text', lazy: true })
text: string;
properties: {
text: { columnType: 'text', lazy: true },
}
我们可以使用 populate
参数来加载它们。
¥We can use populate
parameter to load them.
const b1 = await em.find(Book, 1); // this will omit the `text` property
const b2 = await em.find(Book, 1, { populate: ['text'] }); // this will load the `text` property
如果实体已加载,并且你需要填充惰性标量属性,则可能需要在
FindOptions
中传递refresh: true
。¥If the entity is already loaded, and you need to populate a lazy scalar property, you might need to pass
refresh: true
in theFindOptions
.
ScalarReference
封装器
¥ScalarReference
wrapper
如果你的 Reference
是 POJO(因此是对象,但不是 Ref
实体的实例),则会出现类似(但更隐蔽)的问题,因为这可能会进行类型检查,尽管它也不起作用。对于非对象类型,Ref
类型会自动解析为 ScalarReference
,因此以下内容是正确的:
¥Similarly to the Reference
wrapper, we can also wrap lazy scalars with Ref
into a ScalarReference
object. The Ref
type automatically resolves to ScalarReference
for non-object types, so the below is correct:
@Property({ lazy: true, ref: true })
passwordHash!: Ref<string>;
const user = await em.findOne(User, 1);
const passwordHash = await user.passwordHash.load();
对于类似对象的类型,如果你选择使用引用封装器,则应明确使用 ScalarRef<T>
类型。例如,你可能想要延迟加载一个大的 JSON 值:
¥For object-like types, if you choose to use the reference wrappers, you should use the ScalarRef<T>
type explicitly. For example, you might want to lazily load a large JSON value:
@Property({ type: 'json', nullable: true, lazy: true, ref: true })
// ReportParameters is an object type, imagine it defined elsewhere.
reportParameters!: ScalarRef<ReportParameters | null>;
请记住,一旦通过 ScalarReference
管理标量值,通过 MikroORM 管理对象访问它将始终返回 ScalarReference
封装器。如果属性也是 nullable
,这可能会造成混淆,因为 ScalarReference
永远是真实的。在这种情况下,你应该通过 ScalarReference<T>
的类型参数通知类型系统该属性的可空性,如上所示。下面是其工作原理的示例:
¥Keep in mind that once a scalar value is managed through a ScalarReference
, accessing it through MikroORM managed objects will always return the ScalarReference
wrapper. That can be confusing in case the property is also nullable
, since the ScalarReference
will always be truthy. In such cases, you should inform the type system of the nullability of the property through ScalarReference<T>
's type parameter as demonstrated above. Below is an example of how it all works:
// Say Report of id "1" has no reportParameters in the Database.
const report = await em.findOne(Report, 1);
if (report.reportParameters) {
// Logs Ref<?>, not the actual value. **Would always run***.
console.log(report.reportParameters);
//@ts-expect-error $/.get() is not available until the reference has been loaded.
// const mistake = report.reportParameters.$
}
const populatedReport = await em.populate(report, ['reportParameters']);
// Logs `null`
console.log(populatedReport.reportParameters.$);
虚拟属性
¥Virtual Properties
我们可以将我们的属性定义为虚拟的,可以是方法,也可以通过 JavaScript get/set
。
¥We can define our properties as virtual, either as a method, or via JavaScript get/set
.
以下示例定义了具有 firstName
和 lastName
数据库字段的用户实体,这两个字段都隐藏在序列化响应中,替换为虚拟属性 fullName
(定义为经典方法)和 fullName2
(定义为 JavaScript getter)。
¥Following example defines User entity with firstName
and lastName
database fields, that are both hidden from the serialized response, replaced with virtual properties fullName
(defined as a classic method) and fullName2
(defined as a JavaScript getter).
对于 JavaScript getter,你需要提供
{ persist: false }
选项,否则该值将存储在数据库中。¥For JavaScript getter you need to provide
{ persist: false }
option otherwise the value would be stored in the database.
- reflect-metadata
- ts-morph
- EntitySchema
@Entity()
export class User {
[HiddenProps]?: 'firstName' | 'lastName';
@Property({ hidden: true })
firstName!: string;
@Property({ hidden: true })
lastName!: string;
@Property({ name: 'fullName' })
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
@Property({ persist: false })
get fullName2() {
return `${this.firstName} ${this.lastName}`;
}
}
@Entity()
export class User {
[HiddenProps]?: 'firstName' | 'lastName';
@Property({ hidden: true })
firstName!: string;
@Property({ hidden: true })
lastName!: string;
@Property({ name: 'fullName' })
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
@Property({ persist: false })
get fullName2() {
return `${this.firstName} ${this.lastName}`;
}
}
export class User {
[HiddenProps]?: 'firstName' | 'lastName';
firstName!: string;
lastName!: string;
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
get fullName2() {
return `${this.firstName} ${this.lastName}`;
}
}
properties: {
firstName: { type: String, hidden: true },
lastName: { type: String, hidden: true },
fullName: { type: 'method', persist: false, getter: true, getterName: 'getFullName' },
fullName2: { type: 'method', persist: false, getter: true },
}
const repo = em.getRepository(User);
const author = repo.create({ firstName: 'Jon', lastName: 'Snow' });
console.log(author.getFullName()); // 'Jon Snow'
console.log(author.fullName2); // 'Jon Snow'
console.log(wrap(author).toJSON()); // { fullName: 'Jon Snow', fullName2: 'Jon Snow' }
实体文件名
¥Entity file names
从 MikroORM 4.2 开始,实体文件名没有限制。现在也可以使用基于文件夹的发现在单个文件中定义多个实体。
¥Starting with MikroORM 4.2, there is no limitation for entity file names. It is now also possible to define multiple entities in a single file using folder based discovery.
使用自定义基础实体
¥Using custom base entity
我们可以定义自己的基本实体,其中包含所有实体所需的属性,例如主键和创建/更新时间。还支持单表继承。
¥We can define our own base entity with properties that are required on all entities, like primary key and created/updated time. Single table inheritance is also supported.
在 继承映射 部分中阅读更多有关此主题的内容。
¥Read more about this topic in Inheritance Mapping section.
如果你通过
entities
选项初始化 ORM,你还需要指定所有基本实体。¥If you are initializing the ORM via
entities
option, you need to specify all your base entities as well.
- reflect-metadata
- ts-morph
- EntitySchema
import { v4 } from 'uuid';
export abstract class CustomBaseEntity {
@PrimaryKey()
uuid = v4();
@Property()
createdAt = new Date();
@Property({ onUpdate: () => new Date() })
updatedAt = new Date();
}
import { v4 } from 'uuid';
export abstract class CustomBaseEntity {
@PrimaryKey()
uuid = v4();
@Property()
createdAt = new Date();
@Property({ onUpdate: () => new Date() })
updatedAt = new Date();
}
import { v4 } from 'uuid';
export interface CustomBaseEntity {
uuid: string;
createdAt: Date;
updatedAt: Date;
}
export const schema = new EntitySchema<CustomBaseEntity>({
name: 'CustomBaseEntity',
abstract: true,
properties: {
uuid: { type: 'uuid', onCreate: () => v4(), primary: true },
createdAt: { type: 'Date', onCreate: () => new Date(), nullable: true },
updatedAt: { type: 'Date', onCreate: () => new Date(), onUpdate: () => new Date(), nullable: true },
},
});
有一种特殊情况,我们需要注释基础实体 - 如果我们使用基于文件夹的发现,并且基本实体未使用任何装饰器(例如,它没有定义任何装饰属性)。在这种情况下,我们需要将其标记为抽象:
¥There is a special case, when we need to annotate the base entity - if we are using folder based discovery, and the base entity is not using any decorators (e.g. it does not define any decorated property). In that case, we need to mark it as abstract:
@Entity({ abstract: true })
export abstract class CustomBaseEntity {
// ...
}
SQL 生成的列
¥SQL Generated columns
要使用生成的列,你可以使用 generated
选项,也可以将其指定为 columnType
的一部分:
¥To use generated columns, you can either use the generated
option, or specify it as part of the columnType
:
- reflect-metadata
- ts-morph
- EntitySchema
@Entity()
export class User {
@PrimaryKey()
id!: number;
@Property({ length: 50 })
firstName!: string;
@Property({ length: 50 })
lastName!: string;
@Property<User>({ length: 100, generated: cols => `(concat(${cols.firstName}, ' ', ${cols.lastName})) stored` })
fullName!: string & Opt;
@Property({ columnType: `varchar(100) generated always as (concat(first_name, ' ', last_name)) virtual` })
fullName2!: string & Opt;
}
@Entity()
export class User {
@PrimaryKey()
id!: number;
@Property({ length: 50 })
firstName!: string;
@Property({ length: 50 })
lastName!: string;
@Property<User>({ length: 100, generated: cols => `(concat(${cols.firstName}, ' ', ${cols.lastName})) stored` })
fullName!: string & Opt;
@Property({ columnType: `varchar(100) generated always as (concat(first_name, ' ', last_name)) virtual` })
fullName2!: string & Opt;
}
export interface IUser {
id: number;
firstName: string;
lastName: string;
fullName: string & Opt;
fullName2: string & Opt;
}
export const User = new EntitySchema<IUser>({
name: 'User',
properties: {
id: { type: 'number', primary: true },
firstName: { type: 'string', length: 50 },
lastName: { type: 'string', length: 50 },
fullName: {
type: 'string',
length: 100,
generated: cols => `(concat(${cols.firstName}, ' ', ${cols.lastName})) stored`,
},
fullName2: {
type: 'string',
columnType: `varchar(100) generated always as (concat(first_name, ' ', last_name)) virtual`,
},
},
});
要在 PostgreSQL 中使用生成的标识列,请将 generated
选项设置为 identity
:
¥To use a generated identity column in PostgreSQL, set the generated
option to identity
:
要允许明确提供值,请使用
generated: 'by default as identity'
。¥To allow providing the value explicitly, use
generated: 'by default as identity'
.
- reflect-metadata
- ts-morph
- EntitySchema
@Entity()
export class User {
@PrimaryKey({ generated: 'identity' })
id!: number;
}
@Entity()
export class User {
@PrimaryKey({ generated: 'identity' })
id!: number;
}
export interface IUser {
id: number;
}
export const User = new EntitySchema<IUser>({
name: 'User',
properties: {
id: { type: 'number', primary: true, generated: 'identity' },
},
});
具有各种主键的实体定义示例
¥Examples of entity definition with various primary keys
使用 id 作为主键(SQL 驱动程序)
¥Using id as primary key (SQL drivers)
- reflect-metadata
- ts-morph
- EntitySchema
@Entity()
export class Book {
@PrimaryKey()
id!: number; // string is also supported
@Property()
title!: string;
@ManyToOne(() => Author)
author!: Author;
@ManyToOne(() => Publisher, { ref: true, nullable: true })
publisher?: Ref<Publisher>;
}
@Entity()
export class Book {
@PrimaryKey()
id!: number; // string is also supported
@Property()
title!: string;
@ManyToOne()
author!: Author;
@ManyToOne()
publisher?: Ref<Publisher>;
}
export interface Book {
id: number;
title: string;
author: Author;
}
export const BookSchema = new EntitySchema<Book>({
name: 'Book',
properties: {
id: { type: Number, primary: true },
title: { type: String },
author: { kind: 'm:1', entity: 'Author' },
publisher: { kind: 'm:1', entity: 'Publisher', ref: true, nullable: true },
},
});
使用 UUID 作为主键(SQL 驱动程序)
¥Using UUID as primary key (SQL drivers)
- reflect-metadata
- ts-morph
- EntitySchema
import { v4 } from 'uuid';
@Entity()
export class Book {
@PrimaryKey()
uuid = v4();
@Property()
title!: string;
@ManyToOne(() => Author)
author!: Author;
}
import { v4 } from 'uuid';
@Entity()
export class Book {
@PrimaryKey()
uuid = v4();
@Property()
title!: string;
@ManyToOne()
author!: Author;
}
export interface IBook {
uuid: string;
title: string;
author: Author;
}
export const Book = new EntitySchema<IBook>({
name: 'Book',
properties: {
uuid: { type: 'uuid', onCreate: () => v4(), primary: true },
title: { type: 'string' },
author: { entity: () => Author, kind: 'm:1' },
},
});
使用 PostgreSQL 内置 gen_random_uuid 函数作为主键
¥Using PostgreSQL built-in gen_random_uuid function as primary key
- reflect-metadata
- ts-morph
- EntitySchema
@Entity()
export class Book {
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
uuid: string;
@Property()
title!: string;
@ManyToOne(() => Author)
author!: Author;
}
@Entity()
export class Book {
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
uuid: string;
@Property()
title!: string;
@ManyToOne()
author!: Author;
}
export class Book {
uuid: string;
title!: string;
author!: Author;
}
export const BookSchema = new EntitySchema<Book>({
class: Book,
properties: {
uuid: { type: 'uuid', defaultRaw: 'gen_random_uuid()', primary: true },
title: { type: 'string' },
author: { entity: () => Author, kind: 'm:1' },
},
});
使用 BigInt 作为主键(MySQL 和 PostgreSQL)
¥Using BigInt as primary key (MySQL and PostgreSQL)
自 v6 起,bigint
由原生 BigInt
类型表示,因此,它们不需要在装饰器选项中明确指定类型:
¥Since v6, bigint
s are represented by the native BigInt
type, and as such, they don't require explicit type in the decorator options:
@PrimaryKey()
id: bigint;
你还可以指定希望将 bigint 映射到的目标类型:
¥You can also specify the target type you want your bigints to be mapped to:
@PrimaryKey({ type: new BigIntType('bigint') })
id1: bigint;
@PrimaryKey({ type: new BigIntType('string') })
id2: string;
@PrimaryKey({ type: new BigIntType('number') })
id3: number;
当映射到
number
类型时,JavaScript 无法表示bigint
的所有可能值 - 仅安全支持高达Number.MAX_SAFE_INTEGER
(2^53 - 1)的值。¥JavaScript cannot represent all the possible values of a
bigint
when mapping to thenumber
type - only values up toNumber.MAX_SAFE_INTEGER
(2^53 - 1) are safely supported.
- reflect-metadata
- ts-morph
- EntitySchema
@Entity()
export class Book {
@PrimaryKey()
id: bigint;
}
@Entity()
export class Book {
@PrimaryKey()
id: bigint;
}
properties: {
id: { type: 'bigint' },
},
如果你想使用原生 bigint
,请阅读以下指南:使用原生 BigInt PK。
¥If you want to use native bigint
s, read the following guide: Using native BigInt PKs.
Mongo 实体示例
¥Example of Mongo entity
- reflect-metadata
- ts-morph
- EntitySchema
@Entity()
export class Book {
@PrimaryKey()
_id!: ObjectId;
@SerializedPrimaryKey()
id!: string; // string variant of PK, will be handled automatically
@Property()
title!: string;
@ManyToOne(() => Author)
author!: Author;
}
@Entity()
export class Book {
@PrimaryKey()
_id!: ObjectId;
@SerializedPrimaryKey()
id!: string; // string variant of PK, will be handled automatically
@Property()
title!: string;
@ManyToOne()
author!: Author;
}
export interface IBook {
_id: ObjectId;
id: string;
title: string;
author: Author;
}
export const Book = new EntitySchema<IBook>({
name: 'Book',
properties: {
_id: { type: 'ObjectId', primary: true },
id: { type: String, serializedPrimaryKey: true },
title: { type: String },
},
});
使用 MikroORM 的 BaseEntity(以前称为 WrappedEntity)
¥Using MikroORM's BaseEntity (previously WrappedEntity)
从 v4 开始,BaseEntity
类提供了 init
、isInitialized
、assign
和其他可通过 wrap()
助手获得的方法。
¥From v4 BaseEntity
class is provided with init
, isInitialized
, assign
and other methods that are otherwise available via the wrap()
helper.
BaseEntity
的使用是可选的。¥Usage of the
BaseEntity
is optional.
import { BaseEntity } from '@mikro-orm/core';
@Entity()
export class Book extends BaseEntity {
@PrimaryKey()
id!: number;
@Property()
title!: string;
@ManyToOne()
author!: Author;
}
const book = new Book();
console.log(book.isInitialized()); // true
设置实体后,我们现在可以启动 使用实体管理器 和 repositories,如以下部分所述。
¥Having the entities set up, we can now start using entity manager and repositories as described in following sections.