【C#】SQL Server (Docker コンテナ) をDatabase Firstで操作してみる

タイトルもうちょっとどうにかならんかったか・・・orz

# この記事は

1. Microsoft SQL Server 2019のDockerコンテナを立て
1. C#の[EF Core](https://docs.microsoft.com/ja-jp/ef/core/)を使ってDBにアクセスし
1. 簡単な[CRUD動作](https://e-words.jp/w/CRUD.html)を試す

# Docker Desktop for Windows をインストールする

下記記事の動作確認まで行う（CentOSは不要）

https://qiita.com/gahoh/items/7b21377b5c9e3ffddf4a

Powershellで`docker -v`する
Dockerが正しくインストールされていれば、それらしき文字列が表示される

```Powershell:versionが表示されればOK
> docker -v
Docker version 20.10.5, build 55c4c88
# ↑それらしき文字列
```

# SQL Server 2019のコンテナを立てる

Docker Hub の Description に沿って進む

https://hub.docker.com/_/microsoft-mssql-server

## イメージをプルする

`2019-latest`を指定する

```Powershell
> docker pull mcr.microsoft.com/mssql/server:2019-latest
2019-latest: Pulling from mssql/server
```

## コンテナを立てる

:::note warn
パスワードには要件がある
8文字以下のパスワードを設定すると、コンテナが`RUNNING`状態にならない
:::

[Microsoft SQL Server の強力なパスワードの要件](https://support.microsoft.com/ja-jp/topic/microsoft-sql-server-%E3%81%AE%E5%BC%B7%E5%8A%9B%E3%81%AA%E3%83%91%E3%82%B9%E3%83%AF%E3%83%BC%E3%83%89%E3%81%AE%E8%A6%81%E4%BB%B6-84f0aa37-d9e6-7399-3478-b61beb5511ed)

```Powershell:コンテナを立てる
# PORTは11433にした
docker run --name sql1 -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=SqlPass1234' -p 11433:1433 -d mcr.microsoft.com/mssql/server:2019-latest
```

Powershellで`docker ps`を実行すると、`sql1`という名前のコンテナが生成されていることがわかる

```Powershell:プロセス確認
> docker ps
CONTAINER ID   IMAGE                                        COMMAND                  CREATED          STATUS          PORTS                     NAMES
8097100726be   mcr.microsoft.com/mssql/server:2019-latest   "/opt/mssql/bin/perm…"   24 minutes ago   Up 10 minutes   0.0.0.0:11433->1433/tcp   sql1
```

同様に、Docker Desktop の `Container/Apps` をみれば、`PORT: 11433`で`RUNNING`状態であることがわかる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/5f4d7bab-4b3e-c81c-6340-13b1a834094b.png)

参考：[DockerでSQL Serverを建ててsqlcmd, SSMS, JDBCでアクセスする ](https://aegif.jp/alfresco/tech-info/-/20201104-alfresco/1.3)

# 新しいデータベースをつくる

SSMSでDockerのSQL Serverへ接続する

https://docs.microsoft.com/ja-jp/sql/ssms/download-sql-server-management-studio-ssms

Dockerコンテナ（localhost, PORT 11433）にアクセスする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/01509cea-7f88-ba7b-e1c6-b15ae7a1be18.png)

`MyDockerDB`データベースを新規作成し、さらに`BlogTb`テーブルを追加した
`Id`は主キーかつ[IDENTITY](https://notepad-blog.com/content/181/)を指定している

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/55d8dda4-b9b3-67b0-6332-5769ea83af77.png)

# WPFプロジェクトをつくる

適当に作業用ディレクトリを作成し、Powershellでコマンドを打っていく
このプロジェクトからDBを操作する

```Powershell:Powershell
# 1. プロジェクトを作成する
> dotnet new sln
テンプレート "Solution File" が正常に作成されました。

> dotnet new wpf -o MainProject
テンプレート "WPF Application" が正常に作成されました。

> dotnet sln add .\MainProject\MainProject.csproj
プロジェクト `MainProject\MainProject.csproj` をソリューションに追加しました。

> cd .\MainProject\

# 2. DB操作に必要なパッケージをインストールする
> dotnet add package Microsoft.EntityFrameworkCore.Tools
  復元対象のプロジェクトを決定しています...
# 以下同じ手順で4つのパッケージをインストールする

> dotnet list package
プロジェクト 'MainProject' に次のパッケージ参照が含まれています
   [net5.0-windows7.0]:
   最上位レベル パッケージ                                   要求済み    解決済み
   > Microsoft.EntityFrameworkCore                5.0.9   5.0.9
   > Microsoft.EntityFrameworkCore.Design         5.0.9   5.0.9
   > Microsoft.EntityFrameworkCore.SqlServer      5.0.9   5.0.9
   > Microsoft.EntityFrameworkCore.Tools          5.0.9   5.0.9
```

Visual Studioのパッケージマネージャー コンソールから`Get-Help`を打ち
EFユニコーンが表示されればOK

```Powershell:パッケージマネージャーコンソール
> Get-Help about_EntityFrameworkCore

                     _/\__
               ---==/    \\
         ___  ___   |.    \|\
        | __|| __|  |  )   \\\
        | _| | _|   \_/ |  //|\\
        |___||_|       /   \\\/\\
```

https://docs.microsoft.com/ja-jp/ef/core/cli/powershell

# プロジェクトにDBを移植する

先にSSMSで作っておいた`BlogTb`をプロジェクトに移植する
移植という言葉は適切ではないかもしれない
いわゆるデータファーストで、さっきつくった`MyDockerDB`をC#のモデルとして取り込む

EF Core ではないけれど参考↓

https://docs.microsoft.com/ja-jp/ef/ef6/modeling/designer/workflows/database-first

## データベースへ接続する

Visual Stduio のツール(T)>データベースへの接続(D)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/aeb009bc-2d0b-9198-2f5c-bfbfb1db72cd.png)

