Logo

NHibernate

The object-relational mapper for .NET

How to

This page is converted from the old nhforge.org Wiki. First published by: Chris Nicola on 09-03-2009, Last revision by: Steve Bohlen on 11-20-2011

Creating a custom id generator for nHibernate

I originally blogged about this here and here on my blog but Fabio suggested I add these posts to the Forge, so here I am.  I have to say I am a little exited to make my first contribution to the Wiki [H].  I will try to add a bit more to them and show some examples that do not use the TableGenerator class but directly implement IIdentifierGenerator . Please let me know if there are any mistakes or anything else you want to see.

Before beginning I recommend that you download the NH source code and take a quick look at the TableGenerator , IIdentifierGenerator and IPersistentIdentifierGenerator classes as you will be working with these classes and interfaces to implement your custom generator.

Part 1: Inheriting from TableGenerator class

Currently I am doing some work developing a utility that imports securities data into a fairly old legacy database. It is still SQL but the database and software havn't been updated in about 15 years (maybe more) and never will be again.

The original application for this database uses a primitive hilo-style ID generator mechanism. Tt simply queries an integer value from a table in the database and increments it, then it adds a fixed value to it and uses that as the next unique ID. It doesn't seem very optimal, but that's how it works and there's no changing it. 

It isn't complex, but it also isn't one of the identity strategies available in nHibernate and I fully intend to use nHibernate.

So what to do? One option would be to set generator="assigned" and do it all myself, assigning the ID using ADO.Net directly or something similar... but what fun would that be? No, I've decided to take a peak at the nHibernate code and learn to extend it. Read on to find out how to create a custom class that inherits from IIdentifierGenerator to implement your own custom ID generator strategy for nHibernate.

One of the beautiful things about nHibernate the ability to extend it. You can extend its functionality without ever touching their source code.

It was very easy to locate the classes responsible for ID generation in the NHibernate.ID namespace. It was also clear immediately that most classes inherited from IIdentifierGenerator.   I also knew that my strategy was similar to the procedure that hilo implements so I started with TableHiLoGenerator which inherts from TableGenerator.

After playing around with this for a while and trying different things, it was clear that TableGenerator, basically takes a tablename and columnname from the configuration, reads a value, increments it and passes it on as an ID. Voila! That was almost exactly what I needed to do with little else to change. I was quickly able to create my own class inheriting from TableGenerator

public class FDPSequence : TableGenerator

{

    private const Int32 SeedValue = 1048576;

 

    private static readonly ILog Log = LogManager.GetLogger(typeof(FDPSequence));

 

    public override object Generate(ISessionImplementor session, object obj)

    {

        int counter = Convert.ToInt32(base.Generate(session, obj));

        return counter + SeedValue + 1;

    }

}

Not much to that is there? Now that is a fairly simple case, but even getting more complex isn't much harder than that. You can implement IIdentifierGenerator directly and create something far more customized. TableGenerator actually implements IPersistentIdentifierGenerator which is an interface for identifiers which need to write and updated value to the database. However, since nHibernate provides so many versatile and inheritable base class generators that you will likely get by with inheriting one of those and extending it as I did.

In order to use this custom generator class I setup my configuration to use it. Using Fluent I wrote a mapping like this:

public class InvestSpecMap : FDPEntityBaseMap<InvestSpec>

{

    public InvestSpecMap()

    {

        WithTable("InvestSpec");

        Id(x => x.Id).ColumnName(tablename + "ID")

            .SetGeneratorClass(typeof(FDPGenerator.FDPSequence).AssemblyQualifiedName)

            .AddGeneratorParam("table", "zSysCounters") //Name of the ID counter table

            .AddGeneratorParam("column", "InvestSpec"); //ID counter column for this table

        Map(x => x.AnnualDividend).ColumnName("AnnlzdDiv");

        Map(x => x.AssetClass);

        Map(x => x.CurrencyType);

        Map(x => x.CusipNumber).ColumnName("CusipNum");

        Map(x => x.DividendFreq).ColumnName("DivFrequency");

        Map(x => x.FullName);

        Map(x => x.MaturityDate).ColumnName("MaturityDt");

        Map(x => x.PriceAsOf).ColumnName("PriceAsOfDt");

        Map(x => x.ShortName).ColumnName("AbbrName");

        Map(x => x.TaxTreatment);

        Map(x => x.TickerSymbol);

        Map(x => x.Type);

        Map(x => x.UnitPrice);

    }

}

