Wednesday, January 12, 2011

FluentMigrator: Understanding the Framework Code

On the last post of this session: FluentMigrator: Introduction we saw how to use the FluentMigrator framework. Today I will dip into the code so you might understand how it was created.

Part 1: Expression Builders

Well remember this:

FluentMigrator-framework-create-tableFluentMigrator-framework-create-table-in-schema

Will you believe that we are looking at the IntelliScense of the same class?

The only difference is in the interface being returned by the methods, the class that implements them is the same class.

When you call InSchema(“GIS”) you set a field inside the class with the schema name and return the current class back. And that is the whole Fluent way of writing the migration in a nutshell.

You create expressions of the work you want the FluentMigrator to do and then the Runner go over those expressions and runs them.

Part 2: Expression Data (implements IMigrationExpression)

Expression Data is the data being set by the user it also contains the code to execute the changes in the DB. For example when creating a table and the table name and the table schema are stored inside an Expression Data of type CreateTableExpression:

  1. public ICreateTableWithColumnSyntax InSchema(string schemaName)
  2. {
  3.     Expression.SchemaName = schemaName;
  4.     return this;
  5. }

Part of the Expression Data definition:

  1. public class CreateTableExpression : MigrationExpressionBase
  2. {
  3.     public virtual string SchemaName { get; set; }
  4.     public virtual string TableName { get; set; }
  5.     public virtual IList<ColumnDefinition> Columns { get; set; }

Executing the data:

  1. public override void ExecuteWith(IMigrationProcessor processor)
  2. {
  3.     processor.Process(this);
  4. }

The processor has a generator that generates SQL according to the used DB and then it executes the SQL.

Part 3: IMigrationContext

Every expression builder uses IMigrationContext to store the expression data. The IMigrationContext has a collection of IMigrationExpression:

  1. public interface IMigrationContext
  2. {
  3.     IMigrationConventions Conventions { get; }
  4.     ICollection<IMigrationExpression> Expressions { get; set; }
  5.     IQuerySchema QuerySchema { get; }
  6. }

The MigrationRunner will execute that collection using a Processor.

Part 4: Migration

The classes that creates the Expression Data using Expression Builders. The examples in the previous post was of implementing this class. Using the properties of Create/Delete/Rename/… actually creates a factory class that will build a new Expression builder class using the IMigrationContext this class has as a private field:

  1. public abstract class Migration : IMigration
  2. {
  3.     private IMigrationContext _context;

  1. public ICreateExpressionRoot Create
  2. {
  3.     get { return new CreateExpressionRoot(_context); }
  4. }

CreateExpressionRoot factory class for the Expression builders:

  1. public class CreateExpressionRoot : ICreateExpressionRoot
  2. {
  3.     private readonly IMigrationContext _context;
  4.  
  5.     public CreateExpressionRoot(IMigrationContext context)
  6.     {
  7.         _context = context;
  8.     }
  9.  
  10.     public void Schema(string schemaName)
  11.     {
  12.         var expression = new CreateSchemaExpression { SchemaName = schemaName };
  13.         _context.Expressions.Add(expression);
  14.     }
  15.  
  16.     public ICreateTableWithColumnOrSchemaSyntax Table(string tableName)
  17.     {
  18.         var expression = new CreateTableExpression { TableName = tableName };
  19.         _context.Expressions.Add(expression);
  20.         return new CreateTableExpressionBuilder(expression, _context);
  21.     }

Schema is a unique case where there is only one variable. Table is a more usual case, CreateTableExpression is the Expression Data class (it is inserted into the IMigrationContext field) and an Expression Builder is returned.

Part 5: MigrationRunner

This is the class that actually runs the migration. The Up/Down methods create the MigrationContext and pass it to the Migration class which builds the Expression data using the Expression Builders. The MigrationRunner apply the IMigrationConventions on the Expression data and then uses a Processor class to execute the DB changes written in the IMigrationContext object

Part 6: IMigrationConventions

This class is used to create missing fields not set in the Expression data. For example you can choose to create a primary key without specifying the name of the key:

  1. public ICreateColumnOptionSyntax PrimaryKey()
  2. {
  3.     Expression.Column.IsPrimaryKey = true;
  4.     return this;
  5. }
  6.  
  7. public ICreateColumnOptionSyntax PrimaryKey(string primaryKeyName)
  8. {
  9.     Expression.Column.IsPrimaryKey = true;
  10.     Expression.Column.PrimaryKeyName = primaryKeyName;
  11.     return this;
  12. }

One of the conventions is:

  1. public static string GetPrimaryKeyName(string tableName)
  2. {
  3.     return "PK_" + tableName;
  4. }

Meaning the primary key will be created with the name PK_[TableName]

Part 7: IMigrationProcessor

This interface has many implementations – one for each DB. For example I use SQL Server 2008 so I use SqlServerProcessor. The most important funtion in it is:

  1. protected override void Process(string sql)

Which opens a connection to the DB (if it’s not opened), creates a command and executes it.

The Processor uses a Generator class which generates the SQL used in the various basic create/rename/delete/… functionalities.

Part 8: IMigrationGenerator

Again there are many implementations of this, this time one for each implementation of a DB. For example there are three implementations for SQL Server – 2000,2005,2008 and they are not duplicates (the rename stored procedure in 2008 was different from previous versions.

//TODO: change the link to the real DllShepherd.net blog site

//TODO: add a basic drawing of all the interfaces involved – this is a bit too much mess!

Resources:

Github: FluentMigrator Project (the source code)

Keywords: FluentMigrator , framework, expression, nutshell