关系

关系

关系可以帮助你轻松地与相关实体合作。有几种类型的关系:

你可以为关系指定几个选项:

  • eager: boolean -如果设置为true,则在此实体上使用 find *QueryBuilder 时,将始终使用主实体加载关系
  • cascade: boolean -如果设置为true,则将插入相关对象并在数据库中更新。
  • onDelete: "RESTRICT"|"CASCADE"|"SET NULL" -指定删除引用对象时外键的行为方式
  • primary: boolean -指示此关系的列是否为主列。
  • nullable: boolean -指示此关系的列是否可为空。默认情况下是可空。

一对一

一对一是一种A只包含一个B实例,而B只包含一个A实例的关系。我们以UserProfile实体为例。用户只能拥有一个配置文件,并且一个配置文件仅由一个用户拥有。

@Entity()
export class Profile {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  gender: string;

  @Column()
  photo: string;
}

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @OneToOne(type => Profile)
  @JoinColumn()
  profile: Profile;
}

这里我们将@OneToOne添加到profile并将目标关系类型指定为Profile。我们还添加了@JoinColumn,这是必选项并且只能在关系的一侧设置。你设置@JoinColumn的哪一方,哪一方的表将包含一个 “relation id” 和目标实体表的外键。此示例将生成以下表:

+-------------+--------------+----------------------------+
|                        profile                          |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| gender      | varchar(255) |                            |
| photo       | varchar(255) |                            |
+-------------+--------------+----------------------------+

+-------------+--------------+----------------------------+
|                          user                           |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| name        | varchar(255) |                            |
| profileId   | int(11)      | FOREIGN KEY                |
+-------------+--------------+----------------------------+

同样,@JoinColumn必须仅设置在关系的一侧且必须在数据库表中具有外键的一侧。该例子展示如何保存这样的关系:

const profile = new Profile();
profile.gender = "male";
profile.photo = "me.jpg";
await connection.manager.save(profile);

const user = new User();
user.name = "Joe Smith";
user.profile = profile;
await connection.manager.save(user);

启用级联后,只需一次save调用即可保存此关系。要加载带有配置文件的用户,必须在FindOptions中指定关系:

const userRepository = connection.getRepository(User);
const users = await userRepository.find({ relations: ["profile"] });

或者使用QueryBuilder:

const users = await connection
  .getRepository(User)
  .createQueryBuilder("user")
  .leftJoinAndSelect("user.profile", "profile")
  .getMany();

通过在关系上启用预先加载,你不必指定关系或手动加入,它将始终自动加载。

双向关系

关系可以是单向的和双向的。单向是仅在一侧与关系装饰器的关系。双向是与关系两侧的装饰者的关系。我们刚刚创建了一个单向关系。让我们将它改为双向:

@Entity()
export class Profile {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  gender: string;

  @Column()
  photo: string;

  @OneToOne(
    type => User,
    user => user.profile
  ) // 将另一面指定为第二个参数
  user: User;
}

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @OneToOne(
    type => Profile,
    profile => profile.user
  ) // 指定另一面作为第二个参数
  @JoinColumn()
  profile: Profile;
}

我们只是创建了双向关系。注意,反向关系没有@JoinColumn@JoinColumn必须只在关系的一侧且拥有外键的表上。双向关系允许你使用QueryBuilder从双方加入关系:

const profiles = await connection
  .getRepository(Profile)
  .createQueryBuilder("profile")
  .leftJoinAndSelect("profile.user", "user")
  .getMany();

多对一/一对多的关系

多对一/一对多是指A包含多个B实例的关系,但B只包含一个A实例。让我们以UserPhoto实体为例。User可以拥有多张photos,但每张photo仅由一位user拥有。

@Entity()
export class Photo {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  url: string;

  @ManyToOne(
    type => User,
    user => user.photos
  )
  user: User;
}

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @OneToMany(
    type => Photo,
    photo => photo.user
  )
  photos: Photo[];
}

这里我们将@ManyToOne添加到photos属性中,并将目标关系类型指定为Photo。你也可以在@ManyToOne/@OneToMany关系中省略@JoinColumn。没有@ManyToOne@OneToMany就不可能存在。如果你想使用@OneToMany,则需要@ManyToOne。在你设置@ManyToOne的地方,相关实体将有"关联id"和外键。

+-------------+--------------+----------------------------+
|                         photo                           |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| url         | varchar(255) |                            |
| userId      | int(11)      |                            |
+-------------+--------------+----------------------------+

+-------------+--------------+----------------------------+
|                          user                           |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| name        | varchar(255) |                            |
+-------------+--------------+----------------------------+

如何保存这种关系:

const photo1 = new Photo();
photo1.url = "me.jpg";
await connection.manager.save(photo1);

const photo2 = new Photo();
photo2.url = "me-and-bears.jpg";
await connection.manager.save(photo2);

const user = new User();
user.name = "John";
user.photos = [photo1, photo2];
await connection.manager.save(user);

或者你可以选择:

const user = new User();
user.name = "Leo";
await connection.manager.save(user);

const photo1 = new Photo();
photo1.url = "me.jpg";
photo1.user = user;
await connection.manager.save(photo1);

const photo2 = new Photo();
photo2.url = "me-and-bears.jpg";
photo2.user = user;
await connection.manager.save(photo2);

启用级联后,只需一次 save 调用即可保存此关系。要在内部加载带有photosuser,必须在FindOptions中指定关系:

const userRepository = connection.getRepository(User);
const users = await userRepository.find({ relations: ["photos"] });

// or from inverse side

const photoRepository = connection.getRepository(Photo);
const photos = await photoRepository.find({ relations: ["user"] });

或者使用QueryBuilder:

const users = await connection
  .getRepository(User)
  .createQueryBuilder("user")
  .leftJoinAndSelect("user.photos", "photo")
  .getMany();

// or from inverse side

const photos = await connection
  .getRepository(Photo)
  .createQueryBuilder("photo")
  .leftJoinAndSelect("photo.user", "user")
  .getMany();

通过在关系上启用预先加载,你不必指定关系或手动加入,它将始终自动加载。

多对多的关系

多对多是一种A包含多个B实例,而B包含多个A实例的关系。我们以QuestionCategory实体为例。Question可以有多个categories,每个category可以有多个questions

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

  @Column()
  name: string;
}

@Entity()
export class Question {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  text: string;

  @ManyToMany(type => Category)
  @JoinTable()
  categories: Category[];
}

@JoinTable()@ManyToMany关系所必需的。你必须把@JoinTable放在关系的一个(拥有)方面。此示例将生成以下表:

+-------------+--------------+----------------------------+
|                        category                         |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| name        | varchar(255) |                            |
+-------------+--------------+----------------------------+

+-------------+--------------+----------------------------+
|                        question                         |
+-------------+--------------+----------------------------+
| id          | int(11)      | PRIMARY KEY AUTO_INCREMENT |
| title       | varchar(255) |                            |
+-------------+--------------+----------------------------+

+-------------+--------------+----------------------------+
|              question_categories_category               |
+-------------+--------------+----------------------------+
| questionId  | int(11)      | PRIMARY KEY FOREIGN KEY    |
| categoryId  | int(11)      | PRIMARY KEY FOREIGN KEY    |
+-------------+--------------+----------------------------+

如何保存这种关系:

const category1 = new Category();
category1.name = "animals";
await connection.manager.save(category1);

const category2 = new Category();
category2.name = "zoo";
await connection.manager.save(category2);

const question = new Question();
question.categories = [category1, category2];
await connection.manager.save(question);

启用级联后,只需一次save调用即可保存此关系。要在categories里面加载question,你必须在FindOptions中指定关系:

const questionRepository = connection.getRepository(Question);
const questions = await questionRepository.find({ relations: ["categories"] });

或者使用QueryBuilder

const questions = await connection
  .getRepository(Question)
  .createQueryBuilder("question")
  .leftJoinAndSelect("question.categories", "category")
  .getMany();

通过在关系上启用预先加载,你不必指定关系或手动加入,它将始终自动加载。

双向关系

关系可以是单向的和双向的。单向是仅在一侧与关系装饰器的关系。双向是与关系两侧的装饰者的关系。我们刚刚创建了一个单向关系。让我们改为双向:

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

  @Column()
  name: string;

  @ManyToMany(
    type => Question,
    question => question.categories
  )
  questions: Question[];
}

@Entity()
export class Question {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  text: string;

  @ManyToMany(
    type => Category,
    category => category.questions
  )
  @JoinTable()
  categories: Category[];
}

我们只是创建了双向关系。注意,反向关系没有 @JoinTable@JoinTable 必须只在关系的一边。双向关系允许您使用 QueryBuilder 从双方加入关系:

const categoriesWithQuestions = await connection
  .getRepository(Category)
  .createQueryBuilder("category")
  .leftJoinAndSelect("category.questions", "question")
  .getMany();

EagerLazy关系

Eager关系

每次从数据库加载实体时,都会自动加载Eager关系。例如:

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

  @Column()
  name: string;

  @ManyToMany(
    type => Question,
    question => question.categories
  )
  questions: Question[];
}

@Entity()
export class Question {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  text: string;

  @ManyToMany(
    type => Category,
    category => category.questions,
    {
      eager: true
    }
  )
  @JoinTable()
  categories: Category[];
}

现在当你加载questions时,不需要加入或指定要加载的关系。它们将自动加载:

const questionRepository = connection.getRepository(Question);

// questions 将加载其类别 categories
const questions = await questionRepository.find();

Eager关系只有在使用find *方法时才有效。如果你使用QueryBuilder,则禁用eager关系,并且必须使用leftJoinAndSelect来加载。Eager的关系只能用于关系的一方,在关系的两边使用eager:true是不允许的。

Lazy关系

当你访问的时候会加载Lazy关系中的实体。这种关系必须有Promise作为类型,并且将值存储在一个promise中,当你加载它们时,也会返回promise。例如:

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

  @Column()
  name: string;

  @ManyToMany(
    type => Question,
    question => question.categories
  )
  questions: Promise<Question[]>;
}

@Entity()
export class Question {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  text: string;

  @ManyToMany(
    type => Category,
    category => category.questions
  )
  @JoinTable()
  categories: Promise<Category[]>;
}

categories 是一个Promise.这意味着它是lazy的,它只能存储一个带有值的promise

例如:

保存这种关系:

const category1 = new Category();
category1.name = "animals";
await connection.manager.save(category1);

const category2 = new Category();
category2.name = "zoo";
await connection.manager.save(category2);

const question = new Question();
question.categories = Promise.resolve([category1, category2]);
await connection.manager.save(question);

如何在Lazy关系中加载对象:

const question = await connection.getRepository(Question).findOne(1);
const categories = await question.categories;
// you'll have all question's categories inside "categories" variable now

注意:如果你来自其他语言(Java,PHP等)并且习惯于在任何地方使用lazy关系,请小心使用。这些语言不是异步的,延迟加载是以不同的方式实现的,这就是为什么不能使用promises的原因。在JavaScriptNode.JS中,如果你想拥有延迟加载的关系,你必须使用promises。但是这是非标准技术,而且在TypeORM中被认为是实验性的。

下一页