Closed11

TypeORMの知見まとめ

uttkuttk

TypeORMのリレーションでSoft Deleteが機能してない問題

以下のissuesを参照すると、同じ問題が発生している人が多数いるみたい👀

https://github.com/typeorm/typeorm/issues/6265

現状は、対応して無さそう...

uttkuttk

Active Recordって凄かったんやなって、TypeORM使っていて常々思う。。。

uttkuttk

cascadeを有効にしている場合はinsertは使わない方が良いかも?

cascadeとは、リレーションを設定する時に渡させるオプションの事。
詳しくはドキュメントを参照して欲しいが、例えば以下のような設定。

cascadeオプションを渡す例
@Entity()
class Hoge {
  /* ... */

  @OneToOne(() => Item, { cascade: true })
  item: Item;

  /* ... */
}

そして、cascadeを設定した上記のようなエンティティに以下のような処理をした場合、insert()save() で挙動が変わる。

const repo = getRepository(Hoge);

const item = new Item()
const hoge = repo.create({ /* ... */ })

item.name = 'Hello World!'
hoge.item = item

await repo.save(profile) // itemも一緒に保存される ( 想定していた挙動 )

await repo.insert(profile) // hogeは保存されるが、itemは保存されない( 想定外の挙動 )

なるべくは、save()を使った方が良さそう。

uttkuttk

紐づいているレコード(行)も論理削除する方法

先ず初めに、リレーションを定義する際に cascade オプションを有効にする必要がある。

User.ts
@Entity()
export class User {  /* ... */  }
Profile.ts
@Entity()
export class Profile {
  /* ... */

  // cascade: true を設定する必要がある!
  @OneToOne(() => User, { cascade: true })
  @JoinColumn()
  user: User

  /* ... */
}

次に、削除したいレコード(行)を取ってくるときに relations を指定して取得し、その返り値を softRemove に渡すことで、紐づいているレコード(行)も論理削除が出来る。

const repo = getRepository(Profile)
const profile = repo.findOne({ id: 1, relations: ["user"] }) // relationsを指定する必要がある!

// Profileのカラムを削除
// 紐づいているUserのレコードも削除される
repo.softRemove(profile) 

// softDeleteだと削除できないので注意!
// repo.softDelete( profile )
uttkuttk

@OneToMany()で、紐づいているレコードだけ削除・論理削除する方法

物理削除の場合

エンティティは以下のように定義する。

User.ts
@Entity()
export class User {
  /* ... */

  // 特に設定は無し
  @OneToMany(() => Task, (task) => task.user)
  tasks: Task[]

  /* ... */
}
Task.ts
@Entity()
export class Task {
  /* ... */

  // orphanedRowAction: 'delete' を設定する
  // これによって、Userとの紐づけが切れた時に自動的に物理削除される
  // orphanedRowAction: 'nullify' を設定した場合は、
  // プライマリーカラム( 今回は 'userId' )に null が入る( デフォルトはコレ )
  @ManyToOne(() => User, { orphanedRowAction: 'delete' })
  user: User

  /* ... */
}

その次に以下のようにする事で、Taskのみのレコードを 物理削除 することが出来ます。

Taskのレコードのみ削除する
const userRepo = getRepository(User)
const taskRepo = getRepository(Task)

// relationsを設定しないと、tasksがロードされないので注意!
const user = await userRepo.findOne({ relations: ['tasks'] })

console.log( user.tasks ) // [{ ... }, { ... }] <- タスクが複数登録されている事を確認

user.tasks = [] // 登録されているタスクを全て削除

// 指定して削除したい場合は、削除したい値を配列から除けばよい
// user.tasks = user.tasks.filter(v => v.id !== deleteTaskId) 

await userRepo.save(user) // 配列に入っていないTaskを削除

論理削除する場合

論理削除の場合、エンティティ内での設定は特にない。
というより、対応していない。強いて言えば、@DeleteDateColumn()を設定するくらい。

User.ts
@Entity()
export class User {
  /* ... */

  // 特になし
  @OneToMany(() => Task, (task) => task.user)
  tasks: Task[]

  /* ... */
}
Task.ts
@Entity()
export class Task {
  /* ... */

  // 特になし
  @ManyToOne(() => User)
  user: User

  // 論理削除用のカラム
  @DeleteDateColumn()
  deletedAt?: Date

  /* ... */
}

そのため、論理削除するには強引に softRemove() を実行する必要がある。

Taskのレコードのみを論理削除する
const userRepo = getRepository(User)
const taskRepo = getRepository(Task)

// relationsを設定しないと、tasksがロードされないので注意!
const user = await userRepo.findOne({ relations: ['tasks'] }) 

console.log( user.tasks ) // [{ ... }, { ... }] <- タスクが複数登録されている事を確認

await taskRepo.softRemove( user.tasks ) // 登録されているタスクを全て削除

// 指定して削除したい場合は、削除したい値を配列に含める
// await taskRepo.softRemove( user.tasks.filter(v => v.id === deleteTaskId)  ) 
uttkuttk

TypeORMでCircularRelationsErrorが発生した時

上記のようなエラーが発生した場合は、どこかのリレーションで nullable : false としている所を true に設定するか、設定しない必要がある。

example.ts
@Entity()
class User {

-  @OneToMany(() => Tag, (tag) => Tag.user, { cascade: true, nullable: fasle })
+  @OneToMany(() => Tag, (tag) => Tag.user, { cascade: true })
   tags: Tag[];
}
このスクラップは2021/05/07にクローズされました