LoginSignup
0
2

More than 1 year has 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パラメータで使うコンテキストを指定しないと、エラーが発生する

パッケージマネージャーコンソール
# -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

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

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

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

パッケージマネージャーコンソール
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(Docker版SQLサーバー)をスタートさせているので、その中に入る

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

SQL Serverにログインする

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

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

-Wオプションをつけると、画面への出力が整形されてイイ感じになる(参考にさせていただいた記事

DB一覧を取得する

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

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

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

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

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

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

sqpcmd
1> SELECT DB_NAME();
2> GO

-
Blogging

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

テーブル一覧を取得する

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

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> BlogsDbSet<Post> Postsである

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

__EFMigrationsHistoryの中を覗いてみる

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

(1 rows affected)

MigrationIdProductVersionの2つのフィールドが存在する

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

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

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

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

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

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

image.png

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

マイグレーションしたときのコード
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");
}

おわりに

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

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

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

関連する過去の記事

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