情報応用演習Ⅰ(2024)

【T7a】データモデルの変更とマイグレーションの設計(5/7)

プロジェクトタイプ(注意: 本文参照)
プロジェクト名T7a
ソリューション名PIT7
注意
  • 本ページの作業内容は 前のページまでの続き になっていることに注意せよ.
    • 先に前のページまでをすべて読み,指示されている作業を済ませてから本ページを読むこと.
    • プロジェクトの作成作業については準備作業を参照せよ.

7a-5. 手動でマイグレーション処理を記述する必要があるケース

つぎに,dotnet ef migrations add コマンドによって自動生成されるコードだけではうまくいかない他のケースも見ておこう.

先ほど説明した通り dotnet ef migrations add コマンドは,モデルクラスの新旧の定義を比較してマイグレーションコードを自動生成する. これは列の追加や削除といった形でコードに現れる.前節までの例では列(プロパティ)の追加や削除に関しては,少なくとも正しいマイグレーションコードが 生成されていた.では 既存の列(≒プロパティ)の名前や型,制約なども同時に変更 した場合はどうなるだろうか.実際に試してみよう.

_に示すように,StudentクラスのMiddleNameプロパティに以下二点の変更を行ってみよう.

  1. 名前の変更
    • プロパティ名をMiddleNameからHogeに変更する.
  2. 制約の変更
    • Null非許容の文字列型(?のつかないstring型)にして必須の項目にする.
プロパティの定義の変更
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Student
{
    [Display(Name = "ID")]
    public int Id { get; set; }                   // ID

    [Display(Name = "姓")]
    public string LastName { get; set; } = "";    // 姓

    [Display(Name = "ミドルネーム")]
    public string Hoge { get; set; } = "";        // ミドルネーム    
    
    [Display(Name = "名")]        
    public string FirstName { get; set; } = "";   // 名

    [Display(Name = "性別")]
    public SexType Sex { get; set; }              // 性別

    [Phone]        
    [Display(Name ="電話番号")]
    public string PhoneNumber { get; set; } = ""; // 電話番号

    [EmailAddress]
    [Display(Name = "メールアドレス")]
    public string? Mail { get; set; }             // メールアドレス

    [DataType(DataType.Date)]
    [Display(Name = "誕生日")]
    public DateTime? Birthday { get; set; }       // 誕生日

    [DataType(DataType.DateTime)]
    [Display(Name = "登録日時")]
    public DateTime Registered { get; set; }      // 登録日時
}

_の追記ができたらコマンドラインターミナル 1_に示すコマンドを実行しよう.

マイグレーション処理の生成
PS> dotnet ef migrations add ChangeMiddleName

実行するとマイグレーションコードが生成される.このさい_に示すような警告が表示されるはずである. 先に書いておくと,この状態で絶対に「dotnet ef database update」コマンドを実行してはいけない

マイグレーションコードの生成(警告が表示される場合)
An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.

この警告を直訳すると「 データのロスにつながる操作 がスキャフォルドされました. 的確さのためマイグレーションをレビューしてください」といったところだろうか.もう少し噛み砕いて言えば 「 このまま実行するデータベースから既存のデータが失われるかもしれないから,生成されたコードを確認してね 」ということである. 言われた通りこのマイグレーションコードの内容を確認してみよう.プロジェクト内の Migrations フォルダに コマンドを実行した日時_ChangeMiddleName.csというファイルが生成されているはずなので, これを開いてみよう.このファイルの内容は_のようになっているはずである なお,コメントは著者によるものである. もし,_ のようになっていない場合は,ここまでの操作を間違えている可能性がある. 手順を見直し適切に修正すること決して_のコードを手打ちするなどしてつじつまを合わせてはならない

プロパティ名の変更によって生成されるマイグレーションコード

生成されたUp()およびDown()メソッドの内容はおおむね以下のようになっている.

  • Up()メソッドの内容
    1. MiddleName列の削除(①; 11~13行目)
    2. Hoge列の追加(②; 15~20行目)
  • Down()メソッドの内容
    1. Hoge列の削除(③; 25~27行目)
    2. MiddleName列の追加(④; 29~33行目)

