【T7b】複数のモデルクラスの連携(5/9)
プロジェクトタイプ | (注意: 本文参照) |
---|---|
プロジェクト名 | T7b |
ソリューション名 | PIT7 |
注意
- 本ページの作業内容は 前のページまでの続き になっていることに注意せよ.
- 先に前のページまでをすべて読み,指示されている作業を済ませてから本ページを読むこと.
- プロジェクトの作成作業については準備作業を参照せよ.
7b-5. 新たなモデルクラスの作成
それでは実際に「学科」を表すモデルクラスを作成してみよう. この「学科」の情報には_に示す項目を含めることにしよう. 主キーと学科名だけを含むシンプルなモデルクラスである.
項目名 | 必須/任意 | データ型 | 備考 |
---|---|---|---|
ID | 必須 | 整数 | 学科のID. |
名前 | 必須 | 文字列 | 学科名. |
この「学科」に対応するモデルクラスを定義しよう.クラス名は「学科」を単純に
英訳してDepartment
とする.まずプロジェクト内の Models フォルダを右クリックし,「追加」→「クラス」をクリックする.
作成するクラス名を訊かれるのでDepartment
(.csは省略可能)と入力して「追加」ボタンをクリックする.
すると空のクラス定義が作られるので_の定義を書き込もう.
|
|
このクラスがデータベース側に反映されるようにするには,データコンテキストクラスにも手を加える必要がある. Data/T7bContext.cs に_に示す内容を追記しよう.
|
|
さてここまでの追記によって作られたのは,「学生」と関係性を持たない独立した「学科」というモデルクラスである.
「学科」と「学生」とのあいだに一対多の関係を作るには,「多」の側のモデルクラス─つまりこの場合はStudents
クラスに
二つのプロパティを追加する必要がある. Models/Student.cs に_に示す内容を追記しよう.
|
|
①の int 型のプロパティDepartmentId
がデータベース上で外部キーとなるプロパティである.
このプロパティはこの例のように
という名前である必要がある.「1」側
←の主キープロパティ名
②は ナビゲーションプロパティ といい,C#のプログラム上でその「学生」が在籍している「学科」のデータ,
すなわち Department
クラスのオブジェクト にアクセスするためのプロパティである. ナビゲーションプロパティは
①で参照しているモデルクラス(Department
クラス)のNull許容型にしておく必要がある .ちなみに,「多」の側から「1」の
側のモデルクラスを参照するナビゲーションプロパティを参照ナビゲーションプロパティ という.
Department
プロパティにアクセスすると,DepartmentId
プロパティの値と等しい
Id
プロパティの値を持つDepartment
クラスのオブジェクトを参照することができる.
注意する点は,外部キーとして使用されるDepartmentId
プロパティは実際にデータベースにテーブルの列として反映されるが,
ナビゲーションプロパティそれ自体は データベース上で反映されるものではない ことである.
ナビゲーションプロパティはあくまでもプログラミングをする上での利便性のためのものである が,非常に重要なものである.
EF Core における外部キーのようなリレーションシップの詳細に関しては
Relationships - EF Core - Microsoft Docs
を読むとよいだろう.
ここまで追記ができたら,コマンドラインターミナル1で_に 示すコマンドを実行して,マイグレーションコードの生成とデータベース側への適用を行ってみよう(_). この際にカレントディレクトリが,操作対象のプロジェクトのプロジェクトフォルダになっていることを確認しておこう. また,このコマンドを実行する前に一度以上プロジェクトをビルドしておく必要があることも注意しておこう.
先に書いておくと,この状況では dotnet ef database update
コマンドはエラーとなって失敗する .
もし,dotnet ef database update
コマンドがエラーにならない場合は,ここまでの
手順で何か間違いを犯している可能性がある.そのまま進まずにそれまでの作業内容を見直し場合によってはこの章の最初からのやり直しを検討すること.
PS>
dotnet ef migrations add AddDepartmentPS>
dotnet ef database update
なぜ失敗してしまうのだろうか?エラーメッセージを見てみると以下のように書かれているはずである.
23503: insert or update on table "Students" violates foreign key constraint "FK_Students_Departments_DepartmentId"
頭の数値は単にエラーコードだが,メッセージの部分を直訳すればおおむね以下のような内容である.
23503: "Students"テーブルに対する挿入または更新が,外部キー制約"FK_Students_Departments_DepartmentId"に違反しました.
_のStudent
クラスの変更によって,データベース側のテーブルにどのような変更が行われるのか,マイグレーションコードをもとに考えてみよう.
dotnet ef migrations add
コマンドによって, Migrations フォルダに
が生成されているはずなので開いて内容を確認してみよう.このコードは_のようになっているはずである.なお,_には説明のためにコメントを追加してある.
もし,_ のようになっていない場合は,ここまでの操作を間違えている可能性がある.
手順を見直し適切に修正すること .決して,_のコードを手打ちするなどしてつじつまを合わせてはならない.コマンドを実行した日時
_AddDepartment.cs

