Перейти к основному содержанию

Разделение определения сущности

Неофициальный Бета-перевод

Эта страница переведена PageTurner AI (бета). Не одобрена официально проектом. Нашли ошибку? Сообщить о проблеме →

Определение схем

Вы можете определить сущность и её колонки непосредственно в модели с использованием декораторов. Но некоторые предпочитают определять сущности и их колонки в отдельных файлах, которые в TypeORM называются "схемами сущностей" (entity schemas).

Простой пример определения:

import { EntitySchema } from "typeorm"

export const CategoryEntity = new EntitySchema({
name: "category",
columns: {
id: {
type: Number,
primary: true,
generated: true,
},
name: {
type: String,
},
},
})

Пример со связями:

import { EntitySchema } from "typeorm"

export const PostEntity = new EntitySchema({
name: "post",
columns: {
id: {
type: Number,
primary: true,
generated: true,
},
title: {
type: String,
},
text: {
type: String,
},
},
relations: {
categories: {
type: "many-to-many",
target: "category", // CategoryEntity
},
},
})

Сложный пример:

import { EntitySchema } from "typeorm"

export const PersonSchema = new EntitySchema({
name: "person",
columns: {
id: {
primary: true,
type: "int",
generated: "increment",
},
firstName: {
type: String,
length: 30,
},
lastName: {
type: String,
length: 50,
nullable: false,
},
age: {
type: Number,
nullable: false,
},
countryCode: {
type: String,
length: 2,
foreignKey: {
target: "countries", // CountryEntity
inverseSide: "code",
},
},
cityId: {
type: Number,
foreignKey: {
target: "cities", // CityEntity
},
},
},
checks: [
{ expression: `"firstName" <> 'John' AND "lastName" <> 'Doe'` },
{ expression: `"age" > 18` },
],
indices: [
{
name: "IDX_TEST",
unique: true,
columns: ["firstName", "lastName"],
},
],
uniques: [
{
name: "UNIQUE_TEST",
columns: ["firstName", "lastName"],
},
],
foreignKeys: [
{
target: "cities", // CityEntity
columnNames: ["cityId", "countryCode"],
referencedColumnNames: ["id", "countryCode"],
},
],
})

Чтобы сделать вашу сущность типобезопасной, вы можете определить модель и указать её в схеме:

import { EntitySchema } from "typeorm"

export interface Category {
id: number
name: string
}

export const CategoryEntity = new EntitySchema<Category>({
name: "category",
columns: {
id: {
type: Number,
primary: true,
generated: true,
},
name: {
type: String,
},
},
})

Расширение схем

При использовании подхода с Decorator легко extend базовые колонки до абстрактного класса и просто наследовать его. Например, ваши колонки id, createdAt и updatedAt могут быть определены в такой BaseEntity. Подробнее см. в документации по наследованию с конкретной таблицей.

При использовании подхода EntitySchema это невозможно. Однако вы можете использовать Spread Operator (...) в своих интересах.

Рассмотрим пример Category из предыдущего раздела. Вы можете extract описания базовых колонок для повторного использования в других схемах. Это можно сделать следующим образом:

import { EntitySchemaColumnOptions } from "typeorm"

export const BaseColumnSchemaPart = {
id: {
type: Number,
primary: true,
generated: true,
} as EntitySchemaColumnOptions,
createdAt: {
name: "created_at",
type: "timestamp with time zone",
createDate: true,
} as EntitySchemaColumnOptions,
updatedAt: {
name: "updated_at",
type: "timestamp with time zone",
updateDate: true,
} as EntitySchemaColumnOptions,
}

Теперь вы можете использовать BaseColumnSchemaPart в других моделях схем, например:

export const CategoryEntity = new EntitySchema<Category>({
name: "category",
columns: {
...BaseColumnSchemaPart,
// the CategoryEntity now has the defined id, createdAt, updatedAt columns!
// in addition, the following NEW fields are defined
name: {
type: String,
},
},
})

Вы можете использовать встроенные сущности в моделях схем:

export interface Name {
first: string
last: string
}

export const NameEntitySchema = new EntitySchema<Name>({
name: "name",
columns: {
first: {
type: "varchar",
},
last: {
type: "varchar",
},
},
})

export interface User {
id: string
name: Name
isActive: boolean
}

export const UserEntitySchema = new EntitySchema<User>({
name: "user",
columns: {
id: {
primary: true,
generated: "uuid",
type: "uuid",
},
isActive: {
type: "boolean",
},
},
embeddeds: {
name: {
schema: NameEntitySchema,
prefix: "name_",
},
},
})

Убедитесь, что добавили extended колонки также в интерфейс Category (например, через export interface Category extend BaseEntity).

Наследование с единой таблицей (Single Table Inheritance)

Чтобы использовать наследование с единой таблицей:

  1. Добавьте опцию inheritance в схему родительского класса, указав шаблон наследования ("STI") и дискриминаторную колонку, которая будет хранить имя дочернего класса в каждой строке

  2. Установите опцию type: "entity-child" для схем всех дочерних классов, расширяя колонки родительского класса с помощью синтаксиса spread operator, описанного выше

// entity.ts

export abstract class Base {
id!: number
type!: string
createdAt!: Date
updatedAt!: Date
}

export class A extends Base {
constructor(public a: boolean) {
super()
}
}

export class B extends Base {
constructor(public b: number) {
super()
}
}

export class C extends Base {
constructor(public c: string) {
super()
}
}
// schema.ts

const BaseSchema = new EntitySchema<Base>({
target: Base,
name: "Base",
columns: {
id: {
type: Number,
primary: true,
generated: "increment",
},
type: {
type: String,
},
createdAt: {
type: Date,
createDate: true,
},
updatedAt: {
type: Date,
updateDate: true,
},
},
// NEW: Inheritance options
inheritance: {
pattern: "STI",
column: "type",
},
})

const ASchema = new EntitySchema<A>({
target: A,
name: "A",
type: "entity-child",
// When saving instances of 'A', the "type" column will have the value
// specified on the 'discriminatorValue' property
discriminatorValue: "my-custom-discriminator-value-for-A",
columns: {
...BaseSchema.options.columns,
a: {
type: Boolean,
},
},
})

const BSchema = new EntitySchema<B>({
target: B,
name: "B",
type: "entity-child",
discriminatorValue: undefined, // Defaults to the class name (e.g. "B")
columns: {
...BaseSchema.options.columns,
b: {
type: Number,
},
},
})

const CSchema = new EntitySchema<C>({
target: C,
name: "C",
type: "entity-child",
discriminatorValue: "my-custom-discriminator-value-for-C",
columns: {
...BaseSchema.options.columns,
c: {
type: String,
},
},
})

Использование схем для запросов и вставки данных

Конечно, вы можете использовать определённые схемы в репозиториях или менеджере сущностей так же, как и декораторы. Рассмотрите ранее определённый пример Category (с его Interface и схемой CategoryEntity) для получения данных или управления базой данных.

// request data
const categoryRepository = dataSource.getRepository<Category>(CategoryEntity)
const category = await categoryRepository.findOneBy({
id: 1,
}) // category is properly typed!

// insert a new category into the database
const categoryDTO = {
// note that the ID is autogenerated; see the schema above
name: "new category",
}
const newCategory = await categoryRepository.save(categoryDTO)