TypeORM で @OneToMany <-> @ManyToOne の関係を定義して、テーブルに保存する

React Native では TypeORM を使って端末の SQLite を操作できます。 SQLite 3 からは Relation も定義できます。

この記事では、以下のような 1対多、 多対1の関係を持つ Entity をテーブルに保存する方法を紹介します。

Entity の定義

@OneToMany() , @ManyToOne() の Entity を定義します。

SnapShot.ts
import {Column, Entity, ManyToOne, OneToMany, PrimaryColumn} from "typeorm";

@Entity('snap_shots')
export class SnapShot {
  @PrimaryColumn({ type: "varchar"})
  id: string

  @PrimaryColumn({ type: "varchar"})
  date: string

  @Column({type: "int", default: 0})
  totalTaskTime: number

  @OneToMany(() => SnapShotTask, snapShotTask => snapShotTask.snapShot, {cascade: true})
  snapShotTasks: SnapShotTask[];

}



@Entity('snap_shot_tasks')
export class SnapShotTask {
  @PrimaryColumn({ type: "varchar"})
  id: string

  @Column({ type: "varchar"})
  name: string

  @Column({ type: "int", default: 0})
  totalTime: number

  @ManyToOne(() => SnapShot, snapShot => snapShot.snapShotTasks, )
  snapShot: SnapShot;
}

テーブルを操作する関数を定義する

テーブルを操作する関数を作ります(今回のサンプルで使うものに限定しています)

snapshotRepository.ts
export const getSnapShots = async () => {
  const snapShotRepository = AppDataSource.getRepository(SnapShot);
  return await snapShotRepository.find({
    relations: {
      snapShotTasks: true,
    },
  });
};

export const getSnapShotByDateString = async (date: string) => {
  const snapShotRepository = AppDataSource.getRepository(SnapShot);
  return await snapShotRepository.findOne({ where: { date }, relations: ['snapShotTasks'] });
};

export const addSnapShot = async (snapShot: SnapShot) => {
  const snapShotRepository = AppDataSource.getRepository(SnapShot);
  return await snapShotRepository.save(snapShot);
};

find メソッドを呼ぶときは、 relations: { xxx: true } を指定する必要があります。

https://orkhan.gitbook.io/typeorm/docs/find-options

上記のテーブルを操作する関数を使ってテーブルにリレーションを保存する関数は以下のとおりです。

sample.ts
export async function recordResults(tasks: Task[], dateString: string) {
  const snapShotByDate = await getSnapShotByDateString(dateString);

  const snapShot = new SnapShot();
  snapShot.id = snapShotByDate === null ? uuidv4() : snapShotByDate.id;
  snapShot.date = dateString;
  snapShot.totalTaskTime = tasks.reduce((acc, task) => acc + task.totalTime, 0);

  // 親に子の配列のプロパティを設定する
  snapShot.snapShotTasks = tasks.map((task) => {
    const snapShotTask = new SnapShotTask();
    snapShotTask.id = uuidv4();
    snapShotTask.name = task.name;
    snapShotTask.totalTime = task.totalTime;
    snapShotTask.snapShot = snapShot;
    return snapShotTask;
  });

  // 親を保存する
  await addSnapShot(snapShot);

  // 結果を確認する
  const result = await getSnapShotByDateString(dateString);
  if (result !== null) {
    console.log('result:', result);
    result.snapShotTasks.forEach((snapShotTask) => {
      console.log(`タスク: ${snapShotTask.name} - ${result.id}`);
    });
  }
}

上の例だと、「同じ日付につき1つの SnapShot を保存する」ために、 snapShot.id = snapShotByDate === null ? uuidv4() : snapShotByDate.id; などとやっていますが、この辺は自分が作っているアプリの仕様なので、不要なときは無視してください。

ポイントは、「親のプロパティに子の配列を設定すること」→その後で「親を保存すること」です。

また getSnapShotByDateString という関数で relation を設定している点にも注目です。関連を貼ったテーブルの情報を取得するには relation を指定しなければいけません。指定する文字列は子との関連を定義している「プロパティ名」です。