Saltar al contenido principal

Entidades de Árbol

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 →

TypeORM admite los patrones de Lista de Adyacencia y Tabla de Clausura para almacenar estructuras de árbol. Para aprender más sobre tablas jerárquicas, revisa esta excelente presentación de Bill Karwin.

Lista de Adyacencia

La Lista de Adyacencia es un modelo simple con autoreferencia. Nota que TreeRepository no soporta Lista de Adyacencia. La ventaja de este enfoque es su simplicidad, un inconveniente es que no puedes cargar árboles grandes de una vez debido a limitaciones de joins. Para conocer más beneficios y usos de Listas de Adyacencia, revisa este artículo de Matthew Schinckel. Ejemplo:

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

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

@Column()
name: string

@Column()
description: string

@ManyToOne((type) => Category, (category) => category.children)
parent: Category

@OneToMany((type) => Category, (category) => category.parent)
children: Category[]
}

Conjunto Anidado (Nested Set)

El Conjunto Anidado es otro patrón para almacenar estructuras de árbol en bases de datos. Es muy eficiente para lecturas, pero deficiente para escrituras. No puedes tener múltiples raíces en conjuntos anidados. Ejemplo:

import {
Entity,
Tree,
Column,
PrimaryGeneratedColumn,
TreeChildren,
TreeParent,
TreeLevelColumn,
} from "typeorm"

@Entity()
@Tree("nested-set")
export class Category {
@PrimaryGeneratedColumn()
id: number

@Column()
name: string

@TreeChildren()
children: Category[]

@TreeParent()
parent: Category
}

Ruta Materializada (Materialized Path)

La Ruta Materializada (también llamada Enumeración de Rutas) es otro patrón para almacenar estructuras de árbol en bases de datos. Es simple y efectivo. Ejemplo:

import {
Entity,
Tree,
Column,
PrimaryGeneratedColumn,
TreeChildren,
TreeParent,
TreeLevelColumn,
} from "typeorm"

@Entity()
@Tree("materialized-path")
export class Category {
@PrimaryGeneratedColumn()
id: number

@Column()
name: string

@TreeChildren()
children: Category[]

@TreeParent()
parent: Category
}

Tabla de Clausura (Closure Table)

La Tabla de Clausura almacena relaciones entre padre e hijo en una tabla separada de manera especial. Es eficiente tanto en lectura como escritura. Ejemplo:

import {
Entity,
Tree,
Column,
PrimaryGeneratedColumn,
TreeChildren,
TreeParent,
TreeLevelColumn,
} from "typeorm"

@Entity()
@Tree("closure-table")
export class Category {
@PrimaryGeneratedColumn()
id: number

@Column()
name: string

@TreeChildren()
children: Category[]

@TreeParent()
parent: Category
}

Puedes especificar el nombre de la tabla de clausura y/o nombres de columnas configurando el parámetro opcional options en @Tree("closure-table", options). ancestorColumnName y descandantColumnName son funciones callback que reciben los metadatos de la columna primaria y devuelven el nombre de la columna.

@Tree("closure-table", {
closureTableName: "category_closure",
ancestorColumnName: (column) => "ancestor_" + column.propertyName,
descendantColumnName: (column) => "descendant_" + column.propertyName,
})

Trabajando con entidades de árbol

Para vincular entidades de árbol entre sí, se requiere establecer el padre en la entidad hija y luego guardarlas. Por ejemplo:

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

const a11 = new Category()
a11.name = "a11"
a11.parent = a1
await dataSource.manager.save(a11)

const a12 = new Category()
a12.name = "a12"
a12.parent = a1
await dataSource.manager.save(a12)

const a111 = new Category()
a111.name = "a111"
a111.parent = a11
await dataSource.manager.save(a111)

const a112 = new Category()
a112.name = "a112"
a112.parent = a11
await dataSource.manager.save(a112)

Para cargar dicho árbol usa TreeRepository:

const trees = await dataSource.manager.getTreeRepository(Category).findTrees()

trees será lo siguiente:

