0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【C#】Entity Framework Core で移行したあと、sqlcmdを使ってSQL Serverを観察する

Last updated at Posted at 2021-08-29

この記事は

SQL Server (Docker版)をデータベースファーストで操作してみた記事の続き

前回は、既存のデータベースからモデル(DbContextを継承したクラス)を生成する、いわゆるスキャフォールディングを試した
スキャフォールディングについて書かれた、マイクロソフトのよくわかる記事

今回はモデルからデータベースを生成する方法、いわゆるコードファーストな方法を試す

EF Core で何ができるか

公式ドキュメントから一部引用させていただく

EF は、次のモデル開発アプローチをサポートしています。

既存のデータベースからモデルを生成する。
データベースに合わせてモデルのコードを手動で書く。
モデルが作成されたら、EF の移行 を使用して、モデルからデータベースを作成します。 移行により、モデルの変更に応じてデータベースを進化させることができます。

引用元:https://docs.microsoft.com/ja-jp/ef/core/#the-model

EF Core 個人的ロードマップ

  • 「既存のデータベースから~」は前回やった
  • 今回はモデルのコードを手動で書いて、そこから移行(Migration)を使ってデータベースをつくるところまでをでやりたい
  • 次回以降があれば、データベースを進化させるを試したい
    • 移行によりDBがどう変化していくか など

ソリューションにライブラリプロジェクトを追加する

さっそく作業を始める

まず最初に、EF Core ドキュメントを参考にして、ライブラリプロジェクトIntroをつくる
今回は、主にこのプロジェクトでの作業となる

スタートアッププロジェクトは、前回記事からの続きでWPF_EFCoreをつかう
(※ 前回記事ではプロジェクト名まで言及してないかも・・・)

開発者用パワーシェル
# プロジェクト作成
dotnet new classlib --framework "netcoreapp3.1" -o Intro

# ライブラリプロジェクトをスタートアッププロジェクトの参照に追加
dotnet sln add .\Intro\Intro.csproj
dotnet add .\WPF_EFCore\WPF_EFCore.csproj reference .\Intro\Intro.csproj

# ライブラリプロジェクトに必要なパッケージをインストール
dotnet add .\Intro\Intro.csproj package Microsoft.EntityFrameworkCore
dotnet add .\Intro\Intro.csproj package Microsoft.EntityFrameworkCore.SqlServer

プロジェクトにインストールしたパッケージを確認する

WPF_EFCoreおよびIntroのどちらも .NET Core 3.1 であり、EF Core 5系 が入っている

開発者用パワーシェル
dotnet list package
# プロジェクト 'WPF_EFCore' に次のパッケージ参照が含まれています
#    [netcoreapp3.1]:
#    最上位レベル パッケージ                                   要求済み         解決済み
#    > Microsoft.EntityFrameworkCore.Sqlite         5.0.9        5.0.9
#    > Microsoft.EntityFrameworkCore.SqlServer      5.0.9        5.0.9
#    > Microsoft.EntityFrameworkCore.Tools          5.0.9        5.0.9
#    > Prism.Unity                                  8.0.0.1909   8.0.0.1909
# 
# プロジェクト 'Intro' に次のパッケージ参照が含まれています
#    [netcoreapp3.1]:
#    最上位レベル パッケージ                                   要求済み    解決済み
#    > Microsoft.EntityFrameworkCore                5.0.9   5.0.9
#    > Microsoft.EntityFrameworkCore.SqlServer      5.0.9   5.0.9

なぜ .NET5 ではないのか?

Prism Template Pack から作った空のプロジェクトのデフォルトが .NET Core 3.1 であり
そこから .NET 5 に変更するのを忘れたため

Introプロジェクトにモデルを追加する

公式ドキュメントのモデルを参考にして、Introプロジェクトにクラスを3つ追加する

接続文字列を自分の環境にあわせる

BloggingContextOnConfiguringメソッドを一部変更する

SQL Server DockerコンテナBloggingデータベースに接続する

BloggingContext
 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
 {
     optionsBuilder.UseSqlServer(
+        "Data Source=\"localhost, 11433\";Initial Catalog=Blogging;User ID=sa;Password=SqlPass1234");
-        @"Server=(localdb)\mssqllocaldb;Database=Blogging;Trusted_Connection=True");
 }

接続文字列がベタ書きであることはいけない気がするも、ベストプラクティスが分からない&テストのため、こう書いている

SQLサーバーをたてる

コードファーストで書いたモデルをDBに移行するために
移行先となる Docker コンテナの SQL サーバーを起動する

Powershell
# コンテナを起動する
docker start sql1

sql1については、関連する過去の記事を参照

EF Core ツールのコマンドを使って移行する

コードファーストでSQLサーバーに新しいDBをつくっていく

具体的には、Visual Studio のパッケージマネージャーコンソールから、EF Core ツールのコマンドを打っていく作業となる

移行というのが、モデルからDBを作成、または変更していくことを表している

image.png

前準備

リファレンスを準備して、共通パラメータを確認する

