SQL Serverにシードデータを与える

# はじめに

[この記事](https://qiita.com/t13801206/items/9330dd3a540966261320)では、`Blogging`データベースをつくった
今回は、**`Blogging`データベースにシードデータを与える**

## データシード処理とは

> データシード処理とは、データベースに初期データセットを設定するプロセスのことです。

https://docs.microsoft.com/ja-jp/ef/core/modeling/data-seeding

データベースをつくったタイミングで、そのデータベースにサンプルデータを挿入できるというもの

特にローカルでいろいろ試しているうちは、検証用にデータが必要になることが多いので
あらかじめ任意の初期データセットをセットできると便利でよい

# `DbContext`を継承したクラスを編集する

ここから実際に手を動かす

シードデータを定義するために
`DbContext`を継承した`BloggingContext`を編集する

具体的には、**基底クラスに用意されている`OnModelCreating`メソッドをオーバーライドして処理を置き換える**

今回は、`Blog`クラスには2つのレコード、`Post`クラスには4つのレコードを、それぞれ初期値として設定する

```cs:BloggingContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // BlogSeed
    modelBuilder.Entity<Blog>().HasData(
        new Blog { BlogId = 1, Url = "http://one.com" },
        new Blog { BlogId = 2, Url = "http://two.com" }
        );

    // PostSeed
    modelBuilder.Entity<Post>().HasData(
        new Post { BlogId = 1, PostId = 1, Title = "title11", Content = "content11" },
        new Post { BlogId = 1, PostId = 2, Title = "title12", Content = "content12" },
        new Post { BlogId = 2, PostId = 3, Title = "title23", Content = "content23" },
        new Post { BlogId = 2, PostId = 4, Title = "title24", Content = "content24" }
        );
}
```

# データベースを移行する

次の手順で、データシード処理をデータベースに反映させる

1. 移行を追加する`Add-Migration`
1. 移行をデータベースに反映させる`Update-Database`

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

コンテキストを編集したので、編集した内容を記録する
（＝`Add-Migration`で、移行の履歴に追加する）

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

使っているコードは過去記事からの続いているので
この`Add-Migraiton`は、`Init`マイグレーションにつづいて、2回目のマイグレーションとなる

### 移行の履歴を確認する`Get-Migration`

`Get-Migration`コマンドにより、`BloggingContext`に関する移行の履歴を確認できる
また、それらの移行がデータベースに反映されているか否かも確認できる

```Powershell:パッケージマネージャーコンソール
Get-Migration -Context BloggingContext -Project Intro -StartupProject WPF_EFCore
# id                  name safeName applied
# --                  ---- -------- -------
# 202108********_Init Init Init        True
# 202109********_Seed Seed Seed       False
```

`Init`につづいて`Seed`が追加されていることがわかる
この時点ではまだデータベースには変更が反映されていないので、`applied`は`false`である

ビジュアルで確認すると、プロジェクトのMigrationsフォルダは以下のようになっている

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/e409f86c-245d-ccd3-1688-31db9428a5b1.png)

## 移行をデータベースに反映させる`Update-Database`

プロジェクト内で完結していたコードの変更を、実際のデータベースに反映させる

```Powershell:パッケージマネージャーコンソール
Update-Database -Context BloggingContext -Project Intro
```

### すでにデータがある場合は失敗するかもしれない

実際にこの記事を書きながら、`Update-Database`をしたところ、下記のようなエラーが出た

> Violation of PRIMARY KEY constraint 'PK_Posts'. Cannot insert duplicate key in object 'dbo.Posts'. The duplicate key value is (4).
The statement has been terminated.

Google翻訳

> PRIMARYKEY制約「PK_Posts」への違反。オブジェクト 'dbo.Posts'に重複するキーを挿入できません。重複するキー値は（4）です。

たとえば、データベースを新規作成してから、シードを与えるマイグレーションをする間に
マニュアルでレコードを挿入する操作をしていた場合
**挿入した（既存の）データと、シードデータの主キーが衝突する**可能性があり、上記のようなエラーが出る

#### `Drop-Database`で解決する

