Relaciones muchos a muchos
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
¿Qué son las relaciones muchos a muchos?
Una relación muchos a muchos ocurre cuando A contiene múltiples instancias de B, y B contiene múltiples instancias de A.
Tomemos como ejemplo las entidades Question y Category.
Una pregunta puede tener múltiples categorías, y cada categoría puede tener múltiples preguntas.
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
}
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(() => Category)
@JoinTable()
categories: Category[]
}
@JoinTable() es obligatorio para relaciones @ManyToMany.
Debes colocar @JoinTable únicamente en el lado propietario (owning side) de la relación.
Este ejemplo generará las siguientes tablas:
+-------------+--------------+----------------------------+
| category |
+-------------+--------------+----------------------------+
| id | int | PRIMARY KEY AUTO_INCREMENT |
| name | varchar(255) | |
+-------------+--------------+----------------------------+
+-------------+--------------+----------------------------+
| question |
+-------------+--------------+----------------------------+
| id | int | PRIMARY KEY AUTO_INCREMENT |
| title | varchar(255) | |
| text | varchar(255) | |
+-------------+--------------+----------------------------+
+-------------+--------------+----------------------------+
| question_categories_category |
+-------------+--------------+----------------------------+
| questionId | int | PRIMARY KEY FOREIGN KEY |
| categoryId | int | PRIMARY KEY FOREIGN KEY |
+-------------+--------------+----------------------------+
Guardar relaciones muchos a muchos
Con las cascadas activadas, puedes guardar esta relación con una sola llamada a save.
const category1 = new Category()
category1.name = "animals"
await dataSource.manager.save(category1)
const category2 = new Category()
category2.name = "zoo"
await dataSource.manager.save(category2)
const question = new Question()
question.title = "dogs"
question.text = "who let the dogs out?"
question.categories = [category1, category2]
await dataSource.manager.save(question)
Eliminar relaciones muchos a muchos
Con las cascadas activadas, puedes eliminar esta relación con una sola llamada a save.
Para eliminar una relación muchos a muchos entre dos registros, remuévela del campo correspondiente y guarda el registro.
const question = await dataSource.getRepository(Question).findOne({
relations: {
categories: true,
},
where: { id: 1 },
})
question.categories = question.categories.filter((category) => {
return category.id !== categoryToRemove.id
})
await dataSource.manager.save(question)
Esto solo eliminará el registro en la tabla de unión. Los registros question y categoryToRemove seguirán existiendo.
Eliminación suave (soft delete) de relaciones con cascada
Este ejemplo muestra cómo funciona la eliminación suave en cascada:
const category1 = new Category()
category1.name = "animals"
const category2 = new Category()
category2.name = "zoo"
const question = new Question()
question.categories = [category1, category2]
const newQuestion = await dataSource.manager.save(question)
await dataSource.manager.softRemove(newQuestion)
En este ejemplo no llamamos a save ni softRemove para category1 y category2, pero se guardarán y eliminarán suavemente de forma automática cuando la cascada de opciones de relación esté activada así:
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToMany,
JoinTable,
} from "typeorm"
import { Category } from "./Category"
@Entity()
export class Question {
@PrimaryGeneratedColumn()
id: number
@ManyToMany(() => Category, (category) => category.questions, {
cascade: true,
})
@JoinTable()
categories: Category[]
}
Cargar relaciones muchos a muchos
Para cargar preguntas con sus categorías asociadas, debes especificar la relación en FindOptions:
const questionRepository = dataSource.getRepository(Question)
const questions = await questionRepository.find({
relations: {
categories: true,
},
})
O usando QueryBuilder puedes unirlas:
const questions = await dataSource
.getRepository(Question)
.createQueryBuilder("question")
.leftJoinAndSelect("question.categories", "category")
.getMany()
Con la carga ansiosa (eager loading) activada en una relación, no necesitas especificarla en el comando find porque SIEMPRE se cargará automáticamente. Si usas QueryBuilder, las relaciones eager están desactivadas y debes usar leftJoinAndSelect para cargarla.
Relaciones bidireccionales
Las relaciones pueden ser unidireccionales o bidireccionales. Las unidireccionales tienen decoradores de relación solo en un lado. Las bidireccionales tienen decoradores en ambos lados de la relación.
Acabamos de crear una relación unidireccional. Hagámosla bidireccional:
import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from "typeorm"
import { Question } from "./Question"
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@ManyToMany(() => Question, (question) => question.categories)
questions: Question[]
}
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(() => Category, (category) => category.questions)
@JoinTable()
categories: Category[]
}
Hemos hecho la relación bidireccional. Nota que la relación inversa no tiene @JoinTable.
@JoinTable debe estar solo en un lado de la relación.
Las relaciones bidireccionales te permiten unir desde ambos lados usando QueryBuilder:
const categoriesWithQuestions = await dataSource
.getRepository(Category)
.createQueryBuilder("category")
.leftJoinAndSelect("category.questions", "question")
.getMany()
Relaciones muchos a muchos con propiedades personalizadas
Si necesitas propiedades adicionales en tu relación muchos a muchos, debes crear una nueva entidad manualmente.
Por ejemplo, si quieres que las entidades Question y Category tengan una relación muchos a muchos con una columna adicional order, entonces necesitas crear una entidad QuestionToCategory con dos relaciones ManyToOne en ambas direcciones y columnas personalizadas:
import { Entity, Column, ManyToOne, PrimaryGeneratedColumn } from "typeorm"
import { Question } from "./question"
import { Category } from "./category"
@Entity()
export class QuestionToCategory {
@PrimaryGeneratedColumn()
public questionToCategoryId: number
@Column()
public questionId: number
@Column()
public categoryId: number
@Column()
public order: number
@ManyToOne(() => Question, (question) => question.questionToCategories)
public question: Question
@ManyToOne(() => Category, (category) => category.questionToCategories)
public category: Category
}
Adicionalmente, deberás agregar una relación como la siguiente en Question y Category:
// category.ts
...
@OneToMany(() => QuestionToCategory, questionToCategory => questionToCategory.category)
public questionToCategories: QuestionToCategory[];
// question.ts
...
@OneToMany(() => QuestionToCategory, questionToCategory => questionToCategory.question)
public questionToCategories: QuestionToCategory[];