サーバーエクスプローラーに追加されたDBを右クリックしてプロパティの中の"接続文字列"をメモしておく

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/d3aee833-aee2-2756-dc9f-67fc60d1e3d5.png)

## データベースをもとにモデルを自動作成する

`MyDockerDB`データベースからモデルとコンテキストを自動生成する

```Powershell:パッケージマネージャーコンソール
> Scaffold-DbContext 'Data Source="localhost, 11433";Initial Catalog=MyDockerDB;User ID=sa;Password=SqlPass1234' Microsoft.EntityFrameworkCore.SqlServer
Build started...
Build succeeded.
To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.

# Google翻訳
# 接続文字列内の潜在的に機密性の高い情報を保護するには、ソースコードから情報を移動する必要があります。 Name =構文を使用して構成から接続文字列を読み取ることにより、接続文字列のスキャフォールディングを回避できます。 
```

接続文字列がまる見えだと怒られつつも、プロジェクト内に`MyDockerDBContext.cs`（コンテキスト）と`BlogTb.cs`（POCOモデル）が生成された

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/b98a3571-1da0-5a24-f77d-b0da0e967d2b.png)

[Scaffold-DbContextのオプション](https://docs.microsoft.com/ja-jp/ef/core/cli/powershell#scaffold-dbcontext)を明記すれば、ModelやContextの生成先などを細かく指定できる

接続文字列の保護に関するお作法↓↓

https://docs.microsoft.com/ja-jp/dotnet/framework/data/adonet/protecting-connection-information

# CRUD操作をしてみる

CRUD用に4つメソッドを用意して、SSMSでDBの様子を都度確認する

![Animation2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/27d90fdf-0f98-2d57-396a-8789aafcf5cc.gif)

連続的にレコードを追加して、読み取って、更新して、連続的に削除して・・・をしている。

```cs:MainWindow.xaml.cs(コードビハインド)
        // 上から Create, Read, Update, Delete
        private void Button_Click1(object sender, RoutedEventArgs e)
        {
            _context.Add(new BlogTb() { Title = "a", Date = new System.DateTime() }); // Idは連番で勝手に振られる
            _context.SaveChanges();
        }
        private void Button_Click2(object sender, RoutedEventArgs e)
        {
            var d = _context.BlogTbs.FirstOrDefault();
            MessageBox.Show(d.Title);
        }
        private void Button_Click3(object sender, RoutedEventArgs e)
        {
            var d = _context.BlogTbs.FirstOrDefault();
            d.Title = "last"; // 先頭のタイトルをlastに変更する
            _context.SaveChanges();
        }
        private void Button_Click4(object sender, RoutedEventArgs e)
        {
            var d = _context.BlogTbs.FirstOrDefault();
            _context.Remove(d); // 先頭を削除する
            _context.SaveChanges();
        }
```

当たり前のように、このままだと
空のテーブルになってもなおDELETEをするとエラーになる

# おわりに

普段 Docker は使わないので、言葉遣いや操作に誤りがあれば、ご指摘いただけると幸いです

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

https://qiita.com/Nossa/items/94e5d15a9f05b3bc1b02

https://qiita.com/takanemu/items/ebca534db398aa9cce34

https://docs.microsoft.com/ja-jp/nuget/consume-packages/install-use-packages-dotnet-cli

https://docs.microsoft.com/ja-jp/sql/relational-databases/databases/create-a-database