Wednesday, February 8, 2012

Mapping-by-Code - Id, NaturalId

I've already finished going through all the Ayende's posts about NHibernate mappings and analysing its mapping-by-code equivalents, but I feel I haven't covered everything. The first quite obvious NHibernate's feature Ayende skipped is primary key mapping. There are several options available - from identity columns, native generators, through HiLo and sequences, up to composite identifiers. As always, I'm not going to explain how each of them works, I'll keep focus on mapping-by-code and Fluent NHibernate's possibilities.

The most common primary key mapping is available through Id method.

Id(x => x.Id, m =>

m.Generator(Generators.Native, g => g.Params(new
// generator-specific options

m.Type(new Int32Type());

There are several features missing - like unsaved-value (will be added in NHibernate 3.3) or DDL column options.

Probably the most important option is Generator. It specifies the way NHibernate (or database) generates unique values. There are 8 predefined types of generators available through static Generators class:

public static class Generators
// assigning the values is client code responsibility
public static IGeneratorDef Assigned { get; }

// whatever is native in underlying database
public static IGeneratorDef Native { get; }

// High-Low
public static IGeneratorDef HighLow { get; }

// Guid
public static IGeneratorDef Guid { get; }

// sequential Guid
public static IGeneratorDef GuidComb { get; }

// database-level sequence
public static IGeneratorDef Sequence { get; }

// database-level identity
public static IGeneratorDef Identity { get; }

// driven by referenced entity
public static IGeneratorDef Foreign<TEntity>(Expression<Func<TEntity, object>> property);
public static IGeneratorDef Foreign(MemberInfo property);

Some of those can have additional parameters specified using Generator method second parameter and anonymous type inside Params call. This is probably the only place in mapping-by-code when we have not strongly-typed options, but it's not worse than in XML.

Here are the parameters available for HighLow, as well as its default values:

m.Generator(Generators.HighLow, g => g.Params(new
max_lo = Int16.MaxValue, // size of ids range reserved at once - Int16.MaxValue by default
table = "hibernate_unique_key",
schema = null,
catalog = null,
column = "next_hi",
where = "", // additional SQL condition applied to query when fetching next_hi value

And here is the similiar list for Sequence:

m.Generator(Generators.Sequence, g => g.Params(new
sequence = "hibernate_sequence",
schema = null,
catalog = null,
parameters = null, // driver-specific DDL string to be appended to create command

There's another id-related option available - natural ID. It is in fact not a primary key handling option, but it's related, so I decided to include it here. It is available through NaturalId mapping method.

NaturalId(m =>
m.Property(x => x.KeyPropery);
// etc...

Mutable attribute from XML natural-id element has no equivalent in mapping-by-code.

Fluent NHibernate's equivalents

For mapping surrogate keys there is Id method chain available. Let's see first which of the generators are available directly:

Id(x => x.Id)
.GeneratedBy.HiLo("table", "column", "maxLo", "where")
.GeneratedBy.SeqHiLo("sequenceName", "maxLo")

All the parameters are optional. Apart from that, there is an additional configuration interface available in most generators:

p => p.AddParam("name", "value")

As we can see, the number of options available directly is higher than in mapping-by-code.

Time to look at different id mapping options.

Id(x => x.Id)
.Check("sql constraint")

There are a lot more options available in FNH than in mapping-by-code. I think that at least some of them are completely useless (like defining unique key name), but this is not a FNH's bug, this is what NHibernate mapping does allow, surprisingly.

NaturalId is supported well, too.

.Property(x => x.Name, "columnName")
.Reference(x => x.Test, "referenceColumnName")
// etc.

Column names are optional. There's no way to set other column options. And ReadOnly is FNH name for mutable attribute.


  1. How about hooking up your own ID generator class?

    You can implement IIdentifierGenerator and pass it as a in XML but how do you do it with mapping-by-code? i can't figure it out.