この記事は
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つ追加する
接続文字列を自分の環境にあわせる
BloggingContext
のOnConfiguring
メソッドを一部変更する
SQL Server DockerコンテナのBlogging
データベースに接続する
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 サーバーを起動する
# コンテナを起動する
docker start sql1
sql1
については、関連する過去の記事を参照
EF Core ツールのコマンドを使って移行する
コードファーストでSQLサーバーに新しいDBをつくっていく
具体的には、Visual Studio のパッケージマネージャーコンソールから、EF Core ツールのコマンドを打っていく作業となる
移行というのが、モデルからDBを作成、または変更していくことを表している
前準備
リファレンスを準備して、共通パラメータを確認する
EF Core コマンドツールのドキュメント
コマンドは10個くらいあるらしい
今回は移行に関するものだけを使う
共通パラメータ
共通パラメータである-Project
を省略すると
パッケージマネージャーコンソール の 既定のプロジェクト がターゲットプロジェクトとして使用されます
とのこと。
毎回プルダウンから既定のプロジェクトを選びなおしてもいいし
あるいは毎回-Project
を指定してもいい
また、-V
or -Verbose
パラメータをつけると、実行中の詳細情報を見ることができる
コマンドからエラーが返ってきたときには、エラーの原因を探るために-V
をつけて再度コマンドを打つ
〈本題〉モデルを移行する
3つのコマンドを順番に打って、モデルをDBに移行する
-
Get-DbContext
・・・使用可能なDbContext
を取得する -
Add-Migration
・・・移行を追加する -
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サーバー)をスタートさせているので、その中に入る
# コンテナに入る
docker exec -it sql1 "bash"
SQL Serverにログインする
localhost
にユーザIDsa
でログインする
パスワードを聞かれるので入力する
# DBサーバーにログインする。
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -W
-W
オプションをつけると、画面への出力が整形されてイイ感じになる(参考にさせていただいた記事)
DB一覧を取得する
既存のDBに加えて、Blogging
データベースが新たに追加されている
1> SELECT name FROM sys.databases;
2> GO
name
----
master
tempdb
model
msdb
+ Blogging
作業するデータベースをBlogging
にする
作業するデータベースをUSE
で指定する
使うのはもちろんBlogging
データベース
1> USE Blogging;
2> GO
Changed database context to 'Blogging'.
今いるデータベースの名前を取得すると、Blogging
と表示される
1> SELECT DB_NAME();
2> GO
-
Blogging
ちなみに自分の環境では、デフォルトはmaster
だった
テーブル一覧を取得する
TYPE = 'U'
のテーブル一覧を取得する
TYPE
を指定しないと、たくさんのテーブルが表示される
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
の中を覗いてみる
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 のバージョンと一致している
テーブルのフィールドを確認する
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
フィールドは存在しない
いずれれもstring
やint
などのプリミティブ型のみがテーブルのフィールドに反映されて
List
やBlog
などの集約関係や実態などはテーブルには反映されていないことがわかる
マイグレーションしたときのコード
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の挙動を理解したい