前回の Entity Framework 4.3 Beta 1 の自動マイグレーションを試してみた - しばやん雑記 では自動でマイグレーションを行いましたが、今回はコードベースでマイグレーションを試してみようと思います。
基本的な流れとしては自動マイグレーションと同じで、プロジェクトを作成し、モデルクラスを追加してテーブルを作ります。
public class Product { public int ProductId { get; set; } public string Name { get; set; } }
モデルは前回と同じです。そして Enable-Migrations コマンドを実行してマイグレーションの設定クラスを生成します。
internal sealed class Configuration : DbMigrationsConfiguration<MvcApplication4.Models.ProductContext> { public Configuration() { AutomaticMigrationsEnabled = false; } protected override void Seed(MvcApplication4.Models.ProductContext context) { // This method will be called after migrating to the latest version. // You can use the DbSet<T>.AddOrUpdate() helper extension method // to avoid creating duplicate seed data. E.g. // // context.People.AddOrUpdate( // p => p.FullName, // new Person { FullName = "Andrew Peters" }, // new Person { FullName = "Brice Lambson" }, // new Person { FullName = "Rowan Miller" } // ); // } }
今回は自動マイグレーションは使いませんので AutomaticMigrationsEnabled は false のままにしておきます。
ここからが前回と違います。自動でマイグレーションを行わない代わりに、どのテーブルを追加・削除したか、どのカラムを追加・削除したかを自分で実装する必要があります。
簡単な例として、これも前回と同じく Summary プロパティを Product クラスに追加してマイグレーションを行ってみます。
public class Product { public int ProductId { get; set; } public string Name { get; set; } // これを追加する! public string Summary { get; set; } }
Summary プロパティを追加した状態で実行すると、前回と同様にエラーが表示されます。DB とモデルの構造が異なってしまっているので当然ですね。
そこで、不足している Summary カラムを追加するために Add-Migration コマンドを実行して、マイグレーション用のクラスを作成します。
Add-Migration AddSummaryMigration
すると Migrations ディレクトリの中に入力した名前のクラスが追加されます。ファイル名の頭にタイムスタンプが付いていますが、クラス名はコマンド実行時に指定した名前になっています。
中身はスキャフォールディングである程度までは自動生成してくれるので便利ですね。実際に生成されたコードは以下の通りです。
namespace MvcApplication4.Migrations { using System.Data.Entity.Migrations; public partial class AddSummaryMigration : DbMigration { public override void Up() { AddColumn("Products", "Summary", c => c.String()); } public override void Down() { DropColumn("Products", "Summary"); } } }
特徴としては Up と Down のメソッドが用意されているので、正しく実装されている場合にはマイグレーション後に元に戻すことが可能になっています。
とりあえずこれで準備は出来ましたので実際にマイグレーションを行ってみます。マイグレーションは Update-Database コマンドを使って行います。
Update-Database -TargetMigration:AddSummaryMigration
TargetMigration の後には追加したクラス名を指定してください。これでマイグレーションが実行されますが -Verbose スイッチを付けることでどのような SQL が実行されたか確認できます。
ALTER TABLE ADD COLUMN が実行されていることがわかりますね。
そしてこの変更を元に戻したい場合には -TargetMigration に "0" を指定して実行します。
Update-Database -TargetMigration:"0"
これも -Verbose スイッチを付けて実行すると SQL が出力されます。
今度は ALTER TABLE DROP COLUMN が実行されていることが確認できました。当然ながらカラムに格納されていたデータは失われるので注意してください。
折角なのでもうちょっと試してみます。外部キーも試してみたいので、Tag テーブルを追加して Product クラスに ICollection
public class Product { public int ProductId { get; set; } public string Name { get; set; } public string Summary { get; set; } // これを追加! public virtual ICollection<Tag> Tags { get; set; } } // クラスを追加 public class Tag { public int TagId { get; set; } public string Name { get; set; } }
そして忘れずに DbContext にも DbSet
public DbSet<Tag> Tags { get; set; }
この状態で Add-Migration コマンドを実行してみました。実行したコマンドと出力されたコードは以下の通りです。
Add-Migration AddTagMigration
public partial class AddTagMigration : DbMigration { public override void Up() { CreateTable( "Tags", c => new { TagId = c.Int(nullable: false, identity: true), Name = c.String(), Product_ProductId = c.Int(), }) .PrimaryKey(t => t.TagId) .ForeignKey("Products", t => t.Product_ProductId) .Index(t => t.Product_ProductId); } public override void Down() { DropIndex("Tags", new[] { "Product_ProductId" }); DropForeignKey("Tags", "Product_ProductId", "Products"); DropTable("Tags"); } }
先程の例と比較すると格段にコードが複雑になっていますが、単純に Tags テーブルを作成しているだけにすぎません。よく見ると、元々存在しなかった外部キーも自動で追加されていますね。
そして今回は一度データベースを最初の状態に戻してから、マイグレーションを行ってみます。定義されているすべてのマイグレーションを実行する場合はパラメータなしで Update-Database コマンドを実行するだけです。
実行した結果は以下の通りです。整合性を保持するためにタイムスタンプごとにマイグレーションが実行されていることが分かります。
PM> Update-Database -Verbose Using NuGet project 'MvcApplication4'. Using StartUp project 'MvcApplication4'. Target database connection string: 'Data Source=.\SQLEXPRESS;Initial Catalog=MvcApplication4.Models.ProductContext;Integrated Security=True;MultipleActiveResultSets=True;Application Name=EntityFrameworkMUE'. Applying explicit migrations: [201201171524584_AddSummaryMigration, 201201171545151_AddTagMigration]. Applying explicit migration: 201201171524584_AddSummaryMigration. ALTER TABLE [Products] ADD [Summary] [nvarchar](max) [Inserting migration history record] Applying explicit migration: 201201171545151_AddTagMigration. CREATE TABLE [Tags] ( [TagId] [int] NOT NULL IDENTITY, [Name] [nvarchar](max), [Product_ProductId] [int], CONSTRAINT [PK_Tags] PRIMARY KEY ([TagId]) ) CREATE INDEX [IX_Product_ProductId] ON [Tags]([Product_ProductId]) ALTER TABLE [Tags] ADD CONSTRAINT [FK_Tags_Products_Product_ProductId] FOREIGN KEY ([Product_ProductId]) REFERENCES [Products] ([ProductId]) [Inserting migration history record]
実際は自動マイグレーションだけで事足りるとは思いますが、コードベースでのマイグレーションではインデックスの指定など、これまでのコードファースト開発に比べて DB に近い部分で操作が行えるので、自動マイグレーションとの組み合わせがベストだと思います。
DbMigration 内での定義については時間があるときにまとめたいと思います。