データベースを削除してから作り直すと,DBに保存されている情報が全て削除されてしまいます. こういった事態を回避する方法として、データベースマイグレーションを行う方法があります。 マイグレーションとは、DBに保存されているデータを保持したまま、テーブルの作成やカラムの変更などを行うための機能です。
まずは簡単なデータベースマイグレーションを行うことにしましょう. あらかじめ,次のようなProductクラスを作成しておきます。
public class Product { public int ProductId { get; set; } public string Name { get; set; } public int Price { get; set; } }
また、このProductクラスにアクセスするためのContextである、ShoppingContextも作っておきましょう。
public class ShoppingContext : DbContext { public DbSet<Product> Products { get; set; } }
次にデータベースマイグレーションの準備を行います. まず, メニューバーのツール,Library Package Manager内にある NuGetのPackage Manager Consoleを起動してください.
その後,以下のコマンドを入力してマイグレーションの準備をします.
PM> Enable-Migrations
無事に成功すると,プロジェクト内にMigrationsというディレクトリと, 設定用のクラスファイルが自動的に作成されていると思います.
これでマイグレーションのための準備が整いました。
マイグレーションの機能を使って、Productsテーブルを生成しましょう。 EntityFrameworkには、Productクラスの中身を参照し、テーブル作成のためのクラスを自動生成してくれる機能が備わっています。 次のコマンドをPackage Manager Consoleに入力してください.
PM> Add-Migration InitialModels
InitialModelsの部分は、マイグレーションファイルのクラス名となります。できるだけ分かりやすい名前を付けましょう。
もし、ソリューション内に複数プロジェクトが存在する場合は、ProjectName とStartUpProjectName を指定する必要があります。 例えば、プロジェクト名がCodeFirstMigrationの場合は、次のように指定します。
PM> Add-Migration InitialModels -ProjectName CodeFirstMigration -StartUpProjectName CodeFirstMigration
ProjectNameは、生成したいテーブルのDbContextを含んでいるプロジェクト名を指定します。 StartUpProjectNameには、App.config等でDBへのConnectionStringsの設定がされているプロジェクト名を指定します。
Add-Migrationコマンドの発行に成功すると、Migrationsディレクトリ化に、作成日時 + _InitialModels.cs というファイルが生成されるはずです。 InitialModels.cs の中身は以下のようになります。
namespace CodeFirstMigration.Migrations { using System.Data.Entity.Migrations; public partial class InitialModels : DbMigration { public override void Up() { CreateTable( "Products", c => new { ProductId = c.Int(nullable: false, identity: true), Name = c.String(), Price = c.Int(nullable: false), }) .PrimaryKey(t => t.ProductId); } public override void Down() { DropTable("Products"); } } }
マイグレーションクラスには、UpとDownという二つの関数が存在します。 Upにはこれから変更したいDBへの変更を書きます。Downは反対に、変更を取り消したい場合に利用します。
Up関数内にあるCreateTable関数は、第一引数にこれから生成するテーブルの名前を、第2引数にはテーブルの定義を記述します。 Entity Frameworkでは、基本的にPrimaryKeyは必須のため、PrimaryKey関数を使ってPrimaryKeyの指定も行っています。
テーブル生成のためのクラスが作成されていることを確認したので、次はDBへマイグレートを行います。
Package Manager Consoleから次のコマンドを入力します。
PM> Update-Database -Verbose
Verboseコマンドはつける必要はありませんが、この引数を指定することによって、どのようなSqlがDBに発行されているのかを確認することができます。
複数プロジェクトがソリューション内に存在する場合は、Add-Migrationの時と同様に、ProjectNameとStartUpProjectNameの指定を行う必要があります。
PM> Update-Database -ProjectName CodeFirstMigration -StartUpProjectName CodeFirstMigration -Verbose
無事コマンド発行が成功すると、DBにProductsテーブルが生成されているはずです。 SQL SMSなどを使って確認してみましょう。
次に、前節で作成したProductクラスに、新しく在庫情報を管理するためのプロパティ、Stockを追加してみましょう。 DBマイグレーションの機能を使うと、データを保持したまま、テーブルの構成を簡単に変更できます。
まずは、以下のようにProductクラスにStockプロパティを追加します。
namespace CodeFirstMigration { public class Product { public int ProductId { get; set; } public string Name { get; set; } public int Price { get; set; } public int Stock { get; set; } } }
次のコマンドをPackage Manager Consoleに入力してください.
PM> Add-Migration AddProductsStock
マイグレーションファイルの名前は何でも構いませんが、特に理由のない場合は、Add + 変更したいクラスの複数形の名前+変更したプロパティ名 としましょう. 実行後,以下のようなAddProductsStockクラスが自動生成されていると思います.
namespace CodeFirstMigration.Migrations { using System.Data.Entity.Migrations; public partial class AddProductsStock : DbMigration { public override void Up() { AddColumn("Products", "Stock", c => c.Int(nullable: false)); } public override void Down() { DropColumn("Products", "Stock"); } } }
コードを見ればなんとなくわかるとは思いますが、Up関数内のAddColumn関数の第1引数は追加するテーブル名を、 第2引数には作成したいカラム名を、第3引数には作成したい型の情報を入力します。
では,以下のコマンドを入力してマイグレーションを実行してみましょう.
PM> Update-Database -Verbose
SQL Management Studio等を用いてテーブルの定義、中身を確認してみましょう. 前回挿入したデータが消えていなければ成功です.
モデルの変更とコンフィギュレーション で説明したAnnotation機能を使えば、作成するカラムにさまざまな制約をつけることができます。
例えば、Nameクラスを入力必須にし、MaxLengthを200文字としたい場合は、Productクラスを次のように変更します。
namespace CodeFirstMigration { public class Product { public int ProductId { get; set; } [Required] [MaxLength(200)] public string Name { get; set; } public int Price { get; set; } public int Stock { get; set; } } }
この変更を適用するためのマイグレーションクラスを作成します。
PM> Add-Migration AddRequiredMaxLengthAttributesToName
作成されたマイグレーションクラスは次の通りです。
namespace CodeFirstMigration.Migrations { using System.Data.Entity.Migrations; public partial class AddRequiredMaxLengthAttributesToName : DbMigration { public override void Up() { AlterColumn("Products", "Name", c => c.String(nullable: false, maxLength: 200)); } public override void Down() { AlterColumn("Products", "Name", c => c.String()); } } }
nullable falseで必須項目とし、maxLengthで文字数の指定をしています。 期待したマイグレーションクラスが作成されたら、Update-Database を発行し、DBの状態を確認してみましょう。 次のようなテーブルが作成されていたら成功です。
図: 構成変更後のテーブル定義
Productクラスの構成を変更し、Add-Migrationコマンドを発行することで、マイグレーションのためのクラスが自動生成されることを前節で確認しました。 しかし、一部機能を利用するためには、手動でマイグレーションクラスを作成する必要があります。
インデックスを張りたい場合は、直接マイグレーションクラスを記述する必要があります。 まずは、Productテーブルをいじらず、次のコマンドを発行しましょう。
PM> Add-Migration AddNameIndexToProduct
空のマイグレーションが作成されたと思います。 作成されたクラスのUp関数にはIndexを張るための記述を、DownにはIndexを捨てるための記述を行います。 Indexを張るにはCreateIndex関数を、捨てる場合はDropIndex関数を利用します。
namespace CodeFirstMigration.Migrations { using System.Data.Entity.Migrations; public partial class AddNameIndexToProduct : DbMigration { public override void Up() { CreateIndex("Products", "Name"); } public override void Down() { DropIndex("Products", "Name"); } } }
作成したら、Update-Databaseを発行し、NameカラムにIndexが張られていることを確認しましょう。
図: NameカラムへのIndexの追加
複合Indexを張りたい場合は、次のように指定します。
CreateIndex("Products", new[] { "Name", "ProductId" })
また、UNIQUE指定をつけたい場合は、次のような記述を行います。
CreateIndex("Products", "Name", unique: true)
Entity FrameworkというORマッパーを利用していても、時には直接Sql文を発行したい場合もあるかとおもいます。 例えば、あらかじめ指定したデータをDBに投入しておきたい場合などは、マイグレーションクラス内に直接データInsert文を書いてしまう方法もあります。 直接Sqlを発行するには、Sql関数をUp,Down内に記述します。
namespace CodeFirstMigration.Migrations { using System.Data.Entity.Migrations; public partial class InsertRealProducts : DbMigration { public override void Up() { Sql("INSERT INTO Products(Name, Price, Stock) VALUES(N'テスト商品', 100, 100)"); } public override void Down() { Sql("DELETE FROM Products WHERE Name = N'テスト商品'"); } } }