EF Core コマンドツールのドキュメント

コマンドは10個くらいあるらしい
今回は移行に関するものだけを使う

共通パラメータ

共通パラメータである-Project を省略すると

パッケージマネージャーコンソール の 既定のプロジェクト がターゲットプロジェクトとして使用されます

とのこと。

毎回プルダウンから既定のプロジェクトを選びなおしてもいいし
あるいは毎回-Projectを指定してもいい

image.png

また、-V or -Verbose パラメータをつけると、実行中の詳細情報を見ることができる
コマンドからエラーが返ってきたときには、エラーの原因を探るために-Vをつけて再度コマンドを打つ

〈本題〉モデルを移行する

3つのコマンドを順番に打って、モデルをDBに移行する

  1. Get-DbContext ・・・使用可能なDbContextを取得する
  2. Add-Migration ・・・移行を追加する
  3. Update-Database ・・・移行をデータベースに反映する

2つ目の「移行を追加する」のイメージが難しいが
モデルの変更履歴をセーブポイントとしてローカルに残していくイメージに近いと思う

セーブポイントを実際にDBへ反映させるのが、3番目のアップデートになる

Get-DbContext

使用可能なDbContextを取得する

パッケージマネージャーコンソール
Get-DbContext -Project Intro -StartupProject WPF_EFCore
# Build started...
# Build succeeded.

# MyDockerDBContext
# BloggingContext  

スタートアッププロジェクトのMyDockerDBContextと、新規追加したプロジェクトのBloggingContextの2つのコンテキストが表示された

**今回使うのは、ライブラリプロジェクトに追加したBloggingContext**となる
以後、-ContextパラメータをつかってBloggingContextを毎回指定する
(指定しなかった場合の挙動について、次節で言及する)

〈参考〉Get-DbContextのパラメータを未指定としたときの動作

パラメータを省略すると、スタートアッププロジェクトにあるMyDockerDBContextだけが表示される
スタートアッププロジェクトから参照されるライブラリプロジェクトのBloggingContextは表示されない

パッケージマネージャーコンソール
Get-DbContext
# Build started...
# Build succeeded.

# MyDockerDBContext

Add-Migration

移行を追加する

  • 最初の移行は、DBの新規作成に相当する
  • その次からの移行は、1つ前の移行からの差分が記録される
パッケージマネージャーコンソール
Add-Migration -Name Init -Context BloggingContext -Project Intro -StartupProject WPF_EFCore
# Build started...
# Build succeeded.
# To undo this action, use Remove-Migration.
```

`Intro`プロジェクトに新しく`Migrations`フォルダが生成される
以後、`Add-Migration`をするたびに、移行履歴が記録されていく

移行の挙動については、また別の記事で試す予定

### `DbContext`が複数ある場合は`-Context`を省略してはいけない

`Get-DbContext`で確認したときに`DbContext`が2個あったので
`-Context`パラメータで使うコンテキストを指定しないと、エラーが発生する

```Powershell:パッケージマネージャーコンソール
# -Context 引数を省略した
Add-Migration -Name Init -Project Intro -StartupProject WPF_EFCore
```

> More than one DbContext was found. Specify which one to use. 
> Use the '-Context' parameter for PowerShell commands and the '--context' parameter for dotnet commands.

 〈意訳〉 2つ以上のDbContextが見つかったよ。どっちを使うか決めてね。Powershellをつかっている人は`-Context`パラメータを使うといいよ。

## `Update-Database`

移行をデータベースに反映する
何も指定しなければ、最後の移行が反映される

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

`Add-Migration`を繰り返すと移行先が複数になる
移行先を選択したい場合は、`-Migration`で指定する

```Powershell:パッケージマネージャーコンソール
Update-Database  -Project Intro -Context BloggingContext -Migration Init -StartupProject WPF_EFCore
# Build started...
# Build succeeded.
# No migrations were applied. The database is already up to date.
# Done.
```

すでに移行が完了しているので、「DB`up to date`されている」と表示された

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

ここまでの変更がDBに反映されていることを確認する

## SQL Server (Dokcerコンテナ)に入る

移行作業の前に`sql1`(DockerSQLサーバー)をスタートさせているので、その中に入る

```Powershell:Powershell
# コンテナに入る
docker exec -it sql1 "bash"
```

## SQL Serverにログインする

`localhost`にユーザID`sa`でログインする
パスワードを聞かれるので入力する

```bash:bash
# DBサーバーにログインする。
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -W
```

`-W`オプションをつけると、画面への出力が整形されてイイ感じになる([参考にさせていただいた記事](https://qiita.com/Topiman/items/cd213f172af4733240a5#transact-sql%E3%82%AF%E3%82%A8%E3%83%AA%E3%81%AE%E5%AE%9F%E8%A1%8C))

## DB一覧を取得する

既存のDBに加えて、`Blogging`データベースが新たに追加されている

```diff_sql:sqlcmd
  1> SELECT name FROM sys.databases;
  2> GO
  name
  ----
  master
  tempdb
  model
  msdb
