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

Сущности представлений (View Entities)

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

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

Что такое ViewEntity?

ViewEntity — это класс, который отображается на представление базы данных. Чтобы создать сущность представления, определите новый класс и пометьте его декоратором @ViewEntity():

@ViewEntity() принимает следующие параметры:

  • name - имя представления. Если не указано, генерируется из имени класса сущности.

  • database - имя базы данных на выбранном сервере БД.

  • schema - имя схемы.

  • expression - определение представления. Обязательный параметр.

  • dependsOn - список других представлений, от которых зависит текущее. Если ваше представление использует другое представление в своём определении, можно добавить его здесь для корректной генерации миграций в правильном порядке.

expression может быть строкой с правильно экранированными столбцами и таблицами (для примера — Postgres):

@ViewEntity({
expression: `
SELECT "post"."id" AS "id", "post"."name" AS "name", "category"."name" AS "categoryName"
FROM "post" "post"
LEFT JOIN "category" "category" ON "post"."categoryId" = "category"."id"
`
})

или экземпляром QueryBuilder

@ViewEntity({
expression: (dataSource: DataSource) => dataSource
.createQueryBuilder()
.select("post.id", "id")
.addSelect("post.name", "name")
.addSelect("category.name", "categoryName")
.from(Post, "post")
.leftJoin(Category, "category", "category.id = post.categoryId")
})

Примечание: привязка параметров не поддерживается из-за ограничений драйверов. Используйте литеральные параметры.

@ViewEntity({
expression: (dataSource: DataSource) => dataSource
.createQueryBuilder()
.select("post.id", "id")
.addSelect("post.name", "name")
.addSelect("category.name", "categoryName")
.from(Post, "post")
.leftJoin(Category, "category", "category.id = post.categoryId")
.where("category.name = :name", { name: "Cars" }) // <-- this is wrong
.where("category.name = 'Cars'") // <-- and this is right
})

Каждая сущность представления должна быть зарегистрирована в настройках источника данных:

import { DataSource } from "typeorm"
import { UserView } from "./entities/UserView"

const dataSource = new DataSource({
type: "mysql",
host: "localhost",
port: 3306,
username: "test",
password: "test",
database: "test",
entities: [UserView],
})

Столбцы сущности представления

Для корректного отображения данных из представления в столбцы сущности пометьте столбцы декоратором @ViewColumn() и укажите их как алиасы в SELECT-выражении.

пример с определением в виде строки:

import { ViewEntity, ViewColumn } from "typeorm"

@ViewEntity({
expression: `
SELECT "post"."id" AS "id", "post"."name" AS "name", "category"."name" AS "categoryName"
FROM "post" "post"
LEFT JOIN "category" "category" ON "post"."categoryId" = "category"."id"
`,
})
export class PostCategory {
@ViewColumn()
id: number

@ViewColumn()
name: string

@ViewColumn()
categoryName: string
}

пример с использованием QueryBuilder:

import { ViewEntity, ViewColumn } from "typeorm"

@ViewEntity({
expression: (dataSource: DataSource) =>
dataSource
.createQueryBuilder()
.select("post.id", "id")
.addSelect("post.name", "name")
.addSelect("category.name", "categoryName")
.from(Post, "post")
.leftJoin(Category, "category", "category.id = post.categoryId"),
})
export class PostCategory {
@ViewColumn()
id: number

@ViewColumn()
name: string

@ViewColumn()
categoryName: string
}

Параметры столбцов представления

Параметры столбцов представления определяют дополнительные настройки, аналогично параметрам столбцов для обычных сущностей.

Параметры можно указать в @ViewColumn:

@ViewColumn({
name: "postName",
// ...
})
name: string;

Доступные параметры в ViewColumnOptions:

  • name: string - имя столбца в представлении базы данных.

  • transformer: { from(value: DatabaseType): EntityType, to(value: EntityType): DatabaseType } - используется для преобразования свойств произвольного типа DatabaseType (поддерживаемого БД) в тип EntityType. Поддерживаются массивы преобразователей, применяемые в обратном порядке при чтении. Поскольку представления доступны только для чтения, transformer.to(value) никогда не используется.

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

Поддерживается создание индексов для материализованных представлений в PostgreSQL.

@ViewEntity({
materialized: true,
expression: (dataSource: DataSource) =>
dataSource
.createQueryBuilder()
.select("post.id", "id")
.addSelect("post.name", "name")
.addSelect("category.name", "categoryName")
.from(Post, "post")
.leftJoin(Category, "category", "category.id = post.categoryId"),
})
export class PostCategory {
@ViewColumn()
id: number

@Index()
@ViewColumn()
name: string

@Index("catname-idx")
@ViewColumn()
categoryName: string
}

Однако в настоящее время для индексов материализованных представлений поддерживается только опция unique. Остальные параметры индексов игнорируются.

@Index("name-idx", { unique: true })
@ViewColumn()
name: string

Полный пример

Создадим две сущности и представление с агрегированными данными из них:

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number

@Column()
name: string
}
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
JoinColumn,
} from "typeorm"
import { Category } from "./Category"

@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number

@Column()
name: string

@Column()
categoryId: number

@ManyToOne(() => Category)
@JoinColumn({ name: "categoryId" })
category: Category
}
import { ViewEntity, ViewColumn, DataSource } from "typeorm"

@ViewEntity({
expression: (dataSource: DataSource) =>
dataSource
.createQueryBuilder()
.select("post.id", "id")
.addSelect("post.name", "name")
.addSelect("category.name", "categoryName")
.from(Post, "post")
.leftJoin(Category, "category", "category.id = post.categoryId"),
})
export class PostCategory {
@ViewColumn()
id: number

@ViewColumn()
name: string

@ViewColumn()
categoryName: string
}

заполним таблицы данными и запросим все данные из представления PostCategory:

import { Category } from "./entities/Category"
import { Post } from "./entities/Post"
import { PostCategory } from "./entities/PostCategory"

const category1 = new Category()
category1.name = "Cars"
await dataSource.manager.save(category1)

const category2 = new Category()
category2.name = "Airplanes"
await dataSource.manager.save(category2)

const post1 = new Post()
post1.name = "About BMW"
post1.categoryId = category1.id
await dataSource.manager.save(post1)

const post2 = new Post()
post2.name = "About Boeing"
post2.categoryId = category2.id
await dataSource.manager.save(post2)

const postCategories = await dataSource.manager.find(PostCategory)
const postCategory = await dataSource.manager.findOneBy(PostCategory, { id: 1 })

результат в postCategories будет:

[ PostCategory { id: 1, name: 'About BMW', categoryName: 'Cars' },
PostCategory { id: 2, name: 'About Boeing', categoryName: 'Airplanes' } ]

а в postCategory:

PostCategory { id: 1, name: 'About BMW', categoryName: 'Cars' }