【EF Core】移行をつかってデータベースの列を追加・更新・削除する

# この記事は

手を動かしながら Entity Framwwork Core の[移行機能](https://docs.microsoft.com/ja-jp/ef/core/managing-schemas/migrations)を体感するために
「モデルを更新 → `Add-Migration` → モデルを更新　→　・・・」を繰り返した記録である

## この記事の前提条件

- [この記事の前段となる記事](https://qiita.com/t13801206/items/4c0da0936d4f637940de)がある
- [公式ドキュメントのモデル](https://docs.microsoft.com/ja-jp/ef/core/#the-model)を参考にしてつくった`Blog`クラスがある
- プロジェクトには、すでに2つの移行が存在する
    - はじめての移行である`Init`移行と、テーブルに初期値を与えてた`Seed`移行がすでに追加されている

## 検証につかう`Blogs`テーブルの説明

その名の通り、ブログを管理するためのテーブル

今回は、この`Blogs`テーブルを対象にして
列を追加したり、名前を変えたり、削除したり、、を行う

### テーブルの構造

**3つのフィールド**から構成される

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/93203182-e2c1-b0fc-4361-65d5a7b1f217.png)

※ 画像は Visual Studio 2019 - SQL Server オブジェクトエクスプローラー のキャプチャ

### テーブルの中身

過去におこなった`Seed`移行により、**2つのレコード**が挿入されている

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/5cb316b9-ec89-2067-3374-bec4fb95fde2.png)

# （復習）EF Core コマンドの使い方

上の2つは、移行を進めるために使うコマンド
下の2つは、なにか間違いがあったときに巻き戻すためのコマンド

```powershell
# 移行を追加する
Add-Migration -Name {移行の名前} -Context BloggingContext -Project Intro -StartupProject WPF_EFCore

# 最後に移行までデータベースを更新する（途中までの以降は -Migration オプションで指定可能）
Update-Database -Context BloggingContext -Project Intro -StartupProject WPF_EFCore

#----上：移行を進める-----下：なにか間違ったときにつかう----------------------------------------

# 最後の移行を削除する
Remove-Migration -Context BloggingContext -Project Intro -StartupProject WPF_EFCore

# データベースを削除する
Drop-Database -Context BloggingContext -Project Intro -StartupProject WPF_EFCore
```

末尾の`-Context BloggingContext -Project Intro -StartupProject WPF_EFCore`は共通パラメータである

- `-Project`と`-StartupProject`は、Visual Studio 側で設定していれば省略可能
- `-Context`は、プロジェクト内に複数の`DbContext`が存在する場合は明示する必要がある

参考：[EF Core ツールリファレンス](https://docs.microsoft.com/ja-jp/ef/core/cli/powershell)

# 移行１；新しく列を追加する

ブログの公開状況を管理する`IsPrivate`列を追加する

## モデルを変更する

モデルに`IsPrivate`フィールドを追加する

```diff_c_sharp
 public class Blog
 {
     public int BlogId { get; set; }
     public string Url { get; set; }
     public int Rating { get; set; }
+    public bool IsPrivate { get; set; }
     public List<Post> Posts { get; set; }
 }
```

## 移行を追加する`Add-Migration`

```powershell:パッケージマネージャーコンソール
Add-Migration -Name Add_NewColumn_IsPrivate -Context BloggingContext -Project Intro -StartupProject WPF_EFCore
```


## データベースを更新`Update-Database`の結果

- 4列目に`IsPrivate`が追加された
- 値はすべて`False`が割り当てられている

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/b52096d7-c04c-0a61-7bce-1c19c4468c42.png)

# 移行２；列の名前を変える

`IsPrivate`の既定値が`False`であることがわかったので
`IsPrivate`の名前を`IsPublic`に変更する暴挙に出る

:::note
データがまだ入ってないなら、移行を削除してやり直すほうがスマートだけど、検証なので・・・
:::

列名を変更するの前と後で、すでにある値が変わらないことを確認するために
テーブルの中の値を一か所だけ`True`にしておく

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/42674a40-5652-c721-4d71-e90edca015ab.png)

2行目の`IsPrivate`を`True`にした

## モデルを変更する

モデルの`IsPrivate`を`IsPublic`に変更する

```diff_c_sharp
 public class Blog
 {
     public int BlogId { get; set; }
     public string Url { get; set; }
     public int Rating { get; set; }
-    public bool IsPrivate { get; set; }
+    public bool IsPublic { get; set; }
     public List<Post> Posts { get; set; }
 }
```

## 移行を追加する`Add-Migration`

```powershell:パッケージマネージャーコンソール
Add-Migration -Name Rename_IsPrivate_to_IsPublic -Context BloggingContext -Project Intro -StartupProject WPF_EFCore
```

### 移行の内容を確認する

公式ドキュメントの[列名の変更](https://docs.microsoft.com/ja-jp/ef/core/managing-schemas/migrations/managing?tabs=dotnet-core-cli#column-renames)によると

>EF Core は、通常、列を削除し、新しい列 (2 つの異なる変更) を作成し、列の名前を変更する必要があるかどうかを知ることができません。

ということらしく、**自分で移行の内容を書き換える必要がある**ことが示唆されている
しかし、実際に生成された`UP`メソッドは以下であり
どうやら自分が検証した環境では**書き換える必要がなさそう**

```cs
migrationBuilder.RenameColumn(
   name: "IsPrivate",
   table: "Blogs",
   newName: "IsPublic");
```

EF Coreのバージョンは`Microsoft.EntityFrameworkCore 5.0.9`をつかった

## データベースを更新`Update-Database`の結果

期待したとおりの結果になった

- 4列目の`IsPrivate`が`IsPublic`に変更された
- 2行目の`IsPublic`が`True`のまま保持されている

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/cbefa332-c227-a4ee-ced2-eba30d1c8f69.png)

