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.Column("id");

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

m.Length(10);
m.Type(new Int32Type());
m.Access(Accessor.Field);
});

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.Assigned()
.GeneratedBy.Custom<CustomGenerator>()
.GeneratedBy.Foreign("propertyName")
.GeneratedBy.Guid()
.GeneratedBy.GuidComb()
.GeneratedBy.GuidNative()
.GeneratedBy.HiLo("table", "column", "maxLo", "where")
.GeneratedBy.Identity()
.GeneratedBy.Increment()
.GeneratedBy.Native("sequenceName")
.GeneratedBy.Select()
.GeneratedBy.SeqHiLo("sequenceName", "maxLo")
.GeneratedBy.Sequence("sequenceName")
.GeneratedBy.SequenceIdentity("sequenceName")
.GeneratedBy.TriggerIdentity()
.GeneratedBy.UuidHex("format")
.GeneratedBy.UuidString();

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)
.Column("id")
.Length(10)
.CustomType<CustomType>()
.Access.Field()
.UnsavedValue(0)
.Check("sql constraint")
.CustomSqlType("sqlType")
.Default(0)
.Index("idx")
.Nullable()
.Precision(10)
.Scale(10)
.Unique()
.UniqueKey("uniq");

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.

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

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

2 comments:

  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.

    ReplyDelete