+ Blogging
```

## 作業するデータベースを`Blogging`にする

作業するデータベースを`USE`で指定する
使うのはもちろん`Blogging`データベース

```sql:sqpcmd
1> USE Blogging;
2> GO
Changed database context to 'Blogging'.
```

今いるデータベースの名前を取得すると、`Blogging`と表示される

```sql:sqpcmd
1> SELECT DB_NAME();
2> GO

-
Blogging
```

ちなみに自分の環境では、デフォルトは`master`だった

## テーブル一覧を取得する

`TYPE = 'U'`のテーブル一覧を取得する
`TYPE`を指定しないと、たくさんのテーブルが表示される

```sql:sqpcmd
1> SELECT * FROM sys.objects WHERE TYPE = 'U';
2> GO
name object_id principal_id schema_id parent_object_id type type_desc create_date modify_date is_ms_shipped is_published is_schema_published
---- --------- ------------ --------- ---------------- ---- --------- ----------- ----------- ------------- ------------ -------------------
__EFMigrationsHistory 885578193 NULL 1 0 U  USER_TABLE ...
Blogs 917578307 NULL 1 0 U  USER_TABLE ...
Posts 949578421 NULL 1 0 U  USER_TABLE ...
```

3つのテーブルが表示された

`Blogs`テーブルと`Posts`テーブルは
移行した際にコンテキストに含まれていた`DbSet<Blog> Blogs`と`DbSet<Post> Posts`である

`__EFMigrationsHistory`は、その名からして、移行履歴を保持しているテーブルらしい

### `__EFMigrationsHistory`の中を覗いてみる

```sql
1> SELECT * FROM __EFMigrationsHistory;
2> GO
MigrationId ProductVersion
----------- --------------
202108********_Init 5.0.9

(1 rows affected)
```

`MigrationId`と`ProductVersion`の2つのフィールドが存在する

`MigrationId`は`Add-Migration`したときに生成された`.cs`ファイルの名前と一致しており
一方の`ProductVersion`は EF Core のバージョンと一致している

## テーブルのフィールドを確認する

```sql
1> SELECT * FROM Blogs;
2> GO
BlogId Url Rating
------ --- ------
```

`Blogs`テーブルには、モデルで指定した3つのフィールドが存在する
`List<Post>`の`Posts`フィールドは存在しない

```sql
1> SELECT * FROM Posts;
2> GO
PostId Title Content BlogId
------ ----- ------- ------
```

`Posts`テーブルには、モデルで指定した4つのフィールドが存在する
`Blog`型`Blog`フィールドは存在しない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/272511/53862ca8-d68c-9d6c-1066-7931fd04d1e5.png)

いずれれも`string`や`int`などのプリミティブ型のみがテーブルのフィールドに反映されて
`List`や`Blog`などの集約関係や実態などはテーブルには反映されていないことがわかる

<details><summary>マイグレーションしたときのコード</summary><div>

```cs:Initクラスから一部抜粋
protected override void Up(MigrationBuilder migrationBuilder)
{
    migrationBuilder.CreateTable(
        name: "Blogs",
        columns: table => new
        {
            BlogId = table.Column<int>(type: "int", nullable: false)
                .Annotation("SqlServer:Identity", "1, 1"),
            Url = table.Column<string>(type: "nvarchar(max)", nullable: true),
            Rating = table.Column<int>(type: "int", nullable: false)
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_Blogs", x => x.BlogId);
        });

    migrationBuilder.CreateTable(
        name: "Posts",
        columns: table => new
        {
            PostId = table.Column<int>(type: "int", nullable: false)
                .Annotation("SqlServer:Identity", "1, 1"),
            Title = table.Column<string>(type: "nvarchar(max)", nullable: true),
            Content = table.Column<string>(type: "nvarchar(max)", nullable: true),
            BlogId = table.Column<int>(type: "int", nullable: false)
        },
        constraints: table =>
        {
            table.PrimaryKey("PK_Posts", x => x.PostId);
            table.ForeignKey(
                name: "FK_Posts_Blogs_BlogId",
                column: x => x.BlogId,
                principalTable: "Blogs",
                principalColumn: "BlogId",
                onDelete: ReferentialAction.Cascade);
        });

    migrationBuilder.CreateIndex(
        name: "IX_Posts_BlogId",
        table: "Posts",
        column: "BlogId");
}
```
</div></details>

# おわりに

今回は、モデルからDBを作成するとこまでを試した

次は、実際にデータを入れて、さらにテーブルの構造を変えてみたりして
EF Core & SQL Serverの挙動を理解したい

# SQL Serverの操作に関して、参考にさせていただいた記事

https://qiita.com/zaburo/items/6edf7c05c5d4f5e039eb

https://qiita.com/qsuke92/items/51a85a58ac91782ee528

https://qiita.com/YamaDash82/items/b459ec502d8390708f45

# 関連する過去の記事

https://qiita.com/t13801206/items/4c2e85fc2732c4de5533

https://zenn.dev/t13801206/articles/2106-wpf-ef-study
0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?