Entità ad Albero
Questa pagina è stata tradotta da PageTurner AI (beta). Non ufficialmente approvata dal progetto. Hai trovato un errore? Segnala problema →
TypeORM supporta i modelli Adjacency list e Closure table per memorizzare strutture ad albero. Per approfondire le tabelle gerarchiche, consulta questa eccellente presentazione di Bill Karwin.
Adjacency List
L'Adjacency list è un modello semplice con autoreferenzialità. Nota che TreeRepository non supporta l'Adjacency list. Il vantaggio di questo approccio è la semplicità, mentre lo svantaggio è l'impossibilità di caricare alberi complessi in una singola operazione a causa delle limitazioni dei join. Per maggiori dettagli su vantaggi e utilizzo, leggi questo articolo di Matthew Schinckel. Esempio:
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[]
}
Nested Set
Il Nested Set è un altro pattern per memorizzare strutture ad albero nel database. È molto efficiente in lettura ma inefficiente in scrittura. Non consente di avere più radici nello stesso nested set. Esempio:
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
}
Materialized Path (detto anche Path Enumeration)
Il Materialized Path (noto anche come Path Enumeration) è un ulteriore pattern per memorizzare strutture ad albero nel database. È semplice ed efficace. Esempio:
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
}
Closure Table
La Closure Table memorizza le relazioni genitore-figlio in una tabella separata con una struttura speciale. È efficiente sia in lettura che in scrittura. Esempio:
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
}
Puoi specificare il nome della closure table e/o i nomi delle colonne tramite il parametro opzionale options in @Tree("closure-table", options). ancestorColumnName e descandantColumnName sono funzioni di callback che ricevono i metadati della colonna primaria e restituiscono il nome della colonna.
@Tree("closure-table", {
closureTableName: "category_closure",
ancestorColumnName: (column) => "ancestor_" + column.propertyName,
descendantColumnName: (column) => "descendant_" + column.propertyName,
})
Utilizzo delle entità ad albero
Per collegare entità ad albero tra loro, è necessario impostare il genitore nell'entità figlia e poi salvarle. Ad esempio:
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)
Per caricare l'albero, usa TreeRepository:
const trees = await dataSource.manager.getTreeRepository(Category).findTrees()
trees avrà questa struttura:
[
{
"id": 1,
"name": "a1",
"children": [
{
"id": 2,
"name": "a11",
"children": [
{
"id": 4,
"name": "a111"
},
{
"id": 5,
"name": "a112"
}
]
},
{
"id": 3,
"name": "a12"
}
]
}
]
Sono disponibili altri metodi specifici per lavorare con le entità ad albero tramite TreeRepository:
findTrees- Restituisce tutti gli alberi nel database con tutti i loro figli, figli dei figli, ecc.
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- Le radici sono entità senza antenati. Le trova tutte. Non carica i figli delle foglie.
const rootCategories = await dataSource.manager
.getTreeRepository(Category)
.findRoots()
// returns root categories without sub categories inside
findDescendants- Ottiene tutti i figli (discendenti) dell'entità specificata. Li restituisce in un array piatto.
const children = await dataSource.manager
.getTreeRepository(Category)
.findDescendants(parentCategory)
// returns all direct subcategories (without its nested categories) of a parentCategory
findDescendantsTree- Ottiene tutti i figli (discendenti) dell'entità specificata. Li restituisce in una struttura ad albero - annidati l'uno nell'altro.
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 per ottenere i discendenti delle entità in un albero.
const children = await repository
.createDescendantsQueryBuilder(
"category",
"categoryClosure",
parentCategory,
)
.andWhere("category.type = 'secondary'")
.getMany()
countDescendants- Restituisce il numero di discendenti dell'entità.
const childrenCount = await dataSource.manager
.getTreeRepository(Category)
.countDescendants(parentCategory)
findAncestors- Ottiene tutti i genitori (antenati) dell'entità specificata. Li restituisce in un array piatto.
const parents = await repository.findAncestors(childCategory)
// returns all direct childCategory's parent categories (without "parent of parents")
findAncestorsTree- Ottiene tutti i genitori (antenati) dell'entità specificata. Li restituisce in una struttura ad albero - annidati l'uno nell'altro.
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 per ottenere gli antenati delle entità in un albero.
const parents = await repository
.createAncestorsQueryBuilder("category", "categoryClosure", childCategory)
.andWhere("category.type = 'secondary'")
.getMany()
countAncestors- Restituisce il numero di antenati dell'entità.
const parentsCount = await dataSource.manager
.getTreeRepository(Category)
.countAncestors(childCategory)
Per i seguenti metodi possono essere passate opzioni:
-
findTrees
-
findRoots
-
findDescendants
-
findDescendantsTree
-
findAncestors
-
findAncestorsTree
Sono disponibili le seguenti opzioni:
relations- Specifica quali relazioni dell'entità devono essere caricate (forma semplificata di left join).
Esempi:
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