【Swift】CoreDataで関係データベース

## はじめに

CoreDataでリレーションシップの機能を使ったので備忘録として残します。
前半は関係データベースについて、後半はCoreDataでの関係データベース設定方法について記述します。

## 環境
- Swift: version 5.6
- Xcode: Version 13.3.1 (13E500a)
- macOS: 12.3.1 (21E258)

## リレーションシップ（Relationship）とは？

エンティティ（Entity）同士の関係性。
リレーションシップの設定を行うことで関係データベースを構築することができる。

## 関係データベース

データを表で管理するデータベースのこと。
行（レコード）と列（属性）で構成される。
- データのが追加されると行が増える。
- 行には名前がないが、列には名前（属性名）がある

### 主キー

表の中からある特定の行を識別するための列。

#### 主キーの条件

1. データの重複がないこと。
2. データが空でないこと。

#### 複合主キー

複数の列を組み合わせてつくる主キーのこと。
上記の主キーの条件を満たす列がない場合に使用する。

### 外部キー
別の表の主キーを参照する列のこと。

### 正規化
データが重複したり、データの更新の際に矛盾が生じたりしないように表をわけること。

## 関係データベースを構築する
今回もポケモンを使って考えてみます。
ボックスと預けているポケモンの関係性が使えそうです。
こんなイメージ。
![2022-05-13-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/e835be4b-edb5-28fa-a647-7ceba0c3b08b.png)

### 1. Entityの作成
`Box`と`Pokemon`の２つのエンティティを作成します。
![2022-05-13-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/483b8c4f-593e-d32f-d412-f3470003d23f.png)

### 2. Attributesの追加

`Box`側
![2022-05-13-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/94c2581c-cac5-9a49-b567-6b447a46fd6c.png)

`Pokemon`側
![2022-05-13-03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/e55d9653-4f1b-4206-f37a-1a82feb65559.png)

### 3. Relationshipの追加

`Box`側
![2022-05-13-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/0c052d4a-dfb8-52af-2192-ee04707ff9d8.png)
`boxNo`を主キーとして設定します。

`Pokemon`
![2022-05-13-05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/0d033e43-53d9-e505-4b14-6f544a121e02.png)
`boxNo`を外部キーとして設定します。

#### 関係性の設定
`Box`側
![2022-05-13-06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/5d610845-8532-5937-1b89-ef0741602431.png)
1つのボックスには複数のポケモンを預けることができます。
なので`To Many`（１対多）に設定します。

`Pokemon`
![2022-05-13-07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/8fa1a8d2-647f-9b16-a640-a3abf83e2200.png)
ポケモンは1つのボックスにだけ預けることができます。
なので`To One`（1対1）に設定します。

以上でCoreDataの設定は完了です。

## データベースの使い方

関係する部分だけを載せています。
いないとは思いますが、他の部分が気になる方はGitHubの[こちら](https://github.com/nownaka/Qiita/tree/main/2022/05/【Swift】CoreDataで関係データベース/CoreDataRelationshipSample)のリポジトリに保存していますのでよかったら見て下さい。
（独学だから誰か見て評価して欲しい。。）

### データの保存

```swift: RegistPokemonView.swift
import SwiftUI

struct RegistPokemonView: View {
    @Environment(\.colorScheme) private var colorScheme
    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Box.boxNo, ascending: true)],
        animation: .default)
    private var boxs: FetchedResults<Box> //ボックスデータの呼び出し
    @State var boxNo: Box? //ピッカーで選択したボックスを格納
    let pokemon: PokemonJSONModel //ポケモンの情報
    
    var body: some View {

    /*省略*/

    }
    
    private func saveCoreData() { //預けるボタン押下時に実行
        if let box = box {
            let newPokemon = Pokemon(context: viewContext)
            newPokemon.id = UUID()
            newPokemon.name = pokemon.name
            newPokemon.pokedexNo = Int64(pokemon.no)
            newPokemon.boxNo = box //ポケモンとボックスの紐付け
            do {
                try viewContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }
}
```

ボックスのデータは予め用意しています。
データの紐付けは代入操作で行います。
![2022-05-13-08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/a14bdaca-c76b-c9f0-1bcd-3a8bd6e04ea5.png)
このとき、`boxNo`の型は`Box?`型になっていました。
（`Int`型じゃなかった。手書きの図のイメージであってるのか不安。）


UI部分は省略しましたがこんな感じです。
![2022-05-13-09-A.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/9e722c59-2c62-a6ce-5ed1-40adda89f552.png)
預けるボタンを押すとボックスに追加されます。
![2022-05-13-10-A.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/f10352c4-7e00-d9ae-13ad-291313daf933.png)
![2022-05-13-11-A.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/691e8723-87f0-8d8e-2c49-f70b38818b19.png)

### データの更新と削除
```swift
import SwiftUI

struct EditPokemonView: View {
    @Environment(\.colorScheme) private var colorScheme
    @Environment(\.managedObjectContext) private var viewContext
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Box.boxNo, ascending: true)],
        animation: .default)
    private var boxies: FetchedResults<Box>
    @State private var boxNo: Box? //変更先のボックスを格納
    let pokemon: Pokemon //ポケモンの情報

    var body: some View {

    /*省略*/

    }

    private func saveCoreData() { //保存ボタン押下時に実行
        do {
            pokemon.boxNo = boxNo //変更先のボックスを代入
            try viewContext.save() //保存（更新）処理
        } catch {
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
    }

    private func deleteCoreData() { //逃がすボタンを押下時に実行
        viewContext.delete(pokemon) //削除処理
    }
}
```

更新時は`Pokemon.boxNo`に別の`Box`を代入してやればOKです。
`.save()`メソッドの呼び出しは忘れずに。

削除時は`.delete(_:)`メソッドの呼び出しだけでOKです。
この時ポケモンのデータがなくなるだけで、紐付いているボックスはなくなりません。

UIはこんな感じ。
![2022-05-13-12-A.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/d7ee5711-25af-eb57-ad30-bafd37e4a1d1.png)

保存を押すとボックスを移動します。
![2022-05-13-13-A.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/dd3a1bb6-c8fd-31fb-bea9-97fe8f0b2f3c.png)
逃がすを押すといなくなります。。
![2022-05-13-14-A.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/44062a4a-b5d2-2610-2e3b-a4303347c47e.png)

## さいごに
今回はCoreDataでの関係データベース構築についてまとめてみました。
学びたてなので間違っていればご指摘お願いします。
最後までご覧いただきありがとうございました。

## 参考記事・書籍

https://uruly.xyz/【swift-3】coredataをつかってみた%E3%80%82relationshipをつけよう【part-3】/

https://www.sbcr.jp/product/4815613730/