# 移行３；列を削除する

よくよく考えると、**ブログは公開するもの**なので、`IsPublic`列を削除する

公開／非公開は記事ごとに区別するケースが多そうなので、`Post`モデルに与えるほうがよい

## モデルを変更する

モデルの`IsPublic`フィールドを削除する

```diff_c_sharp
 public class Blog
 {
     public int BlogId { get; set; }
     public string Url { get; set; }
     public int Rating { get; set; }
-    public bool IsPublic { get; set; }
     public List<Post> Posts { get; set; }
 }
```

## 移行を追加する`Add-Migration`

```powershell:パッケージマネージャーコンソール
Add-Migration -Name Remove_IsPublic -Context BloggingContext -Project Intro -StartupProject WPF_EFCore
```

## データベースを更新`Update-Database`の結果

`IsPublic`列が削除され、3列になった（この記事の最初の状態に戻った）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/cea8f4bf-ccc8-b132-f169-f09b8f3a2f04.png)

# 移行４；すでにNULL値が入っているフィールドに対して、NotNull属性を与えてみる

冒頭でも載せたとおり、テーブルの構造は以下のようになっている

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/93203182-e2c1-b0fc-4361-65d5a7b1f217.png)

`Url`は`Nullを許容`されている

この状態で**`Url`が`NULL`であるレコードを挿入しておき、あとから`Url`にNotNull属性を与えて移行するとどうなるか**を試す

## 新しいレコードを追加

`Url`が`NULL`である3行目を追加した

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/ab16c17b-c316-103f-923f-d64d4d5e645c.png)

## モデルを変更する

`Url`のNULLを許容しないように、`Required`属性をつける

```diff_c_sharp
 public class Blog
 {
     public int BlogId { get; set; }
+    [Required]
     public string Url { get; set; }        
     public int Rating { get; set; }
     public List<Post> Posts { get; set; }
 }
```

## 移行を追加する`Add-Migration`

```powershell:パッケージマネージャーコンソール
Add-Migration -Name Add_NotNullto_Url -Context BloggingContext -Project Intro -StartupProject WPF_EFCore
```

## データベースを更新`Update-Database`の結果

**失敗した**

`Url`列に`NULL`は入れちゃだめよ、と。

>Cannot insert the value NULL into column 'Url', table 'Blogging.dbo.Blogs'; column does not allow nulls. UPDATE fails.
>The statement has been terminated.

### 成功するようにデータを変更する

`NULL`だったところに値を入れる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/49c910fe-b753-d9e7-7552-186aa70f7a66.png)

再度データベースを更新すると、成功した。

```powershell:パッケージマネージャーコンソール
Update-Database -Context BloggingContext -Project Intro -StartupProject WPF_EFCore
Build started...
Build succeeded.
Applying migration '20210912004324_Add_NotNullto_Url'.
```

# 以降５；列の順番を変え・・・られなかった

いろいろ試してみたけど、今の自分には無理だった

## 試しにやってみたけど、ダメだった例

単純に**モデルのプロパティ順序を入れ替えるだけ**では、**列順は入れ替わらない**
`protected override void Up(MigrationBuilder migrationBuilder)`も`Down`も空っぽ

```diff_c_sharp
 public class Blog
 {
     public int BlogId { get; set; }
-    public string Url { get; set; }
-    public int Rating { get; set; }
+    public int Rating { get; set; }
+    public string Url { get; set; }
     public List<Post> Posts { get; set; }
 }
```

## 公式ドキュメントに書いてある注意事項

「SQL Server 列の順番」で Google 検索すると、ちゃんと公式ドキュメントが最初にヒットする

https://docs.microsoft.com/ja-jp/sql/relational-databases/tables/change-column-order-in-a-table

**列の順番に依存するアプリケーションに影響を及ぼすかもしれない**ので
もし列の順番を変更するなら、十分注意してやってください、と。

またアプリケーション側に対しても要請が出ており

- 列の順番はアプリケーション側で面倒を見てください
- データベースの列の順番に依存しないようにするためにも、**`SELECT *`に頼らないでください**

と書かれている。

さらに、とどめの一撃として
**このタスク（列の順序を変更すること）は、T-SQLでは無理っぽい**ことが書いてある。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/281abe14-bb22-2831-3e64-0e1dc88787f1.png)

### T-SQLってなんだっけ？

無意識につかっていたけど、SQL Server でつかっている SQL を T-SQL と呼ぶらしい

> T-SQL (Transact-SQL) とは Microsoft と Sybase 開発した、スタンダードの SQL (Structured Query Languag) を拡張した言語で、Microsoft SQL Server で使われています。

引用元：[T-SQL 入門 - SQL Server 入門](https://sql55.com/t-sql/)

# おわりに

今回は、モデルを変更して、データベースに変更を反映させる操作を4回繰り返した

基本的な作成・更新・削除の動作は確認することができた

# 参考にさせていただいた記事

https://qiita.com/filunK/items/12cc006254e93cdcc53a