FluentMigration の基本機能メモ
前回簡単に使ってみた FluentMigration の機能を整理できたのでメモがてら。ほとんど公式ドキュメントの翻訳みたいになっちゃってますが。
そもそも FluentMigrator って何って話は前回のエントリをどぞ。
なお、ほとんどの例は公式から持ってきています。
目次
- Package Install
- Runner
- Migration
- Profile
- Multiple Database
- DB Function
- Fluent Interface
- Raw Sql Helper for inserting data
- Auto Reversing Migration
- Two Transaction Mode
- Application Context
- Tag
- Original Format Migration Version
- Custom VersionInfo Table
- Contrib
Package Install
Install-Package FluentMigrator
もしくは
Install-Package FluentMigrator.Tools
違いは Runner の有無。Tools の方は Runner が付いてきます。
Tools は NuGet パッケージではあるんですが、プロジェクトの参照設定に入ったりするわけではなく、自分でパッケージを展開して中の Runner をコマンドプロンプトなどから利用します。
ツール群を zip で落としたと思えばわかりやすいですかね。
Runner
以上四つが公式ランナーです。Rake ランナーは Albacore が必須です。
Migration
一回のマイグレーションのために、Migration
抽象クラスを実装したクラスを一つ作ります。
再掲になりますが、基本的には以下のような実装を自分で行えばいいです。
[Migration(1)] public class SampleMigration : Migration { public override void Up() { // Forward Migration の実装 } public override void Down() { // Backward Migration の実装 } }
MigrationAttribute
の付与と、Migration
抽象クラスの継承ですね。
MigrationAttribute
の引数には long
型のバージョン番号を渡します。(後述しますが、カスタマイズ可能)
また、初回のマイグレーション時には自動で管理用テーブルを作成します。あまり気にする必要はないと思いますが、テーブル定義は以下の通りです。PostgreSQL 版なので、他の DBMS を使っている場合は適当に読み替えてください。
CREATE TABLE "VersionInfo" ( "Version" bigint NOT NULL, "AppliedOn" timestamp without time zone, "Description" character varying(1024) ) WITH ( OIDS=FALSE ); ALTER TABLE "VersionInfo" OWNER TO postgres;
このテーブルの管理を FluentMigrator に任せておけば同じマイグレーションは重複して実行されません。逆に、なんでもいいからまっさらに戻したいときは drop table
すれば OK です。
Profile
マイグレーションクラスにプロファイルを設定できます。
[Profile("Dev")] public class SampleMigration : Migration { public override void Up() { // Forward Migration の実装 } public override void Down() { // Backward Migration の実装 } }
Runner のオプションでプロファイルを指定可能なのですが、それが一致したときのみ実行されるマイグレーションの定義が可能です。開発環境、テスト環境、本番環境それぞれで処理を分けたいときなどに使えますね。注意点として、プロファイルが設定されたマイグレーションは常に実行されるようです。
開発環境やテスト環境でテストデータを整理するのに使って欲しいらしいです。
Multiple Database
IfDatabase("oracle").Execute.Script("CreateViewsOracleMigrationUp.sql"); IfDatabase("sqlserver").Execute.Script("CreateViewsSqlServerMigrationUp.sql");
複数の DBMS に対応するマイグレーションクラスを作成できます。
DB Function
Create.Table("TestTable").WithColumn("Created").AsDateTime().Nullable().WithDefault(SystemMethods.CurrentDateTime);
「最低限これは使うだろ」というような DB 関数は直接 SQL を書かなくても実行可能です。
列のデフォルト値などに使う用途を想定しているみたいですね。
サポートしている関数は各 DBMS の実装に依存しますが、インタフェースとして用意されているのは
- NewGuid
- NewSequentialId
- CurrentDateTime
- CurrentUTCDateTime
- CurrentUser
の五つです。
例えばポスグレだと NewSequentialId
以外はサポートされています。
ただ、NewGuid
を使うためには contrib の uuid-ossp 拡張のインストールと有効化が必要です。
Fluent Interface
名前に Fluent と付いているのはこいつがあるからでしょう。
Fluent Interface とは、マイグレーションクラスの実装時に使える SQLHelper
とか QueryBuilder
みたいな Utility クラス群を指します。
これらのクラスはその名の通り Fluent な API を持つので、わかりやすくかつ気持ちよくマイグレーションクラスを実装できます。
// 簡単な例 Create.Table("users") .WithColumn("user_id") .AsGuid() .PrimaryKey("pk_user") .WithDefault(SystemMethods.NewGuid) .WithColumn("user_name") .AsAnsiString(10) .NotNullable() .WithDefaultValue("名無しさん") .WithColumnDescription("コメントも書ける");
Fluent Interface の起点クラスはいくつかあるので順に紹介します。
Create Expression
上記例でも使っているクラスです。
テーブル、列、インデックス、外部キー、シーケンス、スキーマが作成可能です。
データベースは作れない ので注意が必要です。
Alter Expression
Alter.Table("Bar") .AddColumn("SomeDate") .AsDateTime() .Nullable(); Alter.Table("Bar") .AddColumn("SomeDate") .AsDateTime() .SetExistingRowsTo(DateTime.Today) .NotNullable();
テーブル、列に対して定義の変更が可能です。
Delete Expression
Delete.Table("Users"); Delete.FromTable("Users").AllRows(); // delete all rows Delete.FromTable("Users").Row(new { FirstName = "John" }); // delete all rows with FirstName==John Delete.FromTable("Users").IsNull("Username"); //Delete all rows where Username is null
テーブル、列、インデックス、外部キー、制約とスキーマの削除が可能です。
また、テーブルを指定したあとにメソッドチェーンを続けることで行の削除も行えます。対象の抽出条件 (where 句)も指定可能です。
ただ、抽出条件には制限があって、基本的に「equal」での比較しか行えません(is null
含む)。つまり、「not equal」や「lower than equal」などは使えません。
Execute Expression
Execute.Script("myscript.sql"); Execute.EmbeddedScript("UpdateLegacySP.sql"); Execute.Sql("DELETE TABLE Users");
SQL スクリプトか、SQL スクリプトを書いた外部ファイルを実行できます。
外部ファイルの実行は、そのファイルがマイグレーションクラスが属するプロジェクトに追加されていて、かつそのファイルの [プロパティ] > [ビルドアクション] が「埋め込まれたリソース」になっている必要があります。
Fluent Interface を QueryBuilder
として見てしまうとどうしても弱く (特に検索)、時にはやりたいことができないことも出てくると思うのでそういう状況下で利用します。SQL Server だったら T-SQL、ポスグレだったら無名ブロックを使えばだいたい何でもできるので、利用して困っても大抵何とかなるはずです。
Rename Expression
Rename.Table("Users").To("UsersNew");
テーブルか列のリネームが行えます。
Data Expression
Insert.IntoTable("Users").Row(new { FirstName = "John", LastName = "Smith" }); // delete は前述のため省略 Update.Table("Users").Set(new { Name = "John" }).Where(new { Name = "Johnanna" });
行の挿入、更新、削除が行えます。
匿名型を列の値として渡すことが可能です。Dapper っぽいですね。
IfDatabase Expression
IfDatabase("SqlServer", "Postgres") .Create.Table("Users") .WithIdColumn() .WithColumn("Name").AsString().NotNullable(); IfDatabase("Sqlite") .Create.Table("Users") .WithColumn("Id").AsInt16().PrimaryKey() .WithColumn("Name").AsString().NotNullable();
複数 DBMS を使う際、任意の DBMS を指定した上で他の Expression を実行できます。
Schema.Exists Expression
if (!Schema.Table("Users").Column("FirstName").Exists()) { this.Create.Column("FirstName").OnTable("Users").AsAnsiString(128).Nullable(); }
テーブルや列の存在確認が行えます。
SQL Server only Expression
SQL Server 専用の API です。
Raw Sql Helper for inserting data
Insert.IntoTable("User").Row(new { Username = RawSql.Insert("CURRENT_USER") })
名前からちょっと誤解しそうですが、生で SQL がなんでも書ける機能ではないです。
Fluent Interface での行挿入時の値は string
型だと強制的に文字列リテラルとして扱われてしまうため、例えば CURRENT_USER 関数を使おうと
Insert.IntoTable("Users").Row(new { Username = "CURRENT_USER" });
こう書いてしまうと、"CURRENT_USER" というリテラルがセットされてしまいます。
そこで、冒頭の例のように RawSql
クラスを使ってやると、うまいこと式に変換してくれます。
Auto Reversing Migration
マイグレーションクラスは通常 Migration
抽象クラスを継承し、Up() メソッドと Down() メソッドの両方の実装が必要です。
ですが、AutoReversingMigration
抽象クラスを継承すれば Down() メソッドを、Up() メソッドの実装から推測して自動で作成してくれます。
ただまぁ制限があって、サポートしている Fluent Interface は
- Create.Table
- Create.Column
- Create.Index
- Create.ForeignKey
- Create.Schema
- Delete.ForeignKey
- Rename.Table
- Rename.Column
だけです。
まぁ単調な Backward 処理を毎回書くよりはマシですかね。。。
Two Transaction Mode
マイグレーションを実行する際のトランザクションは FluentMigrator が自動で行うのですが、トランザクションのスコープを変更することができます。
- None
- Transaction-Per-Migration (既定値)
- Transaction-Per-Session
上二つは MigrationAttribute
属性への引数で指定可能なのですが、Transaction-Per-Session を有効にするには Runner にオプション指定が必要です。
Application Context
Runner に値を渡すか、マイグレーションクラス中で Context を設定できます。
値の設定
migrate.exe ... --context MyArgument
var migrationContext = new FluentMigrator.Runner.Initialization.RunnerContext(announcer)
{
ApplicationContext = clientName
};
値の取得
if ((string)this.ApplicationContext == "MyArgument") this.Delete.Column("BadColumn").FromTable("MyTable");
ApplicationContext
は object
型なので、マイグレーションクラス中での設定であれば任意の型を渡せます。
Runner からの場合は型はどうしようもないですが、文字列として受けてスイッチに使って欲しいみたいですね。JSON とか突っ込んでコード中でデシリアライズすれば何とかなるかもしれませんw
Tag
[Tags("DK", "NL", "UK")] [Tags("Staging", "Production")] [Migration(1)] public class DoSomeStuffToEuropeanStagingAndProdDbs() { }
マイグレーションクラスにタグを設定できます。
タグは Runner にオプションとして指定することで、任意のマイグレーションのみを実行できます。
Original Format Migration Version
機能というか拡張ポイントなのですが、、、MigrationAttribute
に渡すバージョン番号に独自ルールを設けることができます。
public class MyCustomMigrationAttribute : FluentMigrator.MigrationAttribute { public MyCustomMigrationAttribute(int branchNumber, int year, int month, int day, int hour, int minute, string author) : base(CalculateValue(branchNumber, year, month, day, hour, minute)) { this.Author = author; } public string Author { get; private set; } private static long CalculateValue(int branchNumber, int year, int month, int day, int hour, int minute) { return branchNumber * 1000000000000L + year * 100000000L + month * 1000000L + day * 10000L + hour * 100L + minute; } } [MyCustomMigration(author: "Scott Stafford", branchNumber: 12, year: 2012, month: 8, day: 7, hour: 14, minute: 01)] public class TestLcmpMigration : Migration { public override void Down() { /* ... */ } public override void Up() { /* ... */ } }
標準設定だと数値しか使えませんが、拡張することで実行者や実行日時も指定できるのはいいですね。
Custom VersionInfo Table
これも拡張ポイントですが、自動で作成される VersionInfo
テーブルはカスタマイズ可能です。
IVersionTableMetaData
インタフェースを実装するクラスを定義し、VersionTableMetaDataAttribute
属性を付与することで VersionInfo
テーブル定義を自由にできます。
また、一部だけ拡張できればいいなら DefaultVersionTableMetaData
クラスを継承します。
Contrib
現存する DB からリバースエンジニアリングというか、スキーマダンプできます。
「SchemaDump」と「FluentMigrator.T4」という二つのライブラリがあります。
ドキュメントがなくて詳細はわからないのですが、後者は T4 とかついているもののどちらも初期化マイグレーションクラスを作るための Helper に見えます。。。
使い方は、どちらも既存 DB のスキーマ定義を持ったオブジェクトを自動で作ってくれるので、それをもとにして動的に .cs ファイルを頑張って作成するようです。
GitHub 上でも突っ込み入ってますが、FluentMigrator アセンブリ中に存在しないので利用するにはソースコード持ってこないといけません。
色々イマイチだしドキュメントもないから、もしかして作りかけなのかな。。。