ツナ缶雑記

ぐうたらSEのブログです。主にマイクロソフト系技術を中心に扱います。

Entity Framework Core でテーブルスキーマを更新する

f:id:masatsuna:20210711202657p:plain

2022 年 1 月 27 日 更新

  • 掲載していたサンプルを .NET 6.0.1 に置き換えました。

Entity Framework Core を利用して、テーブルのスキーマを更新する方法について解説していきます。 今回は、前回構築したアプリケーションを利用して、テーブルのスキーマを更新します。

tsuna-can.hateblo.jp

環境

データモデルにカラムを追加する

今回は、 Product モデルクラスに Publisher プロパティを追加します。

public class Product
{
    private ProductCategory? productCategory;

    public long ProductId { get; set; }

    public string ProductName { get; set; } = string.Empty;

    public string? ProductDescription { get; set; }

    public decimal? Price { get; set; }

    public string? Publisher { get; set; }  // ★追加行

    public long ProcuctCategoryId { get; set; }

    public byte[] RowVersion { get; set; } = Array.Empty<byte>();

    public ProductCategory ProductCategory
    {
        get => this.productCategory ?? throw new InvalidOperationException("Uninitialized property: " + nameof(ProductCategory));
        set => this.productCategory = value;
    }
}

DbContext の修正

続いて、 OnModelCreating メソッドの修正も行っていきます。 新たに Publisher プロパティを追加したので、それに対応するカラムの設定を行います。 今回は 256 文字の文字列長制限を指定するだけとしています。

またカラムの追加に伴って、初期データの登録処理も修正を行っています。 前回の記事で作成してあった初期データに、 Publisher のデータも追加しておきます。

public class ProductDbContext : DbContext
{
    // コンストラクター、プロパティは省略

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<Product>(entity =>
        {
            // テーブル名の設定
            entity.ToTable("Products");

            // 主キーの設定
            entity.HasKey(product => product.ProductId);

            // 各カラムの制約条件、カラム名の設定
            entity.Property(product => product.Price)
                .HasColumnType("decimal(18,0)");
            entity.Property(product => product.ProductName)
                    .HasColumnName("Name")
                    .HasMaxLength(256)
                    .IsRequired();
            entity.Property(product => product.ProductDescription)
                .HasMaxLength(1024);
            entity.Property(product => product.Publisher)  // ★追加行
                .HasMaxLength(256);

            // 外部キー制約の設定
            entity.HasOne(product => product.ProductCategory)
                    .WithMany(productCategory => productCategory.Products)
                    .HasForeignKey(product => product.ProcuctCategoryId)
                    .OnDelete(DeleteBehavior.ClientSetNull)
                    .HasConstraintName("FK_Products_ProductCategories");

            // 行バージョンカラムの設定
            entity.Property(product => product.RowVersion)
                    .IsRequired()
                    .IsRowVersion()
                    .IsConcurrencyToken();

            // マスターデータの登録(Publisher の値を追加設定)
            entity.HasData(new Product { ProductId = 1, ProductName = "C#の本", ProcuctCategoryId = 1, Price = 2000, Publisher = "DOTNET" });
            entity.HasData(new Product { ProductId = 2, ProductName = "Visual Studioの本", ProcuctCategoryId = 1, Price = 2200, Publisher = "DOTNET" });
            entity.HasData(new Product { ProductId = 3, ProductName = ".NETの本", ProcuctCategoryId = 1, Price = 2500, Publisher = "DOTNET" });
            entity.HasData(new Product { ProductId = 4, ProductName = "冷蔵庫", ProcuctCategoryId = 2, Price = 150000, Publisher = "HUTABISHI" });
            entity.HasData(new Product { ProductId = 5, ProductName = "トランプ", ProcuctCategoryId = 3, Price = 280, Publisher = "HONTENDO" });
        });

        // ProductCategories の方は省略
    }
}

Migration の追加作成

ここまで来たら、後は前回同様、 Migration を作成していきます。 作成前に、一度ソリューションをビルドしておきましょう。 ビルドが完了したら、 Visual Studio 内のターミナルから Migration を作成します。 ターミナルについてはこちらを参照してください。

tsuna-can.hateblo.jp

