Saltar al contenido principal

Preguntas Frecuentes sobre Relaciones

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 →

¿Cómo crear una relación auto-referenciada?

Las relaciones auto-referenciadas son relaciones que apuntan a la misma entidad. Son útiles para almacenar entidades en estructuras jerárquicas tipo árbol. El patrón "lista de adyacencias" se implementa usando relaciones auto-referenciadas. Por ejemplo, para crear un árbol de categorías en tu aplicación: Las categorías pueden contener subcategorías, que a su vez pueden contener otras subcategorías, etc. Las relaciones auto-referenciadas son ideales para este caso. Básicamente, son relaciones normales que tienen como objetivo la propia entidad. Ejemplo:

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

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

@Column()
title: string

@Column()
text: string

@ManyToOne((type) => Category, (category) => category.childCategories)
parentCategory: Category

@OneToMany((type) => Category, (category) => category.parentCategory)
childCategories: Category[]
}

¿Cómo usar el ID de relación sin cargar la relación?

A veces necesitas tener en tu objeto solo el ID del objeto relacionado sin cargarlo completo. Por ejemplo:

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

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

@Column()
gender: string

@Column()
photo: string
}
import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToOne,
JoinColumn,
} from "typeorm"
import { Profile } from "./Profile"

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

@Column()
name: string

@OneToOne((type) => Profile)
@JoinColumn()
profile: Profile
}

Al cargar un usuario sin unir profile, no tendrás información del perfil en el objeto usuario, ni siquiera el ID del perfil:

User {
id: 1,
name: "Umed"
}

Pero si necesitas saber el "ID de perfil" sin cargar todo el perfil, basta con agregar otra propiedad en tu entidad con @Column que se llame exactamente igual que la columna creada por la relación. Ejemplo:

import {
Entity,
PrimaryGeneratedColumn,
Column,
OneToOne,
JoinColumn,
} from "typeorm"
import { Profile } from "./Profile"

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

@Column()
name: string

@Column({ nullable: true })
profileId: number

@OneToOne((type) => Profile)
@JoinColumn()
profile: Profile
}

Eso es todo. La próxima vez que cargues un usuario, contendrá el ID del perfil:

User {
id: 1,
name: "Umed",
profileId: 1
}

¿Cómo cargar relaciones en entidades?

La forma más sencilla es usar la opción relations en FindOptions:

const users = await dataSource.getRepository(User).find({
relations: {
profile: true,
photos: true,
videos: true,
},
})

Una alternativa más flexible es usar QueryBuilder:

const user = await dataSource
.getRepository(User)
.createQueryBuilder("user")
.leftJoinAndSelect("user.profile", "profile")
.leftJoinAndSelect("user.photos", "photo")
.leftJoinAndSelect("user.videos", "video")
.getMany()

Con QueryBuilder puedes usar innerJoinAndSelect en lugar de leftJoinAndSelect (para entender la diferencia entre LEFT JOIN e INNER JOIN consulta la documentación SQL), filtrar relaciones por condiciones, ordenar resultados, etc.

Más información sobre QueryBuilder.

Evita inicializar propiedades de relación

A veces parece útil inicializar propiedades de relación, por ejemplo:

import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToMany,
JoinTable,
} from "typeorm"
import { Category } from "./Category"

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

@Column()
title: string

@Column()
text: string

@ManyToMany((type) => Category, (category) => category.questions)
@JoinTable()
categories: Category[] = [] // see = [] initialization here
}

Sin embargo, en entidades TypeORM esto puede causar problemas. Para entenderlo, primero carguemos una entidad Question SIN inicializador: Al cargar una pregunta, obtendrás un objeto así:

Question {
id: 1,
title: "Question about ..."
}

Al guardar este objeto, categories no se modificará porque no está definido.

Pero con un inicializador, el objeto cargado se vería así:

Question {
id: 1,
title: "Question about ...",
categories: []
}

Al guardar, se comprobarán las categorías vinculadas en la base de datos y se desvincularán todas. ¿Por qué? Porque una relación igual a [] o con elementos se interpreta como que se eliminaron elementos, ya que no hay otra forma de detectar si un objeto se quitó de la entidad.

Por tanto, guardar así causará problemas: eliminará todas las categorías previamente asignadas.

¿Cómo evitarlo? Simplemente no inicialices arrays en tus entidades. La misma regla aplica para constructores: no los inicialices allí tampoco.

Evitar crear restricciones de clave foránea

Por rendimiento, a veces conviene tener relaciones sin restricciones de clave foránea. Puedes controlarlo con la opción createForeignKeyConstraints (por defecto: true).

import { Entity, PrimaryColumn, Column, ManyToOne } from "typeorm"
import { Person } from "./Person"

@Entity()
export class ActionLog {
@PrimaryColumn()
id: number

@Column()
date: Date

@Column()
action: string

@ManyToOne((type) => Person, {
createForeignKeyConstraints: false,
})
person: Person
}

Evitar errores de importación circular

Ejemplo para definir entidades sin causar errores en ciertos entornos: Supongamos que Action.ts y Person.ts se importan mutuamente para una relación muchos-a-muchos. Usamos import type para obtener información de tipo sin generar código JavaScript.

import { Entity, PrimaryColumn, Column, ManytoMany } from "typeorm"
import type { Person } from "./Person"

@Entity()
export class ActionLog {
@PrimaryColumn()
id: number

@Column()
date: Date

@Column()
action: string

@ManyToMany("Person", (person: Person) => person.id)
person: Person
}
import { Entity, PrimaryColumn, ManytoMany } from "typeorm"
import type { ActionLog } from "./Action"

@Entity()
export class Person {
@PrimaryColumn()
id: number

@ManyToMany("ActionLog", (actionLog: ActionLog) => actionLog.id)
log: ActionLog
}