Saltar al contenido principal

Entidades de Vista

Traducción Beta No Oficial

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

¿Qué es una ViewEntity?

Una entidad de vista es una clase que se mapea a una vista de base de datos. Puedes crear una entidad de vista definiendo una nueva clase y marcándola con @ViewEntity():

@ViewEntity() acepta las siguientes opciones:

  • name - nombre de la vista. Si no se especifica, el nombre se genera automáticamente a partir del nombre de la clase de la entidad.

  • database - nombre de la base de datos en el servidor de BD seleccionado.

  • schema - nombre del esquema.

  • expression - definición de la vista. Parámetro obligatorio.

  • dependsOn - Lista de otras vistas de las que depende la vista actual. Si tu vista utiliza otra vista en su definición, puedes agregarla aquí para que las migraciones se generen en el orden correcto.

expression puede ser una cadena con columnas y tablas escapadas correctamente, dependiendo de la base de datos utilizada (postgres en el ejemplo):

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

o una instancia de 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")
})

Nota: el binding de parámetros no es compatible debido a limitaciones de los drivers. Utiliza parámetros literales en su lugar.

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

Cada entidad de vista debe registrarse en las opciones de tu fuente de datos:

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

Columnas de entidades de vista

Para mapear datos de la vista a las columnas correctas de la entidad, debes marcar las columnas de la entidad con el decorador @ViewColumn() y especificar estas columnas como alias en la sentencia SELECT.

ejemplo con definición de expresión de cadena:

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
}

ejemplo usando 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
}

Opciones de columnas de vista

Las opciones de columna de vista definen configuraciones adicionales para las columnas de tu entidad de vista, similares a las opciones de columna para entidades regulares.

Puedes especificar opciones de columna de vista en @ViewColumn:

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

Lista de opciones disponibles en ViewColumnOptions:

  • name: string - Nombre de la columna en la vista de base de datos.

  • transformer: { from(value: DatabaseType): EntityType, to(value: EntityType): DatabaseType } - Se utiliza para transformar propiedades de tipo arbitrario DatabaseType (compatible con la base de datos) a un tipo EntityType. También se admiten arrays de transformers, que se aplican en orden inverso al leer. Ten en cuenta que, al ser las vistas de base de datos de solo lectura, transformer.to(value) nunca se utilizará.

Índices para vistas materializadas

Existe soporte para crear índices en vistas materializadas si usas 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
}

Sin embargo, unique es actualmente la única opción admitida para índices en vistas materializadas. El resto de opciones de índices serán ignoradas.

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

Ejemplo completo

Creemos dos entidades y una vista que contenga datos agregados de estas entidades:

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
}

luego llenamos estas tablas con datos y solicitamos todos los datos de la vista 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 })

el resultado en postCategories será:

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

y en postCategory:

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