You can do the same with XML mappings just declare the assembly and class as you usually would in XML. This is actually the first time I've used FluentNH.

What is great about this is that even when testing my mappings with SQLLite, SchemaExport is actually able to create the identity table based on the tablename and columnname properties in the nHibernate configuration. The main benefit here however is that identity generation remains transparent to the rest of my application. Also doing something cool with nHibernate, that's priceless of course

Part 2: Stealing from TableGenerator and controlling DDL generation

A while ago I posted about writing a custom id generator using nHibernate and extending one of the existing Generator classes nHibernate provides.  This was done to support working with a legacy database which uses a custom id generation strategy.  One thing I like to do with all my apps is write some basic CRUD tests to make sure my mappings and queries are all working properly.

To do this I use Oren's approach of creating a temporary in-memory database using SQLite to work with.  This involves using the  SchemaExport tool in nHibernate to generate the DDL to create the database.  If you are using a table generator strategy it also needs to create the table and columns for the custom id generator.  In some cases you may need to customize the DDL for your Id table.

As I discovered the DDL to generate the table to store the next id value for my custom id is only created once and does not check the column name value for each class.  For my id generator there is a separate column used for each table in the database, so this was a problem.  For example, the Price table has a column in the Counter table called Price to store it's counter and the Holding table has a column called Holding, but I was only getting one column (this appears to be a problem with hilo and SchemaExport, you can currently only have one column per database)

The TableGenerator class supports two parameters which are set in the HBM files (or using fluent) tablename and columnname which set the table and column names used for the id counter.  If you have a copy of the nHibernate source code you will see that the TableGenerator class calls SqlCreateStrings()and SqlDropString() to create and drop the table for the id generator.  Unfortunately, as I mentioned above this is only called once, and so it only creates one table with one column for whichever mapping is handled first.

In order to fix this I had to copy the TableGenerator class (yes, yes, I know copy-paste is bad form) and modify those functions.  I also added a third parameter allcolumnnames in which I can provide a comma separated list of all the columns the table needs to have (one per table in my database).  The SqlCreateStrings()is simple changed to:

public string[] SqlCreateStrings(Dialect dialect) {

  string create = "create table " + tableName + " (";

  string insert = "insert into " + tableName + " values (";

  foreach (string s in allcolumns.Split(',')) {

    create += " " + s + " " + dialect.GetTypeName(columnSqlType) + ",";

    insert += " 1,";

  }

  create = create.Trim(',') + " )";

  insert = insert.Trim(',') + " )";

  return new[] {create, insert};

}

(Yes, yes, I know I should have learned from my last post and used a StringBuilder here as I pointed out in my last post, but seriously, this method will only ever be called once)

Now when it is called it will create the correct table with all the columns I need. A couple of example mappings for the database in FluentNH are shown below:

public InvestSpecMap() {

  WithTable("InvestSpec");

  Id(x => x.Id).ColumnName("InvestSpecID")

    .SetGeneratorClass(typeof (FdpSequenceGen).AssemblyQualifiedName)

    .AddGeneratorParam("allcolumns", "InvestSpec, InvestPrice")

    .AddGeneratorParam("table", "zSysCounters") //ID counter table name

    .AddGeneratorParam("column", "InvestSpec"); //ID counter column name

public PriceMap() {

  WithTable("InvestPrice");

  Id(x => x.Id).ColumnName("InvestPriceID")

    .SetGeneratorClass(typeof (FdpSequenceGen).AssemblyQualifiedName)

    .AddGeneratorParam("allcolumns", "InvestSpec, InvestPrice")

    .AddGeneratorParam("table", "zSysCounters") //ID counter table name

    .AddGeneratorParam("column", "InvestPrice"); //ID counter column name

To be completely "clean code" and avoid repeating myself I should probably factor out the Id() code into a method in an inhereted base class so I can call it from each mapping class like this:

protected void SetIdGenerator(string tablename) {

  Id(x => x.Id).ColumnName(TableName + "Id")

    .SetGeneratorClass(typeof(FdpSequenceGen).AssemblyQualifiedName)

    .AddGeneratorParam("allcolumns", AllColumns)

    .AddGeneratorParam("table", "zSysCounters") //ID counter table name

    .AddGeneratorParam("column", TableName); //ID counter column name

}

Now I can unit test using a temporary in-memory SQLite database and all my tests are passing.

© NHibernate Community 2024