【T7a】データモデルの変更とマイグレーションの設計(3/7)
プロジェクトタイプ | (注意: 本文参照) |
---|---|
プロジェクト名 | T7a |
ソリューション名 | PIT7 |
注意
- 本ページの作業内容は 前のページまでの続き になっていることに注意せよ.
- 先に前のページまでをすべて読み,指示されている作業を済ませてから本ページを読むこと.
- プロジェクトの作成作業については準備作業を参照せよ.
7a-3. データモデルの変更とマイグレーションの必要性
チュートリアル【T5a】でも述べたが,アプリケーションが扱うデータモデルはアプリケーションの成長
(≒アップデート)とともに変化する.たとえば学生の名簿を管理しているアプリで,学則の変更などに起因して,新たに
「スカラシップ対象かどうか」というフラグを持たせる必要が生じることがあるかもしれない. EF Core では,
これは既存のモデルクラスにプロパティを追加することで実現する.もしチュートリアル【T5a】の
Student
クラス(_左)でこのことを行うならば,_右に示すように bool 型のプロパティを追加することになるだろう.
|
|
|
|
注意しなければならないのは, ASP.NET Core ではモデルクラスの定義は常にデータベース側のテーブル定義と関連づいていること である.
モデルクラスを変更しただけではデータベース側のテーブルは変更されない.こういった モデルクラスの定義の変更をデータベースの
テーブル定義に反映させること がマイグレーションの役割の一つである.
チュートリアル【T5a】で説明したように,dotnet ef migrations add
コマンドで生成した
マイグレーションのためのコード( Migrations フォルダの .cs ファイル)はこのことを行っている.
マイグレーションのもう一つの役割は, データベース上に既にあるデータを新しいデータ定義に合わせること である.
例えば,_のStudent
クラスには学生の名前に関しては姓(FirstName
)と名(LastName
)しかないが,
留学生が増えたので ミドルネーム を管理する必要が出てきたと仮定しよう.先ほどのStudent
クラスをこの要求に合わせるには
_のようなプロパティを追加すればよいだろう( 警告: まだこの内容は書き込まないこと ).

ミドルネームは全員が持っているわけではないのでMiddleName
プロパティは string のNull許容型( string? 型)となっている.
そして,この変更を行う前は ミドルネームはLastName
に空白区切りで書き込む,という運用をしていた と仮定する.
つまり現状のデータベース上はミドルネームは「姓」の列に書き込まれている,ということである(_).
この場合,データベース上でMiddleName
という列を追加するだけでなく,LastName
の列に書き込まれているミドルネームを削除して
MiddleName
の列に移し替える,という必要が出てくる.マイグレーションではこのような 既存データの変換作業 も必要である.
注意しなければならないのは,このような既存データの変換作業のためのコードは プログラマが手作業で書かなければならない ということである.
これは,プロパティ(≒列)の追加や削除に関しては,dotnet ef migrations add
コマンドが新旧のモデルクラスの定義を比較して自動生成してくれるが,
このような既存データの変換作業までは自動生成することができないためである.
実際にこのような既存データの変換作業のためのコードを記述してみよう.
まず, T7a プロジェクトを起動して_に示す学生情報を新規作成しよう(コピペ推奨,ただしペースト時に前後に半角スペースが入らないように注意).
# | 姓 | 名 | 性別 | 電話番号 | メールアドレス | 誕生日 |
---|---|---|---|---|---|---|
Ⅰ | 医療 | 太郎 | (適当でよい) | (適当でよい) | (適当でよい) | (適当でよい) |
Ⅱ | アウグスト ナイトハルト | カール | 同上 | 同上 | 同上 | 同上 |
Ⅲ | ライデノビッチ ライコフ | イワン | 同上 | 同上 | 同上 | 同上 |
Ⅳ | ファルブルケ ウィンゲーツ ヘルシング | インテグラル | 同上 | 同上 | 同上 | 同上 |
※ #
の列はIDではなく項番を表しているだけである.
学生の一覧画面に_のように表示されたら, pgAdmin で Students テーブルの内容も確認しておこう.
pgAdmin を起動して,「 Databases 」→「 t7a_db 」を右クリックして「 Query Tool 」をクリックし(_),
Students テーブルの全内容を表示するSQL文を実行して_で追加した情報が追加されていることを確認しよう(_).
「 Databases 」以下に「 t7a_db 」が表示されていない場合は,「 Servers 」→「 PostgreSQL 16 」→「 Databases 」を右クリックして「 Refresh 」を実行する(_).
なおId
列の値,つまり各レコードの主キーの値についてはデータの登録状況や経緯などによって変化するため必ずしも_の通りにはならないことを付記しておく.
次に実行を停止して,_に示すようにStudents
クラスにMiddleName
プロパティを追加する.
|
|
これで 今現在のデータベース上のテーブル定義とモデルクラスの定義が食い違うことになった .この状態で実行するとエラーになることを
確かめておこう.この状態で実行して_のようにIndex
アクションなど,データベースにアクセスするいずれかのアクションにアクセスすると,_に示すような例外が発生する.
データベースサーバー側でエラーが発生したことを示すPostgresException例外が発生している.
例外メッセージは「42703: column s.MiddleName does not exist
」のようになっていることが分かるだろう.筆者が訳すまでもないと思われるが,
このメッセージは「列 s.MiddleName は存在しない.
」と言っている.このときにデータベースサーバで直前に実行されたSQL文も確認してみよう.
データベースサーバでの直前のエラー内容を確認するには,コマンドラインターミナル 1 で_に示す
コマンドを実行する.
PS>
Get-Eventlog -LogName Application -Source PostgreSQL -EntryType Error | select -ExpandProperty Message -First 1
すると_の2行目ようなSQL文が実行されていることが分かるはずである.
2022-01-25 15:52:20.872 JST [47140] ERROR: column s.MiddleName does not exist at character 69
2022-01-25 15:52:20.872 JST [47140] STATEMENT: SELECT s."Id", s."Birthday", s."FirstName", s."LastName", s."Mail", s."MiddleName", s."PhoneNumber", s."Registered", s."Sex"
FROM "Students" AS s
ORDER BY s."Id"
強調した部分に着目しよう.このSELECT文ではMiddleName
という名前の列を要求している.では 現状のテーブル定義を確認してみよう .
pgAdminで「 Servers 」→「 PostgreSQL 16 」→「 Databases 」→「 t7a_db 」→「 Schemas 」→「 public 」→「 Tables 」→「 Students 」を右クリックして「 Properties 」をクリックしする(_).
すると Students テーブルについての詳細情報を表示する子ウィンドウが開くので「 Columns 」タブをクリックする.
この画面が今現在このテーブルに定義されている列の一覧である(_).
_に示すように, 現状ではStudents
テーブルにはMiddleName
などという列は含まれていない ことが分かるだろう.
ここまでの説明の通り モデルクラスの定義を単に変更するだけではデータベースのテーブル定義は一切変化しない ということを覚えておこう.
重要なことなのでもう一度書いて強調しておこう.モデルクラスの定義を単に変更するだけではデータベースのテーブル定義は一切変化しない.モデルクラスの定義を変更した際は以降に示すマイグレーション処理の作成と実行を行う必要がある.
ではモデルクラスの変更をデータベースに反映させる操作を行おう.プログラムを実行したままになっている場合は一旦停止して, コマンドラインターミナル 1 で_に示すコマンドを実行する. 注意としてこのコマンドを実行する前に カレントディレクトリが,操作対象のプロジェクトのプロジェクトフォルダになっている ことを確認しておこう.
このコマンドを実行する前に一度以上プロジェクトをビルドしておく必要がある.また,ソリューションに含まれるプロジェクトに1つでもコンパイルエラーがあるとこのコマンドは失敗することがある.このためこのコマンドを実行する際は,事前にソリューション全体を一度ビルドして一つもコンパイルエラーがないことを確認しておくこと.
PS>
dotnet ef migrations add AddMiddleName
これによって Migrations フォルダに
というファイルが生成される.
このファイルは_に示すような内容になっているはずである.なおコメントは筆者によるものである.
もし,_ のようになっていない場合は,ここまでの操作を間違えている可能性がある.
手順を見直し適切に修正すること .決して,_のコードを手打ちするなどしてつじつまを合わせてはならない.コマンドを実行した日時
_AddMiddleName.cs

