A question at stack overflow whether NHibernate and migratordotnet play nicely together caught my interest. So I started a little experiment to find out myself. Instead of migratordotnet, however, I wanted to use Fluent Migrator because one of the teams I’ve been working with recently used it as well.
The first challenge I ran into was figuring out how to invoke and use Fluent Migrator from within an assembly. When you look at the sources of Fluent Migrator you will notice that it has several runners that can be used from the command line and from NAnt and msbuild. None of these was what I was looking for. Instead I wanted an API. I couldn’t find one so decided to implement my own API.
About one interface, two classes and about 150 lines of code later I had it running. During startup, e.g. a static initializer in the assembly the migrations are executed using the following statement:
new Migrator(new MigratorContext(Console.Out) { Database = "postgres", Connection = ConnectionString, MigrationsAssembly = typeof(Global).Assembly }).MigrateUp();
Migrator is one of the classes I implemented. It serves as the façade to Fluent Migrator. Within my assembly I now can write and implement regular migrations, e.g. like the following:
[Migration(201110181840)] public class CreateUserTable : Migration { public override void Up() { Create.Table("Users"); } public override void Down() { Delete.Table("Users"); } }
As a result I can now write additional migrations as needed. This approach helps with development as we no longer need to run a separate tool. Each time the startup sequence was executed, e.g. running developer tests which loads the assembly, the database schema was brought up to date. And since I used the automapping feature of Fluent NHibernate I didn’t have to write mappings either.
What I noticed, though, is that although both Fluent NHibernate and Fluent Migrator now play together nicely, there appears to be code that looks similar. Take the following example of a domain class.
public class InputFile { public virtual Guid Id { get; private set; } public virtual Job Job { get; set; } public virtual string FileName { get; set; } }
To have a table for this I also have the following Migration in the code:
[Migration(201111040531)] public class CreateInputFilesTable : Migration { public override void Up() { Create.Table(TableName) .WithColumn("Id").AsGuid().PrimaryKey() .WithColumn("JobId").AsGuid().Indexed() .WithColumn("FileName").AsString(); } public override void Down() { Delete.Table(TableName); } private const string TableName = "InputFile"; }
As you can see there are things that should not be required. For example the domain class already specifies that the property Id is of type Guid. This is equivalent to saying that the table should have a column named “Id” of type Guid.
An additional issue can arise when you add a column/property in one place but forget to add it to a different place. Of course this will be reported next time via an exception. However, it would be nice if I had to change it in a single place only.
So there appears to be an opportunity to simplify the code. Maybe I could rewrite the migration using reflection? And maybe I could rewrite that migration in a kind of generic way so I could reuse at least parts of it for other migrations.
Next I’ll be looking into how an improved solution might look like ideally generic or at the very least with much less duplication. Being able to avoid having to change two files when modifying one domain class would be a nice first step.
Update 13 Nov 2011: I have created a fork of Fluent Migrator on Github. The address for the fork is https://github.com/ManfredLange/fluentmigrator. In that fork I have added a new project FluentMigrator.InProc that contains the sources mentioned in this post.
Hello,
ReplyDeleteGreat post, Manfred !!
I have question, Could you send me this change?
Do you have GitHub?
@Fabio: The sources are now available from a new fork of FluentMigrator at Github. After you have loaded the VS2010 version of the solution file, the sources are included in the project FluentMigrator.InProc.
ReplyDeleteThanks for this post! Very relevant to me right now. Have you done anything towards the ends of the improved solution you mentioned? I have been thinking along the same lines about some way to remove the duplication of domain objects in migrations.
ReplyDelete