1"use strict";(globalThis.webpackChunk_typeorm_docs||=[]).push([[8870],{5465(e,n,t){t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>o,default:()=>h,frontMatter:()=>i,metadata:()=>r,toc:()=>l});const r=JSON.parse('{"id":"entity/tree-entities","title":"Tree Entities","description":"TypeORM supports the Adjacency list and Closure table patterns for storing tree structures.","source":"@site/docs/entity/4-tree-entities.md","sourceDirName":"entity","slug":"/entity/tree-entities","permalink":"/docs/entity/tree-entities","draft":false,"unlisted":false,"editUrl":"https://github.com/typeorm/typeorm/tree/master/docs/docs/entity/4-tree-entities.md","tags":[],"version":"current","sidebarPosition":4,"frontMatter":{},"sidebar":"tutorialSidebar","previous":{"title":"Entity Inheritance","permalink":"/docs/entity/entity-inheritance"},"next":{"title":"View Entities","permalink":"/docs/entity/view-entities"}}');var a=t(1987),s=t(7008);const i={},o="Tree Entities",c={},l=[{value:"Adjacency list",id:"adjacency-list",level:2},{value:"Nested set",id:"nested-set",level:2},{value:"Materialized Path (aka Path Enumeration)",id:"materialized-path-aka-path-enumeration",level:2},{value:"Closure table",id:"closure-table",level:2},{value:"Working with tree entities",id:"working-with-tree-entities",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",header:"header",li:"li",p:"p",pre:"pre",ul:"ul",...(0,s.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(n.header,{children:(0,a.jsx)(n.h1,{id:"tree-entities",children:"Tree Entities"})}),"\n",(0,a.jsxs)(n.p,{children:["TypeORM supports the Adjacency list and Closure table patterns for storing tree structures.\nTo learn more about the hierarchy table take a look at ",(0,a.jsx)(n.a,{href:"https://www.slideshare.net/billkarwin/models-for-hierarchical-data",children:"this awesome presentation by Bill Karwin"}),"."]}),"\n",(0,a.jsx)(n.h2,{id:"adjacency-list",children:"Adjacency list"}),"\n",(0,a.jsxs)(n.p,{children:["Adjacency list is a simple model with self-referencing.\nNote that TreeRepository doesn't support Adjacency list.\nThe benefit of this approach is simplicity,\na drawback is that you can't load big trees all at once because of join limitations.\nTo learn more about the benefits and use of Adjacency Lists look at ",(0,a.jsx)(n.a,{href:"http://schinckel.net/2014/09/13/long-live-adjacency-lists/",children:"this article by Matthew Schinckel"}),".\nExample:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:'import {\n Entity,\n Column,\n PrimaryGeneratedColumn,\n ManyToOne,\n OneToMany,\n} from "typeorm"\n\n@Entity()\nexport class Category {\n @PrimaryGeneratedColumn()\n id: number\n\n @Column()\n name: string\n\n @Column()\n description: string\n\n @ManyToOne((type) => Category, (category) => category.children)\n parent: Category\n\n @OneToMany((type) => Category, (category) => category.parent)\n children: Category[]\n}\n'})}),"\n",(0,a.jsx)(n.h2,{id:"nested-set",children:"Nested set"}),"\n",(0,a.jsx)(n.p,{children:"Nested set is another pattern of storing tree structures in the database.\nIt is very efficient for reads, but bad for writes.\nYou cannot have multiple roots in the nested set.\nExample:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:'import {\n Entity,\n Tree,\n Column,\n PrimaryGeneratedColumn,\n TreeChildren,\n TreeParent,\n TreeLevelColumn,\n} from "typeorm"\n\n@Entity()\n@Tree("nested-set")\nexport class Category {\n @PrimaryGeneratedColumn()\n id: number\n\n @Column()\n name: string\n\n @TreeChildren()\n children: Category[]\n\n @TreeParent()\n parent: Category\n}\n'})}),"\n",(0,a.jsx)(n.h2,{id:"materialized-path-aka-path-enumeration",children:"Materialized Path (aka Path Enumeration)"}),"\n",(0,a.jsx)(n.p,{children:"Materialized Path (also called Path Enumeration) is another pattern of storing tree structures in the database.\nIt is simple and effective.\nExample:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:'import {\n Entity,\n Tree,\n Column,\n PrimaryGeneratedColumn,\n TreeChildren,\n TreeParent,\n TreeLevelColumn,\n} from "typeorm"\n\n@Entity()\n@Tree("materialized-path")\nexport class Category {\n @PrimaryGeneratedColumn()\n id: number\n\n @Column()\n name: string\n\n @TreeChildren()\n children: Category[]\n\n @TreeParent()\n parent: Category\n}\n'})}),"\n",(0,a.jsx)(n.h2,{id:"closure-table",children:"Closure table"}),"\n",(0,a.jsx)(n.p,{children:"Closure table stores relations between parent and child in a separate table in a special way.\nIt's efficie
1nt in both reading and writing.\nExample:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:'import {\n Entity,\n Tree,\n Column,\n PrimaryGeneratedColumn,\n TreeChildren,\n TreeParent,\n TreeLevelColumn,\n} from "typeorm"\n\n@Entity()\n@Tree("closure-table")\nexport class Category {\n @PrimaryGeneratedColumn()\n id: number\n\n @Column()\n name: string\n\n @TreeChildren()\n children: Category[]\n\n @TreeParent()\n parent: Category\n}\n'})}),"\n",(0,a.jsxs)(n.p,{children:["You can specify the closure table name and/or closure table column names by setting optional parameter ",(0,a.jsx)(n.code,{children:"options"})," into ",(0,a.jsx)(n.code,{children:'@Tree("closure-table", options)'}),". ",(0,a.jsx)(n.code,{children:"ancestorColumnName"})," and ",(0,a.jsx)(n.code,{children:"descendantColumnName"})," are callback functions, which receive the primary column's metadata and return the column's name. ",(0,a.jsx)(n.code,{children:"closureTableName"})," is the argument passed to the DataSource's naming strategy's closureJunctionTableName and defaults to parent entity's ",(0,a.jsx)(n.code,{children:"tableNameWithoutPrefix"}),". ",(0,a.jsx)(n.code,{children:"closureTableSchema"})," specifies the schema name where the closure table has to be saved in and defaults to the parent entity's schema (if provided to the parent Entity's decorator options) or the dataSource schema."]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-ts",children:'@Tree("closure-table", {\n closureTableName: "category",\n closureTableSchema: "closures_schema",\n ancestorColumnName: (column) => "ancestor_" + column.propertyName,\n descendantColumnName: (column) => "descendant_" + column.propertyName,\n})\n'})}),"\n",(0,a.jsx)(n.h2,{id:"working-with-tree-entities",children:"Working with tree entities"}),"\n",(0,a.jsx)(n.p,{children:"To bind tree entities to each other, it is required to set the parent in the child entity and then save them.\nfor example:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:'const a1 = new Category()\na1.name = "a1"\nawait dataSource.manager.save(a1)\n\nconst a11 = new Category()\na11.name = "a11"\na11.parent = a1\nawait dataSource.manager.save(a11)\n\nconst a12 = new Category()\na12.name = "a12"\na12.parent = a1\nawait dataSource.manager.save(a12)\n\nconst a111 = new Category()\na111.name = "a111"\na111.parent = a11\nawait dataSource.manager.save(a111)\n\nconst a112 = new Category()\na112.name = "a112"\na112.parent = a11\nawait dataSource.manager.save(a112)\n'})}),"\n",(0,a.jsxs)(n.p,{children:["To load such a tree use ",(0,a.jsx)(n.code,{children:"TreeRepository"}),":"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:"const trees = await dataSource.manager.getTreeRepository(Category).findTrees()\n"})}),"\n",(0,a.jsxs)(n.p,{children:[(0,a.jsx)(n.code,{children:"trees"})," will be the following:"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-json",children:'[\n {\n "id": 1,\n "name": "a1",\n "children": [\n {\n "id": 2,\n "name": "a11",\n "children": [\n {\n "id": 4,\n "name": "a111"\n },\n {\n "id": 5,\n "name": "a112"\n }\n ]\n },\n {\n "id": 3,\n "name": "a12"\n }\n ]\n }\n]\n'})}),"\n",(0,a.jsxs)(n.p,{children:["There are other special methods to work with tree entities through ",(0,a.jsx)(n.code,{children:"TreeRepository"}),":"]}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"findTrees"})," - Returns all trees in the database with all their children, children of children, etc."]}),"\n"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:"const treeCategories = await dataSource.manager\n .getTreeRepository(Category)\n .findTrees()\n// returns root categories with sub categories inside\n\nconst treeCategoriesWithLimitedDepth = await dataSource.manager\n .getTreeRepository(Category)\n .findTrees({ depth: 2 })\n// returns root categories with sub categories inside, up to depth 2\n"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"findRoots"})," - Roots are entities that have no ancestors. Finds them all.\nDoes not load children's leaves."]}),"\n"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:"const rootCategories = await dataSource.manager\n .getTreeRepository(Category)\n .findRoots()\n// returns root categories without sub categories inside\n"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"findDescendants"})," - Gets all children (descendants) of the given entity. Returns them all in a flat array."]}),"\n"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:"const children = await dataSource.manager\n .getTreeRepository(Category)\n .findDescendants(parentCategory)\n// returns all direct subcategories (without its nested categories) of a parentCategory\n"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"findDescendantsTree"})," - Gets all children (descendants) of the given entity. Returns them in a tree - nested into each other."]}
1),"\n"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:"const childrenTree = await repository.findDescendantsTree(parentCategory)\n// returns all direct subcategories (with its nested categories) of a parentCategory\nconst childrenTreeWithLimitedDepth = await repository.findDescendantsTree(\n parentCategory,\n { depth: 2 },\n)\n// returns all direct subcategories (with its nested categories) of a parentCategory, up to depth 2\n"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"createDescendantsQueryBuilder"})," - Creates a query builder used to get descendants of the entities in a tree."]}),"\n"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:'const children = await repository\n .createDescendantsQueryBuilder(\n "category",\n "categoryClosure",\n parentCategory,\n )\n .andWhere("category.type = \'secondary\'")\n .getMany()\n'})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"countDescendants"})," - Gets the number of descendants of the entity."]}),"\n"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:"const childrenCount = await dataSource.manager\n .getTreeRepository(Category)\n .countDescendants(parentCategory)\n"})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"findAnce
1stors"})," - Gets all parents (ancestors) of the given entity. Returns them all in a flat array."]}),"\n"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:'const parents = await repository.findAncestors(childCategory)\n// returns all direct childCategory\'s parent categories (without "parent of parents")\n'})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"findAncestorsTree"})," - Gets all parents (ancestors) of the given entity. Returns them in a tree - nested into each other."]}),"\n"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:'const parentsTree = await dataSource.manager\n .getTreeRepository(Category)\n .findAncestorsTree(childCategory)\n// returns all direct childCategory\'s parent categories (with "parent of parents")\n'})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"createAncestorsQueryBuilder"})," - Creates a query builder used to get the ancestors of the entities in a tree."]}),"\n"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:'const parents = await repository\n .createAncestorsQueryBuilder("category", "categoryClosure", childCategory)\n .andWhere("category.type = \'secondary\'")\n .getMany()\n'})}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"countAncestors"})," - Gets the number of ancestors of the entity."]}),"\n"]}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:"const parentsCount = await dataSource.manager\n .getTreeRepository(Category)\n .countAncestors(childCategory)\n"})}),"\n",(0,a.jsx)(n.p,{children:"For the following methods, options can be passed:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsx)(n.li,{children:"findTrees"}),"\n",(0,a.jsx)(n.li,{children:"findRoots"}),"\n",(0,a.jsx)(n.li,{children:"findDescendants"}),"\n",(0,a.jsx)(n.li,{children:"findDescendantsTree"}
1),"\n",(0,a.jsx)(n.li,{children:"findAncestors"}),"\n",(0,a.jsx)(n.li,{children:"findAncestorsTree"}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"The following options are available:"}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.code,{children:"relations"})," - Indicates what relations of entity should be loaded (simplified left join form)."]}),"\n"]}),"\n",(0,a.jsx)(n.p,{children:"Examples:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-typescript",children:'const treeCategoriesWithRelations = await dataSource.manager\n .getTreeRepository(Category)\n .findTrees({\n relations: ["sites"],\n })\n// automatically joins the sites relation\n\nconst parentsWithRelations = await dataSource.manager\n .getTreeRepository(Category)\n .findAncestors(childCategory, {\n relations: ["members"],\n })\n// returns all direct childCategory\'s parent categories (without "parent of parents") and joins the \'members\' relation\n'})})]})}function h(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,a.jsx)(n,{...e,children:(0,a.jsx)(d,{...e})}):d(e)}},7008(e,n,t){t.d(n,{R:()=>i,x:()=>o});var r=t(1763);const a={},s=r.createContext(a);function i(e){const n=r.useContext(s);return r.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:i(e.components),r.createElement(s.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.