コメントで示した通り,Up()
メソッドでは主に以下の3つの作業をこの順番で行っている.
- ①
Students
テーブルにDepartmentId
列を追加する. - ②
Departments
テーブルを作成する. - ③
Students
テーブルのDepartmentId
列に外部キー制約を追加する.
エラーの原因一つはDepartmentId
プロパティがNull 非 許容な,つまり 値の設定が必須な型 のプロパティであることである.
この場合,対応するデータベース上の列にはNOT NULL制約が課せられることは先ほど説明した.したがって,この列は空にする(NULL
をセットする)ことができない.
そしてもう一つの原因は, ②で作成したばかりのDepartments
テーブルには当然のことながら一件もレコードが登録されていない ことである.
Students
テーブルのDepartmentId
プロパティは,外部キーとなるプロパティであり,Departments
テーブル内のいずれかの
レコードの主キーと同じ値を持つ必要がある.しかし, Departments
テーブルが現時点ではそもそも空なのでこの列にセットできる有効な値が全く存在しない のである.
Students
テーブルには,もともと5件程度のレコードを登録しているはずであるが,この外部キー制約を伴うDepartmentId
列を
追加したことによって 既存のすべてのレコードが制約違反になってしまう のである.
これを避ける方法はいくつか存在する.1つは,Up()
メソッド内で③の前にDepartments
テーブルにSql()
メソッドなどで
INSERT文を実行して適当なレコードを追加し,そのレコードのIDをUPDATE文で既存のStudents
テーブルのレコードにセットする方法である.
これは分かりやすい反面,モデルクラスの値を手作業で(≒生のSQL文を用いて直接的に)データベースに登録することにほかならず,構築が複雑となるようなモデルクラスが
あった場合に作業が極めて煩雑になってしまうという欠点がある.もう一つは,そもそもDepartmentId
に NOT NULL 制約が課されないように
するという方法である.これはStudents
クラスのDepartmentId
プロパティの型を int 型から int? 型に変更するだけで実現できる.
この方法であれば制約違反は発生しないが,同時に 学生が所属する学科がNULL
である という状況を許すようになってしまう.
先ほどの分析の通り,学生は必ずいずれかの学科に所属しているはずでありそれを未設定にできてしまうのは問題である.
もう一つの方法は,マイグレーション処理を「1.Department
クラスの追加」と「2.Students
クラスへのDepartmentId
プロパティの追加」の
2つに分割する ことである.実はデータコンテキストクラスに手を加えることによって, 特定のモデルクラスに決め打ちのレコードをあらかじめ追加しておくことが可能 なのである.
「1.Department
クラスの追加」のためのマイグレーション時にあらかじめ「学科」のレコードを登録しておき,
「2.Students
クラスへのDepartmentId
プロパティの追加」時に登録したレコードの情報をセットすれば問題なくマイグレーションを行うことができる.
次節では実際にこのアプローチでマイグレーション処理を作成してみよう.
そのまえにこの失敗するマイグレーション処理は削除しておこう.幸い dotnet ef database update
コマンドは先ほどエラーとなったので,
データベース側にはいかなる変更も行われてはいない.そのため_に示すように dotnet ef migrations remove
コマンドを実行しておくだけでよい.
PS>
dotnet ef migrations remove
また,_でStudent
クラスに追加した外部キープロパティとナビゲーションプロパティも
_のようにいったんコメントアウトしておこう.
|
|
本節で覚えておいてほしいことは,「 すでにレコードが存在しているテーブルに,必須な(=Null非許容な)外部キープロパティ(列)をいきなり追加してはならない 」ということである. その必要がある場合は,前述の通りマイグレーションを2つに分けるのが無難な方法である.
タブのタイトルは「開発用PowerShell」もしくは「Developer PowerShell」となっている. ↩︎