APIの詳細についてはここでは省略するが,テーブルに対する列の削除は DropColumn()メソッドの呼び出しによって,列の追加は AddColumn()メソッドの呼び出しによって実現されている.

重要なのは_では,今回のクラス定義の変更,すなわちプロパティ名の変更と型(Null許容/非許容)の変更が, 列名の変更や制約の変更としては認識されていない ことである.dotnet ef migrations add コマンドは, モデルクラスの定義の変化を監視しているが,今回のような「MiddleNameプロパティがなくなりHogeプロパティが表れた」 という変化が「MiddleNameプロパティの名前がHogeに変更された」のか「MiddleNameプロパティが削除されて,新しくHogeプロパティが追加された」のか 区別が付かないのである.

実のところ現在の dotnet ef migrations add コマンドは,プロパティ名の単純な付け替え程度であれば 列名の変更として正しく認識できる場合も多い.しかし,今回のように必須かどうかといった制約の変更なども同時に行ってしまうと 前述のように列の追加と削除として認識されてしまうので注意が必要である.

このようなケースでは完全に手作業でマイグレーションコードを書く以外に方法はない. 列名の変更にはmigrationBuilderRenameColumn()メソッドを, 制約の変更にはAlterColumn()メソッドを使用する必要がある. また制約の変更に伴って既存列のデータもSql()メソッドを使って変換する必要がある. ChangeMiddleNameクラスのUp()/Down()メソッドを_に示すように書き換えよう.

プロパティ名の変更のためのマイグレーションコード
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace T7a.Migrations
{
    public partial class ChangeMiddleName : Migration
    {
        protected override void Up(MigrationBuilder migrationBuilder)
        {
            // NOT NULL 制約をつけるため,あらかじめ NULL な MiddleName 列を空文字列 '' で埋めておく.
            migrationBuilder.Sql("UPDATE \"Students\" SET \"MiddleName\" = '' WHERE \"MiddleName\" IS NULL");

            // MiddleName 列に NOT NULL 制約を追加する.
            migrationBuilder.AlterColumn<string>(name: "MiddleName", table: "Students", type: "text",
                nullable: false, defaultValue: "", oldClrType: typeof(string), oldType: "text", oldNullable: true);

            // MiddleName 列を Hoge という名前に変更する.
            migrationBuilder.RenameColumn(name: "MiddleName", table: "Students", newName: "Hoge");

        }

        protected override void Down(MigrationBuilder migrationBuilder)
        {
            // Hoge 列を MiddleName という名前に戻す.
            migrationBuilder.RenameColumn(name: "Hoge", table: "Students", newName: "MiddleName");

            // MiddleName 列から NOT NULL 制約を削除する.
            migrationBuilder.AlterColumn<string>(name: "MiddleName", table: "Students", type: "text",
                nullable: true, oldClrType: typeof(string), oldType: "text");

            // 空文字列が入っている MiddleName 列を NULL に戻す.
            migrationBuilder.Sql("UPDATE \"Students\" SET \"MiddleName\" = NULL WHERE \"MiddleName\" = ''");
        }
    }
}

書き換えたら 忘れずにソースコードを保存しプロジェクトをリビルドしてエラーがないことを確認してから dotnet ef database update コマンドを実行しよう.エラーなどがなければ, pgAdmin で Students テーブルの全内容を確認してみよう. _のようになるはずである.

プロパティ名の変更結果

さきほどまでMiddleNameという名前だった列が,その内容を維持したままHogeという名前に変更されていることが分かるだろう.

_のようなコードを書かずに,_のような自動生成のマイグレーション処理を何も考えずに適用してしまうと, 当然のことながらMiddleName列が単純に削除されてしまい, その列に入っていたデータが失われてしまう ので注意しよう.


  1. タブのタイトルは「開発用PowerShell」もしくは「Developer PowerShell」となっている. ↩︎

Last updated on 2024-06-10
Published on 2024-06-10

Powered by Hugo. Theme by TechDoc. Designed by Thingsym.