以前の説明の通りUp()
メソッドは既存のデータベース定義を新しいデータベース定義に合わせるための処理を記述する場所,
Down()
メソッドはその変更を打ち消す処理を記述する場所である.
APIの詳細は省略するが,おおむね①の部分(11~15行目)ではAddColumn()
というメソッドによって,
Students
テーブルにMiddleName
という列を追加しており,逆に②の部分(19~23行目)では
DropColumn()
というメソッドによってMiddleName
列を削除している,ということが分かるだろう.
さきほど「 ミドルネームはLastName
に空白区切りで書き込む,という運用をしていた 」という状況設定を書いたが,
この自動生成されたマイグレーションコードではMiddleName
列の追加と削除を行うのみで,既存のレコードのLastName
列に
書きこまれているミドルネームを取り出してMiddleName
列に移し替えたり,その逆を行ったりといったコードは生成されていない.
これはある意味当然であるが EntityFramework Core にはもちろんそのような運用されていたということを知るすべはない ので,
「donet ef migrations add
」コマンドは最低限の変換処理のみを含むマイグレーションコードしか生成することしかできない のである.
したがって,「既存のレコードのLastName
列に書き込まれているミドルネームを取り出してMiddleName
列に移し替える」といった
新旧のデータのすり合わせ処理を実装するのはプログラマーの責任 ということである.
ではこのすり合わせ処理を実装してみよう.このAddMiddleName
クラスのUp
/Down
メソッドに_に示す
内容を追記する.
|
|
migrationBuilder
のSql()
メソッドは,その名の通りデータベース上でSQL文を実行するコマンドである.
Up()
メソッドでは,AddColumn()
メソッド(=列の追加)の直後に③のSQL文を実行して,既存のLastName
列を空白区切りで分解して
新たに追加したMiddleName
列にセットしている(同時にLastName
列に含まれていたミドルネーム部分を削除している).
またDown()
メソッドでは,DropColumn()
メソッド(=列の削除)の直前に②のSQL文を実行して,既存のMiddleName
列の内容を
空白区切りでLastName
列に結合している.
_の追記ができたら 忘れずにソースコードを保存してから ,コマンドラインターミナル 1 で_に示すコマンドを実行しよう.
PS>
dotnet ef database update
_を実行したら, pgAdmin で Students テーブルの全内容を確認してみよう.
MiddleName
列が追加され,LastName
列からミドルネームが削除されてMiddleName
列にその内容が移行したことが分かるだろう(_).
これは先ほどのUp()
メソッドの作用によるものである.