typeorm/docs/embedded-entities.md
Andrea Mouraud e67d704138
feat: nullable embedded entities (#10289)
* feat: nullable embedded entities

* fix: ignore embedded columns if not selected

* test: update tests with changed behaviour

* test: replace integer with varchar for cockroachdb

* docs: add documentation

* fix: nested embedded entities

* test: update nested embedded entities behaviour
2024-01-02 12:29:41 +05:00

4.6 KiB

Embedded Entities

There is an amazing way to reduce duplication in your app (using composition over inheritance) by using embedded columns. Embedded column is a column which accepts a class with its own columns and merges those columns into the current entity's database table. Example:

Let's say we have User, Employee and Student entities. All those entities have few things in common - first name and last name properties

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

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

    @Column()
    firstName: string

    @Column()
    lastName: string

    @Column()
    isActive: boolean
}
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class Employee {
    @PrimaryGeneratedColumn()
    id: string

    @Column()
    firstName: string

    @Column()
    lastName: string

    @Column()
    salary: string
}
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class Student {
    @PrimaryGeneratedColumn()
    id: string

    @Column()
    firstName: string

    @Column()
    lastName: string

    @Column()
    faculty: string
}

What we can do is to reduce firstName and lastName duplication by creating a new class with those columns:

import { Column } from "typeorm"

export class Name {
    @Column()
    first: string

    @Column()
    last: string
}

Then you can "connect" those columns in your entities:

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
import { Name } from "./Name"

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

    @Column(() => Name)
    name: Name

    @Column()
    isActive: boolean
}
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
import { Name } from "./Name"

@Entity()
export class Employee {
    @PrimaryGeneratedColumn()
    id: string

    @Column(() => Name)
    name: Name

    @Column()
    salary: number
}
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"
import { Name } from "./Name"

@Entity()
export class Student {
    @PrimaryGeneratedColumn()
    id: string

    @Column(() => Name)
    name: Name

    @Column()
    faculty: string
}

All columns defined in the Name entity will be merged into user, employee and student:

+-------------+--------------+----------------------------+
|                          user                           |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| nameFirst   | varchar(255) |                            |
| nameLast    | varchar(255) |                            |
| isActive    | boolean      |                            |
+-------------+--------------+----------------------------+

+-------------+--------------+----------------------------+
|                        employee                         |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| nameFirst   | varchar(255) |                            |
| nameLast    | varchar(255) |                            |
| salary      | int(11)      |                            |
+-------------+--------------+----------------------------+

+-------------+--------------+----------------------------+
|                         student                         |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| nameFirst   | varchar(255) |                            |
| nameLast    | varchar(255) |                            |
| faculty     | varchar(255) |                            |
+-------------+--------------+----------------------------+

This way code duplication in the entity classes is reduced. You can use as many columns (or relations) in embedded classes as you need. You even can have nested embedded columns inside embedded classes.

Nullable embedded entities

When all its columns values are null, the embedded entity itself is considered null. Saving the embedded entity as null will set all its columns to null

export class Name {
    @Column({ nullable: true })
    first: string | null

    @Column({ nullable: true })
    last: string | null
}

const student = new Student()
student.faculty = 'Faculty'
student.name = { first: null, last: null } // same as student.name = null
await dataSource.manager.save(student)

// this will return the student name as `null`
await dataSource.getRepository(Student).findOne()