自定义类型
你可以通过扩展 Type
抽象类来定义自定义类型。它有几种可选方法:
¥You can define custom types by extending Type
abstract class. It has several optional methods:
-
convertToDatabaseValue(value: any, platform: Platform): any
将值从其 JS 表示转换为此类型的数据库表示。默认情况下,返回未更改的
value
。¥Converts a value from its JS representation to its database representation of this type. By default, returns unchanged
value
. -
convertToJSValue(value: any, platform: Platform): any
将值从其数据库表示转换为此类型的 JS 表示。默认情况下,返回未更改的
value
。¥Converts a value from its database representation to its JS representation of this type. By default, returns unchanged
value
. -
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, uses the runtime value.
-
getColumnType(prop: EntityProperty, platform: Platform): string
获取此类型字段的 SQL 声明片段。默认情况下,返回给定属性的
columnType
。¥Gets the SQL declaration snippet for a field of this type. By default, returns
columnType
of given property. -
convertToDatabaseValueSQL(key: string, platform: Platform): string
将值从其 JS 表示转换为此类型的数据库表示。(在 v4.4.2 中添加)
¥Converts a value from its JS representation to its database representation of this type. (added in v4.4.2)
-
convertToJSValueSQL(key: string, platform: Platform): string
修改 SQL 表达式(标识符、参数)以转换为 JS 值。(在 v4.4.2 中添加)
¥Modifies the SQL expression (identifier, parameter) to convert to a JS value. (added in v4.4.2)
-
compareAsType(): string
应该如何比较原始数据库值?用于
EntityComparator
。可能的值:string
|number
|boolean
|date
|any
|buffer
|array
.¥How should the raw database values be compared? Used in
EntityComparator
.Possible values:string
|number
|boolean
|date
|any
|buffer
|array
. -
ensureComparable(): boolean
当值被水化时,我们将其转换回数据库值以确保可比性,因为原始数据库响应通常与
convertToDatabaseValue
结果不同。如果你知道不需要,这可以禁用额外的转换。¥When a value is hydrated, we convert it back to the database value to ensure comparability, as often the raw database response is not the same as the
convertToDatabaseValue
result. This allows to disable the additional conversion in case you know it is not needed. -
compareValues(a, b): boolean
允许覆盖内部比较逻辑。适用于数据库值(
convertToDatabaseValue
方法的结果)。当数据库值不稳定时,这会很有帮助。¥Allows to override the internal comparison logic. Works with the database values (results of
convertToDatabaseValue
method). This can be helpful when the database value is not stable. -
getDefaultLength?(platform: Platform): number
允许在使用此类型而不指定
columnType
属性选项时为length
属性选项定义默认值。如果方法本身未定义,或者指定了columnType
选项,则将忽略length
属性选项。¥Allows defining a default value for the
length
property option when using this type and not specifying thecolumnType
property option. If the method itself is undefined, or thecolumnType
option is specified, thelength
property option is ignored.
import { Type, Platform, EntityProperty, ValidationError } from '@mikro-orm/core';
/**
* A custom type that maps SQL date column to JS Date objects.
* Note that the ORM DateType maps to string instead of Date.
*/
export class MyDateType extends Type<Date, string> {
convertToDatabaseValue(value: Date | string | undefined, platform: Platform): string {
if (value instanceof Date) {
return value.toISOString().substr(0, 10);
}
if (!value || value.toString().match(/^\d{4}-\d{2}-\d{2}$/)) {
return value as string;
}
throw ValidationError.invalidType(MyDateType, value, 'JS');
}
convertToJSValue(value: Date | string | undefined, platform: Platform): Date {
if (!value || value instanceof Date) {
return value as Date;
}
const date = new Date(value);
if (date.toString() === 'Invalid Date') {
throw ValidationError.invalidType(MyDateType, value, 'database');
}
return date;
}
getColumnType(prop: EntityProperty, platform: Platform) {
return `date(${prop.length})`;
}
}
然后你可以在定义实体属性时使用此类型:
¥Then you can use this type when defining your entity properties:
@Entity()
export class FooBar {
@PrimaryKey()
id!: number;
@Property()
name!: string;
@Property({ type: MyDateType, length: 3 })
born?: Date;
}
如果你的类型实现是有状态的,例如,如果你希望类型对每个属性的行为不同,请提供该类型的实例:
¥If your type implementation is stateful, e.g. if you want the type to behave differently for each property, provide an instance of the type:
@Property({ type: new MyDateType('DD-MM-YYYY') })
born?: Date;
映射到对象和类型安全
¥Mapping to objects and type-safety
当你的自定义类型将值映射到对象时,它可能会破坏内部类型,例如在 em.create()
中,因为没有简单的方法来检测某些对象类型是实体还是其他东西。在这些情况下,使用 IType
在类型级别提供有关你的类型的更多信息会很方便。它有三个参数,第一个代表运行时类型,第二个是原始值类型,最后一个可选参数允许覆盖序列化类型(默认为原始值类型)。
¥When your custom type maps a value to an object, it might break the internal types like in em.create()
, as there is no easy way to detect whether some object type is an entity or something else. In those cases, it can be handy to use IType
to provide more information about your type on the type-level. It has three arguments, the first represents the runtime type, the second one is the raw value type, and the last optional argument allows overriding the serialized type (which defaults to the raw value type).
考虑以下自定义类型:
¥Consider the following custom type:
class MyClass {
constructor(private value: string) {}
}
class MyType extends Type<MyClass, string> {
convertToDatabaseValue(value: MyClass): string {
return value.value;
}
convertToJSValue(value: string): MyClass {
return new MyClass(value);
}
}
现在让我们将它与 IType
一起使用:
¥Now let's use it together with the IType
:
import { IType } from '@mikro-orm/core';
@Entity()
class MyEntity {
@Property({ type: MyType })
foo?: IType<MyClass, string>;
}
这将使 em.create()
正确地禁止除 MyClass 之外的值,并在序列化时将值类型转换为 string
。如果没有 IType
,em.create()
就不会出现错误,序列化会在类型级别上导致 MyClass
(但在运行时会是 string
值):
¥This will make the em.create()
properly disallow values other than MyClass, as well as convert the value type to string
when serializing. Without the IType
, there would be no error with em.create()
and the serialization would result in MyClass
on type level (but would be a string
value on runtime):
// this will fail but wouldn't without the `IType`
const entity = em.create(MyEntity, { foo: 'bar' });
// serialized value is now correctly typed to `string`
const object = wrap(e).toObject(); // `{ foo: string }`
高级示例 - PointType 和 WKT
¥Advanced example - PointType and WKT
在此示例中,我们将通过数据库以及在运行时组合映射值。
¥In this example we will combine mapping values via database as well as during runtime.
Point 类型是 MySQL Spatial 扩展的一部分,它使你能够使用 x 和 y 坐标将单个位置存储在坐标空间中。你可以使用 Point 类型存储经度/纬度对来表示地理位置。
¥The Point type is part of the Spatial extension of MySQL and enables you to store a single location in a coordinate space by using x and y coordinates. You can use the Point type to store a longitude/latitude pair to represent a geographic location.
首先让我们定义在运行时用于表示值的 Point
类:
¥First let's define the Point
class that will be used to represent the value during runtime:
export class Point {
constructor(
public latitude: number,
public longitude: number,
) {
}
}
然后是映射类型:
¥Then the mapping type:
export class PointType extends Type<Point | undefined, string | undefined> {
convertToDatabaseValue(value: Point | undefined): string | undefined {
if (!value) {
return value;
}
return `point(${value.latitude} ${value.longitude})`;
}
convertToJSValue(value: string | undefined): Point | undefined {
const m = value?.match(/point\((-?\d+(\.\d+)?) (-?\d+(\.\d+)?)\)/i);
if (!m) {
return undefined;
}
return new Point(+m[1], +m[3]);
}
convertToJSValueSQL(key: string) {
return `ST_AsText(${key})`;
}
convertToDatabaseValueSQL(key: string) {
return `ST_PointFromText(${key})`;
}
getColumnType(): string {
return 'point';
}
}
现在让我们定义一个实体:
¥Now let's define an entity:
@Entity()
export class Location {
@PrimaryKey()
id!: number;
@Property({ type: PointType })
point?: Point;
}
...并使用它:
¥...and use it:
const loc = new Location();
loc.point = new Point(1.23, 4.56);
await em.persist(loc).flush();
em.clear();
const loc1 = await em.findOneOrFail(Location, loc.id);
// update it
loc1.point = new Point(2.34, 9.87);
await em.flush();
这将导致以下查询:
¥This will result in following queries:
begin
insert into `location` (`point`) values (ST_PointFromText('point(1.23 4.56)'))
commit
select `e0`.*, ST_AsText(`e0`.point) as point from `location` as `e0` where `e0`.`id` = ? limit ?
begin
update `location` set `point` = ST_PointFromText('point(2.34 9.87)') where `id` = ?
commit
我们在这里进行两步转换。在第一步中,我们将 Point 对象转换为字符串表示形式,然后再保存到数据库(在 convertToDatabaseValue 方法中),并在从数据库获取值(在 convertToJSValue 方法中)后将其转换回对象。
¥We do a 2-step conversion here. In the first step, we convert the Point object into a string representation before saving to the database (in the convertToDatabaseValue method) and back into an object after fetching the value from the database (in the convertToJSValue method).
字符串表示格式的格式称为众所周知的文本 (WKT)。这种格式的优点是,它既易于阅读,又可被 MySQL 解析。
¥The format of the string representation format is called Well-known text (WKT). The advantage of this format is, that it is both human-readable and parsable by MySQL.
在内部,MySQL 以与 WKT 格式不同的二进制格式存储几何值。因此,我们需要让 MySQL 将 WKT 表示转换为其内部格式。
¥Internally, MySQL stores geometry values in a binary format that is not identical to the WKT format. So, we need to let MySQL transform the WKT representation into its internal format.
这是 convertToJSValueSQL
和 convertToDatabaseValueSQL
方法发挥作用的地方。
¥This is where the convertToJSValueSQL
and convertToDatabaseValueSQL
methods come into play.
此方法将 sql 表达式(Point 的 WKT 表示)封装到 MySQL 函数 ST_PointFromText 和 ST_AsText 中,这两个函数将 WKT 字符串转换为 MySQL 的内部格式或从 MySQL 的内部格式转换为 WKT 字符串。
¥This methods wrap a sql expression (the WKT representation of the Point) into MySQL functions ST_PointFromText and ST_AsText which convert WKT strings to and from the internal format of MySQL.
使用 DQL 查询时,
convertToJSValueSQL
和convertToDatabaseValueSQL
方法仅适用于 SELECT 子句中的标识变量和路径表达式。WHERE 子句中的表达式未封装!¥When using DQL queries, the
convertToJSValueSQL
andconvertToDatabaseValueSQL
methods only apply to identification variables and path expressions in SELECT clauses. Expressions in WHERE clauses are not wrapped!
MikroORM 提供的类型
¥Types provided by MikroORM
MikroORM 提供的类型很少。它们都旨在为所有驱动程序提供类似的体验,即使驱动程序不支持该特定功能。
¥There are few types provided by MikroORM. All of them aim to provide similar experience among all the drivers, even if the particular feature is not supported out of box by the driver.
从 v5 开始,我们还可以使用从 core
包导出的 type
映射。它包含 ORM 提供的所有映射类型的映射,允许自动补齐。
¥Since v5, we can also use the type
map exported from the core
package. It contains a map of all mapped types provided by the ORM, allowing autocomplete.
import { Property, types } from '@mikro-orm/core';
@Property({ type: types.bigint, nullable: true })
largeNumber?: string; // bigints are mapped to strings so we don't loose precision
相同的映射也导出了快捷方式
t
。¥Same map is also exported shortcut
t
.
映射定义如下:
¥The map is defined as follows:
export const types = {
date: DateType,
time: TimeType,
datetime: DateTimeType,
bigint: BigIntType,
blob: BlobType,
uint8array: Uint8ArrayType,
array: ArrayType,
enumArray: EnumArrayType,
enum: EnumType,
json: JsonType,
integer: IntegerType,
smallint: SmallIntType,
tinyint: TinyIntType,
mediumint: MediumIntType,
float: FloatType,
double: DoubleType,
boolean: BooleanType,
decimal: DecimalType,
character: CharacterType,
string: StringType,
uuid: UuidType,
text: TextType,
interval: IntervalType,
unknown: UnknownType,
} as const;
ArrayType
在 PostgreSQL 和 MongoDB 中,它使用原生数组,否则它会将值连接到用逗号分隔的字符串中。这意味着你不能将包含逗号的值与 ArrayType
一起使用(但你可以创建自定义数组类型来处理这种情况,例如通过使用不同的分隔符)。
¥In PostgreSQL and MongoDB, it uses native arrays, otherwise it concatenates the values into string separated by commas. This means that you can't use values that contain comma with the ArrayType
( but you can create custom array type that will handle this case, e.g. by using different separator).
默认情况下,从类型返回字符串数组。你还可以拥有数字或其他数据类型的数组 - 为此,你需要实现用于将数组值转换为正确类型的自定义 hydrate
方法。
¥By default, array of strings is returned from the type. You can also have arrays of numbers or other data types - to do so, you will need to implement custom hydrate
method that is used for converting the array values to the right type.
如果
type
设置为array
(reflect-metadata 的默认行为)或string[]
或number[]
(手动或通过 ts-morph),则将自动使用ArrayType
。如果是number[]
,它将自动处理转换为数字。这意味着以下示例都会自动使用ArrayType
(但使用 reflect-metadata 时,除非我们手动将类型指定为 `type: 'number[]',否则我们将为两者提供一个字符串数组)¥
ArrayType
will be used automatically iftype
is set toarray
(default behaviour of reflect-metadata) orstring[]
ornumber[]
(either manually or via ts-morph). In case ofnumber[]
it will automatically handle the conversion to numbers. This means that the following examples would both have theArrayType
used automatically (but with reflect-metadata we would have a string array for both unless we specify the type manually as `type: 'number[]')
@Property({ type: ArrayType, nullable: true })
stringArray?: string[];
@Property({ type: new ArrayType(i => +i), nullable: true })
numericArray?: number[];
扩展 ArrayType
¥Extending ArrayType
你还可以将数组项映射到更复杂的类型,如对象。考虑以下将 date[]
列映射到具有 date
字符串属性的对象数组的示例:
¥You can also map the array items to more complex types like objects. Consider the following example of mapping a date[]
column to array of objects with date
string property:
import { ArrayType } from '@mikro-orm/core';
export interface CalendarDate {
date: string;
}
export class CalendarDateArrayType extends ArrayType<CalendarDate> {
constructor() {
super(
date => ({ date }), // to JS
d => d.date, // to DB
);
}
getColumnType(): string {
return 'date[]';
}
}
@Property({ type: CalendarDateArrayType })
favoriteDays!: CalendarDate[];
BigIntType
自 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.
DecimalType
DecimalType 表示 decimal
或 numeric
列类型。默认情况下,它映射到 JS string
,因为将其映射到 number
可能会导致精度丢失。如果你对此没有异议,你可以使用其构造函数强制映射到 number
(就像 BigIntType
一样)。
¥DecimalType represents a decimal
or numeric
column type. By default, it maps to a JS string
, as mapping it to number
could result is precision lost. If you are fine with that, you can force mapping to a number
with its constructor (just like with the BigIntType
).
@Property({ type: DecimalType })
price1: string;
@PrimaryKey({ type: new DecimalType('number') })
price2: number;
BlobType
Blob 类型可用于在数据库中存储二进制数据。
¥Blob type can be used to store binary data in the database.
如果你将类型提示指定为
Buffer
,则会自动使用BlobType
。这意味着即使没有显式type: BlobType
选项(同时使用 reflect-metadata 和 ts-morph 提供程序),以下示例也应该可以工作。¥
BlobType
will be used automatically if you specify the type hint asBuffer
. This means that the following example should work even without the explicittype: BlobType
option (with both reflect-metadata and ts-morph providers).
@Property({ type: BlobType, nullable: true })
blob?: Buffer;
Uint8ArrayType
Uint8Array 类型可用于将二进制数据存储在数据库中。
¥Uint8Array type can be used to store binary data in the database.
如果你将类型提示指定为
Uint8Array
,则会自动使用Uint8ArrayType
。这意味着即使没有显式type: Uint8ArrayType
选项(同时使用 reflect-metadata 和 ts-morph 提供程序),以下示例也应该可以工作。¥
Uint8ArrayType
will be used automatically if you specify the type hint asUint8Array
. This means that the following example should work even without the explicittype: Uint8ArrayType
option (with both reflect-metadata and ts-morph providers).
@Property({ type: Uint8ArrayType, nullable: true })
blob?: Uint8Array;
JsonType
要存储对象,我们可以使用 JsonType
。由于有些驱动程序会自动处理对象,而有些则不会,因此此类型将以独立于驱动程序的方式处理序列化(仅在需要时调用 parse
和 stringify
)。
¥To store objects we can use JsonType
. As some drivers are handling objects automatically and some don't, this type will handle the serialization in a driver independent way (calling parse
and stringify
only when needed).
@Property({ type: JsonType, nullable: true })
object?: { foo: string; bar: number };
DateType
要存储没有时间信息的日期,我们可以使用 DateType
。它确实使用 date
列类型并将其映射到 string
。
¥To store dates without time information, we can use DateType
. It does use date
column type and maps it to a string
.
@Property({ type: DateType, nullable: true })
born?: string;
TimeType
与 DateType
相反,为了仅存储时间信息,我们可以使用 TimeType
。它将使用 time
列类型,运行时类型为字符串。
¥As opposed to the DateType
, to store only the time information, we can use TimeType
. It will use the time
column type, the runtime type is string.
@Property({ type: TimeType, nullable: true })
bornTime?: string;