[
{
"id": 1,
"name": "a1",
"children": [
{
"id": 2,
"name": "a11",
"children": [
{
"id": 4,
"name": "a111"
},
{
"id": 5,
"name": "a112"
}
]
},
{
"id": 3,
"name": "a12"
}
]
}
]

Existen otros métodos especiales para trabajar con entidades de árbol a través de TreeRepository:

  • findTrees - Devuelve todos los árboles en la base de datos con todos sus hijos, hijos de hijos, etc.
const treeCategories = await dataSource.manager
.getTreeRepository(Category)
.findTrees()
// returns root categories with sub categories inside

const treeCategoriesWithLimitedDepth = await dataSource.manager
.getTreeRepository(Category)
.findTrees({ depth: 2 })
// returns root categories with sub categories inside, up to depth 2
  • findRoots - Las raíces son entidades sin ancestros. Encuentra todas ellas. No carga los hijos de las hojas.
const rootCategories = await dataSource.manager
.getTreeRepository(Category)
.findRoots()
// returns root categories without sub categories inside
  • findDescendants - Obtiene todos los hijos (descendientes) de la entidad dada. Los devuelve en un array plano.
const children = await dataSource.manager
.getTreeRepository(Category)
.findDescendants(parentCategory)
// returns all direct subcategories (without its nested categories) of a parentCategory
  • findDescendantsTree - Obtiene todos los hijos (descendientes) de la entidad dada. Los devuelve en una estructura de árbol anidada.
const childrenTree = await repository.findDescendantsTree(parentCategory)
// returns all direct subcategories (with its nested categories) of a parentCategory
const childrenTreeWithLimitedDepth = await repository.findDescendantsTree(
parentCategory,
{ depth: 2 },
)
// returns all direct subcategories (with its nested categories) of a parentCategory, up to depth 2
  • createDescendantsQueryBuilder - Crea un query builder para obtener descendientes de entidades en un árbol.
const children = await repository
.createDescendantsQueryBuilder(
"category",
"categoryClosure",
parentCategory,
)
.andWhere("category.type = 'secondary'")
.getMany()
  • countDescendants - Obtiene el número de descendientes de la entidad.
const childrenCount = await dataSource.manager
.getTreeRepository(Category)
.countDescendants(parentCategory)
  • findAncestors - Obtiene todos los padres (ancestros) de la entidad dada. Los devuelve en un array plano.
const parents = await repository.findAncestors(childCategory)
// returns all direct childCategory's parent categories (without "parent of parents")
  • findAncestorsTree - Obtiene todos los padres (ancestros) de la entidad dada. Los devuelve en una estructura de árbol anidada.
const parentsTree = await dataSource.manager
.getTreeRepository(Category)
.findAncestorsTree(childCategory)
// returns all direct childCategory's parent categories (with "parent of parents")
  • createAncestorsQueryBuilder - Crea un query builder para obtener ancestros de entidades en un árbol.
const parents = await repository
.createAncestorsQueryBuilder("category", "categoryClosure", childCategory)
.andWhere("category.type = 'secondary'")
.getMany()
  • countAncestors - Obtiene el número de ancestros de la entidad.
const parentsCount = await dataSource.manager
.getTreeRepository(Category)
.countAncestors(childCategory)

Para los siguientes métodos se pueden pasar opciones:

  • findTrees

  • findRoots

  • findDescendants

  • findDescendantsTree

  • findAncestors

  • findAncestorsTree

Las siguientes opciones están disponibles:

  • relations - Indica qué relaciones de la entidad deben cargarse (en formato simplificado de left join).

Ejemplos:

const treeCategoriesWithRelations = await dataSource.manager
.getTreeRepository(Category)
.findTrees({
relations: ["sites"],
})
// automatically joins the sites relation

const parentsWithRelations = await dataSource.manager
.getTreeRepository(Category)
.findAncestors(childCategory, {
relations: ["members"],
})
// returns all direct childCategory's parent categories (without "parent of parents") and joins the 'members' relation