2012/07/24

コードファーストのMigration機能に感動した話

話の発端はコードファーストでは無く、複合キーの善し悪しについていろいろ調べてたことでした。自社システム更新にあたりデータベースまわりを見直してたんですが、複合キーが結構あったりして今考えてみるとこれって合理的なのか分かんないよなぁって思ったりなんかしたわけです。それで調べてみると複合キーは使わずにサロゲートキー+複合ユニーク制約(この辺の用語解説は長くなるので省略。)を使ってみなさいというお言葉があったので早速影響されてみました。

 …コードファーストで複合ユニーク制約ってどうすんのよ。この複合ユニーク制約、SQLServerで言うとこの「インデックスで一意キーの作成」になるわけですが、どうモデルに書けばいいんだ?そんな属性見当たらないんだけど?てなことで調べてみたわけですが、どうやらモデル作成時には無理っぽいです。

いきなり挫折しそうになりましたが、どうにかこうにか探ってみたところこの辺を参考にMigrationをいじれば出来そうな気がする。早速実験してみます。

 以下のようなEmployeeテーブルがあったとしてこれの「EmployeeCd」と「Name」に複合ユニーク制約を設けたいと考えます。

public class Employee
{
public int EmployeeId { get; set; }
public string EmployeeCd { get; set; }
public string Name { get; set; }
}



public class MyContext: DbContext
{
public DbSet Employees { get; set; }
}

当然このまま作成しても何もなりませんが、Migrationはテーブルが作成されてない状態では実行できないので一端これでテーブルを作成します。

 テーブルが作成できたら「ツール」から「パッケージマネージャーコンソール」を開き、「Enable-Migrations」を実行します(Enable-Migrationと打つだけです。)。すると以下のようなクラスが自動で作成されます。これがMigration時のコンフィグにたぶん使われるわけです。これの「AutomaticMigrationsEnabled = false;」を「AutomaticMigrationsEnabled = true;」に変えると自動Migrationを利用できたりしますが、今回はこのまま使います。

internal sealed class Configuration : DbMigrationsConfiguration
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}

protected override void Seed(PmsDataBase.PmsContext context)
{
//  This method will be called after migrating to the latest version.

//  You can use the DbSet.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" }
//    );
//
}
}

 続けてコンソールで、「Add-Migration CreateIndexEmployeeMigration」を実行します。CreateIndexEmployeeMigrationの部分は任意です。まぁ頻繁にMigrationしなければ主要な変更点を名前にした方が無難かなと思います。実行すると以下のようなクラスが作成されます。

public partial class CreateIndexEmployeeMigration: DbMigration
{
public override void Up()
{
}

public override void Down()
{
}
}

これのUpメソッドに変更処理を、Downメソッドに取消処理を書けばよいわけです。今回は以下のようになります。

public partial class CreateIndexEmployeeMigration: DbMigration
{
public override void Up()
{
CreateIndex("Employees", new[] { "EmployeeCd", "Name" }, true);
}

public override void Down()
{
DropIndex("Employees", new[] { "EmployeeCd", "Name" });
}
}

「CreateIndex(テーブル名, カラム名, ユニークの場合TRUE)」でユニーク制約付きのインデックスが作成され、「DropIndex(テーブル名, カラム名)」でそのインデックスが削除されます。インデックスに名前を付け、それを指定して削除することも可能です。

これでいよいよ更新です。コンソールで「Update-Database -TargetMigration:CreateIndexEmployeeMigration」を実行すると…エラーが起きます。エラーの内容はSQLServerが「文字数MAXのカラムをインデックス化できるか!」てなことみたいです。全然Migration関係無いエラーでした。なんてこった。

まぁせっかくなのでこれまたMigrationを利用してテーブルを修正しましょう。コンソールで「Add-Migration EmloyeeCdAndNameLengthMigration」(長すぎても気にしない)とし、以下のように編集します。

public partial class EmloyeeCdAndNameLengthMigration: DbMigration
{
public override void Up()
{
AlterColumn("Employees", "EmployeeCd", c => c.String(maxLength: 5));
AlterColumn("Employees", "Name", c => c.String(maxLength: 20));
}

public override void Down()
{
AlterColumn("Employees", "EmployeeCd", c => c.String());
AlterColumn("Employees", "Name", c => c.String());
}
}

「AlterColumn(テーブル名, カラム名, Funcデリゲート)」でカラムの編集が可能です。Funcデリゲート苦手です。これと先ほど作ったMigrationをUpdate-Databaseコマンドで順次実行すると今度こそ思った通りのテーブルになります。

実際のプロジェクト見ると分かると思いますが、Migrationはクラス名の前にタイムスタンプが付けられ世代と整合性保持に使われてるようです。Update-Databaseコマンドにパラメータなしに実行するとこのタイムスタンプ順に全てのMigrationが実行されます。よって今回の作り方だと先にインデックス作成が走るのでエラーになるので注意下さい。

ちなみに「Update-Database –TargetMigration: “0”」とすると全てのMigrationのDownメソッドが実行され、初期化されます。

かなり長くなりましたが、こんな感じでMigrationの便利さを感じた今日この頃でした。


pagetop