ターミナルで `DbContext' を継承したクラスがあるプロジェクトのルートディレクトリに移動します。 移動したら、以下のコマンドを実行します。

dotnet ef migrations add AddPublisherColumn

今回は Publisher カラムを追加する修正を行っているため、それを端的に表すように Migration の名前を付けています。 処理が正常に完了すると、 Migrations ディレクトリに作成した Migration が追加されます。

f:id:masatsuna:20220128164637p:plain

これで、前回作成した InitialCreate と、 AddPublisherColumn の 2 つの Migration が作成されたことがわかります。

作成した Migration を確認してみる

追加作成した Migration の中身を見てみましょう。

public partial class AddPublisherColumn : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.AddColumn<string>(
            name: "Publisher",
            table: "Products",
            type: "nvarchar(256)",
            maxLength: 256,
            nullable: true);

        migrationBuilder.UpdateData(
            table: "Products",
            keyColumn: "ProductId",
            keyValue: 1L,
            column: "Publisher",
            value: "DOTNET");

        migrationBuilder.UpdateData(
            table: "Products",
            keyColumn: "ProductId",
            keyValue: 2L,
            column: "Publisher",
            value: "DOTNET");

        migrationBuilder.UpdateData(
            table: "Products",
            keyColumn: "ProductId",
            keyValue: 3L,
            column: "Publisher",
            value: "DOTNET");

        migrationBuilder.UpdateData(
            table: "Products",
            keyColumn: "ProductId",
            keyValue: 4L,
            column: "Publisher",
            value: "HUTABISHI");

        migrationBuilder.UpdateData(
            table: "Products",
            keyColumn: "ProductId",
            keyValue: 5L,
            column: "Publisher",
            value: "HONTENDO");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropColumn(
            name: "Publisher",
            table: "Products");
    }
}

Migration クラスには、 Up メソッドと Down メソッドが自動的に生成されます。 Up メソッドには、修正した内容をデータベースに反映するための処理が記述されています。 今回は Publisher カラムの追加と、初期データの修正を行いました。 その修正内容が、そのまま Up メソッドに反映されていることがわかると思います。

それに対して Down メソッドは、今回行った修正を元に戻すための処理が記述されています。

データベースの更新

Migration を作成したので、続いてデータベースの更新も行います。 データベースの更新は、前回と同様、以下のコマンドで実行します。

dotnet ef database update

このコマンドを実行すると、現在のデータベースに適用されている Migration と、ソリューション内にある Migration を比較して、必要な Migration を順番に適用してくれます。 今回はすでに InitialCreate の Migration が適用されているデータベースが存在する状態でこのコマンドを実行するため、 AddPublisherColumn の Migration のみが適用されます。

実行が正常に完了すると、以下のように Products テーブルに Publisher カラムが追加されます。

f:id:masatsuna:20210711193702p:plain

また初期データの修正分もしっかり反映されています。 Products テーブルのデータを参照すると、 Publisher カラムに初期データがちゃんと登録されています。

f:id:masatsuna:20210711193920p:plain

データベースの状態を元に戻す

誤った Migration を適用してしまった場合など、データベースの状態を特定の Migration に戻すこともできます。 例えば AddPublisherColumn の Migration がすでに適用されている状態で、最初に作成した InitialCreate の Migration の状態に戻すには、以下のコマンドを実行します。

dotnet ef database update InitialCreate

このように、適用したい Migration を database update コマンドの引数に指定することで、指定した Migration に戻すことができます。

Migration の削除

不要な Migration がある場合、最新の Migration に限って削除することができます。 現時点では最初に作成した InitialCreate と、次に作成した AddPublisherColumn という 2 つの Migration が作成されています。 この状態で以下のコマンドを実行すると、最後に作成した AddPublisherColumn の Migration を削除することができます。

dotnet ef migrations remove

このコマンドを実行すると、 Migrations ディレクトリに追加されていたファイルも削除されます。 Migrations ディレクトリの中身は、 dotnet ef migration コマンドを利用して操作するようにしましょう。 プロジェクト内からコマンドを使わずに削除したり追加したりしないようにすることがポイントです。

まとめ

今回は Entity Framework Core でテーブルスキーマを更新する手順や、 Migration の適用、戻し方について解説しました。 Entity Framework Core は非常に便利な機能を持つ反面、使い方を誤るとめんどくさいことになりがちです。 用法容量を守って使いこなしたいですね。

サンプルソースについて

本稿で解説したソースコード一式は、 GitHub で公開しています。

github.com