[この記事の前段](https://qiita.com/t13801206/items/4c2e85fc2732c4de5533)の通り、今回はDockerコンテナのテスト環境を想定しているので、データベースを一から作り直すほうが早い

- 今からやること　→　データベースを削除して、まっさらな状態からシード処理が含まれている移行まで進む
- [おまけ](#おまけ)　→　シードデータの主キーを変える（推奨しない）

```Powershell:パッケージマネージャーコンソール
Drop-Database -Context BloggingContext -Project Intro
# Build started...
# Build succeeded.
# 確認
# この操作を実行しますか?
# 対象 "database 'Blogging' on server 'localhost, 11433'" に対して操作 "Drop-Database" を実行しています。
# [Y] はい(Y)  [A] すべて続行(A)  [N] いいえ(N)  [L] すべて無視(L)  [S] 中断(S)  [?] ヘルプ (既定では "Y"):y
# Dropping database 'Blogging' on server 'localhost, 11433'.
# Successfully dropped database 'Blogging'.
```

#### データベースを削除したところで、もう1度`Update-Database`

再度データベースをアップデートする
`-Migration`オプションで途中の移行先を指定しない限り、最後まで移行が進む

> コマンドは既定で最後の移行になります。

[Update-Database - Entity Framework Core ツールリファレンス](https://docs.microsoft.com/ja-jp/ef/core/cli/powershell#update-database)

```Powershell:パッケージマネージャーコンソール
Update-Database -Context BloggingContext -Project Intro
# Build started...
# Build succeeded.
# Applying migration '20210828010156_Init'.
# Applying migration '20210903234151_Seed'.
# Done.
```

`Init`が適応され、つづいて`Seed`が適応され、順番に移行が済んだことがわかる

# データベースを確認する

[前回と同じ手順](https://qiita.com/t13801206/items/9330dd3a540966261320#%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B)をたどって、データベースをテーブルを確認する

## データベースに入るまで

Dockerコンテナに入って、さらにデータベースを選択する

```Powershell:Dockerコンテナに入る
docker exec -it sql1 "bash"
$ /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -W
```

```Powershell:データベースを選ぶ
1> USE Blogging;
2> GO
Changed database context to 'Blogging'.
```

## テーブルのデータをみる

`Blogs`テーブルと、`Posts`テーブルのそれぞれにシードデータが存在している

```Powershell:Blogsテーブル
1> SELECT * FROM Blogs;
2> GO
BlogId Url Rating
------ --- ------
1 http://one.com 0
2 http://two.com 0

(2 rows affected)
```

```Powershell:Postsテーブル
1> SELECT * FROM Posts;
2> GO
PostId Title Content BlogId
------ ----- ------- ------
1 title11 content11 1
2 title12 content12 1
3 title23 content23 2
4 title24 content24 2

(4 rows affected)
```

# おわりに

`DbContext`継承クラスのメソッドに数行追加することで
データベース作成時に初期データを与えることができた

# おまけ

すでにデータがある状態で、シードデータを与えるとどうなるか？

:::note warn 
既存データとマイグレーション時のシードデータが重複しないように注意してやればUpdateが通るのか？がこの章の目的
「『すでにあるデータ』のキーが既知であること」が前提であり、そんなこといちいち関知できないので。
:::

## 既存のデータ

`Posts`テーブル（上）と`Blogs`テーブル（下）に、それぞれにデータが入っている

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/f344dfdb-34e2-1b4a-5c40-4b0125c7471f.png)

## シードデータをあたえる

既存のデータの主キーと重複しないように、シードデータをあたえる

シードデータ：

- `BlogId`は11, 22
- `PostId`は11, 22, 33, 44

結果、既存のデータに加えて、マイグレーションによるデータシード処理がおこなわれた

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/3e828af7-a93a-e72e-985e-169449696f97.png)

## つづいてマニュアルでデータを追加する

主キーを指定せず、あらたにデータを追加すると

シードデータの末尾番号からの連番で新規データが追加される

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/df811f86-3be1-c0d8-a80d-da8acbb6e6de.png)

```cs:データ追加のコード
private void Button_CreateNewBlog(object sender, RoutedEventArgs e)
{
    _context.Add(new Intro.Blog() { Url = $"Url{DateTime.Now.Second}" });
    _context.SaveChanges();
}
private void Button_CreateNewPost(object sender, RoutedEventArgs e)
{
    var blogId = _context.Blogs.OrderByDescending(x => x.BlogId).FirstOrDefault().BlogId;

    _context.Add(new Intro.Post() { Title = $"PostTitle{DateTime.Now.Second}", BlogId = blogId });
    _context.SaveChanges();
}
```

キーが衝突しなければ、データベースにデータが入っている状態でもシードデータをあたえることができるとわかった

以上。