Friday, February 18, 2011

Per DB Conventions With FluentNhibernate

I need to support 2 db types on my app, so I thought about a nice pattern that will help me achieve this.

// I'm working with latest Fluent Nhibernate version.

Define a folder for each db type you want to support like this:

contain conventions for MSSQLCE and the second will contain conventions for OracleClient.

For example: Oracle support schemas and sequences, SQLCE does not. lets define some simple conventions

For Oracle:

public class SchemaConvention : IClassConvention
{
  public void Apply(IClassInstance instance)
  {
    instance.Schema("MY_ORACLE_SPECIFIC_SCHEMA");
  }
}

public class SequenceConvention : IIdConvention
{
  public void Apply(IIdentityInstance instance)
  {
    instance.GeneratedBy.Sequence(instance.Columns.First().Name + "_SEQ");
  }
}

And for SqlCe:

class IdentityConvention : IIdConvention
{
  public void Apply(IIdentityInstance instance)
  {
     instance.GeneratedBy.Identity();
  }
}
So the folders will look like this:

Next thing that we will need is some kind of type retriever that knows what conventions to load according to the db we are currently configuring his nhibernate stuff.

Lets call it PerDBConventionSource and this is how its implemented:

internal class PerDBConventionSource : ITypeSource
{
  private readonly string _name;
  private IEnumerable<Type> _types;

  public PerDBConventionSource(string name)
  {
    _name = name;
  }

  public IEnumerable<Type> GetTypes()
  {
    _types = typeof (PerDBConventionSource).Assembly.GetTypes().Where(t =>        t.Namespace.EndsWith(_name));
    return _types;
  }

  /// <summary>
  /// Logs the activity of this type source.
  /// </summary>
  /// <param name="logger">The logger.</param>
  public void LogSource(IDiagnosticLogger logger)
  {
    // TODO : Log if you want
  }

  public string GetIdentifier()
  {
    return _name;
  }
}

Notice that i'm implementing ITypeSource, an interface belong to FluentNhibernate, this is how i tell FluentNhibernate to use this as a convention source: (I used only fluentMappings so I add the conventions only on fluentMappings)


private static IPersistenceConfigurer _persistenceConfigurer;

private static void ConfigureNH(IPersistenceConfigurer persistenceConfigurer)
{
  _persistenceConfigurer = persistenceConfigurer;
  Fluently.
    Configure().
    Database(persistenceConfigurer).
    Diagnostics(x => x.Enable(true).OutputToConsole()).
    Mappings(SomeMappings).
    BuildConfiguration();
}

private static void SomeMappings(MappingConfiguration mappingConfiguration)
{
  string name = _persistenceConfigurer.GetType().Name.Replace("Configuration", string.Empty);

  mappingConfiguration.FluentMappings.Conventions.AddSource(new PerDBConventionSource(name));
}


So when I'll run the following code:

Console.BackgroundColor = ConsoleColor.DarkGreen;
Console.ForegroundColor= ConsoleColor.Green;

ConfigureNH(OracleClientConfiguration.Oracle10.ConnectionString("some OracleClient connection"));

Console.BackgroundColor = ConsoleColor.DarkBlue;
Console.ForegroundColor = ConsoleColor.Blue;

ConfigureNH(MsSqlCeConfiguration.Standard.ConnectionString("some MsSqlCe connection"));

The result will be



Why you should use his pattern anyway(even if you work with 1 db):
1. unit testing with sqlite in memory will not work with sequence conventions, so you shouldn't load them when you are unit testing.
2. If you'll some day change the db type, you'll know where to add its own conventions, just create a folder with db name and add it's conventions inside no other change is necessary.

You can find the source here: