Zum Hauptinhalt springen

Sicht-Entitäten

Inoffizielle Beta-Übersetzung

Diese Seite wurde von PageTurner AI übersetzt (Beta). Nicht offiziell vom Projekt unterstützt. Fehler gefunden? Problem melden →

Was ist eine ViewEntity?

Eine Sicht-Entität ist eine Klasse, die auf eine Datenbank-View abgebildet wird. Du erstellst eine Sicht-Entität, indem du eine neue Klasse definierst und sie mit @ViewEntity() markierst:

@ViewEntity() akzeptiert folgende Optionen:

  • name - Name der View. Wenn nicht angegeben, wird der View-Name aus dem Klassennamen der Entität generiert.

  • database - Datenbankname im ausgewählten DB-Server.

  • schema - Name des Schemas.

  • expression - View-Definition. Erforderlicher Parameter.

  • dependsOn - Liste anderer Views, von denen die aktuelle View abhängt. Wenn deine View in ihrer Definition eine andere View verwendet, kannst du sie hier hinzufügen, damit Migrationen in der richtigen Reihenfolge generiert werden.

expression kann ein String mit korrekt maskierten Spalten und Tabellen sein (abhängig von der verwendeten Datenbank, hier PostgreSQL-Beispiel):

@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"
`
})

oder eine Instanz von 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")
})

Hinweis: Parameter-Binding wird aufgrund von Treibereinschränkungen nicht unterstützt. Verwende stattdessen Literal-Parameter.

@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
})

Jede Sicht-Entität muss in deinen DataSource-Optionen registriert werden:

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],
})

Spalten von Sicht-Entitäten

Um Daten aus der View korrekt auf Entitätsspalten abzubilden, musst du Spalten mit dem @ViewColumn()-Dekorator markieren und diese Spalten als Aliase im SELECT-Statement angeben.

Beispiel mit String-Expression-Definition:

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
}

Beispiel mit 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
}

Optionen für View-Spalten

View-Spaltenoptionen definieren zusätzliche Einstellungen für Sicht-Entitätsspalten, ähnlich wie Spaltenoptionen für reguläre Entitäten.

Du kannst View-Spaltenoptionen in @ViewColumn angeben:

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

Verfügbare Optionen in ViewColumnOptions:

  • name: string - Spaltenname in der Datenbank-View.

  • transformer: { from(value: DatabaseType): EntityType, to(value: EntityType): DatabaseType } - Wandelt Eigenschaften von beliebigem DatabaseType (von der Datenbank unterstützt) in EntityType um. Transformer-Arrays werden ebenfalls unterstützt und beim Lesen in umgekehrter Reihenfolge angewendet. Da Datenbank-Views schreibgeschützt sind, wird transformer.to(value) niemals verwendet.

Indizes für Materialisierte Views

Für Materialisierte Views in PostgreSQL wird die Index-Erstellung unterstützt.

@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
}

Aktuell ist jedoch unique die einzige unterstützte Index-Option für materialisierte Views. Andere Index-Optionen werden ignoriert.

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

Vollständiges Beispiel

Erstellen wir zwei Entitäten und eine View mit aggregierten Daten aus diesen Entitäten:

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
}

Füllen wir dann die Tabellen mit Daten und fragen alle Daten aus der PostCategory-View ab:

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 })

Das Ergebnis in postCategories ist:

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

und in postCategory:

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