EDIT: Please note that this way of configuring Fluent NHibernate is obsolete since the RCs. Look here for an example on how to customize the automapping on Fluent NHibernate 1.0 RTM.
Fluent NHibernate is cool because there is too much XML in the world – so I appreciate any initiative to bring down the amount of XML. Configuring NHibernate using simple code and strongly types lambdas is actually pretty cool.
However, there is also too much code in the world – therefore, the AutoPersistenceModel is my preferred way of configuring Fluent NHibernate. It allows me to focus on what really matters: my domain model.
But don’t you lose control when all your database mapping is done automatically adhering only to a few conventions? No! You can always customize your mappings and complement/override the auto mappings. But then we can easily end up customizing too much, and then we are back to writing too much code.
Therefore, I would like to show how I use attributes to spice up my NHibernate mapping and add that tiny bit of extra customization that I need. I would like to be able to make the following entity:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class ImportantBusinessEntity { public virtual Guid Id { get; set; } [Indexed, Unique] public virtual string Name { get; set; } [Length(2048)] public virtual string Description { get; set; } [Encrypted] public virtual string CodeName { get; set; } } |
That is, I want to be able to describe some extra mapping related stuff by applying a few non-instrusive attributes in my domain model.
How can that be accomplished with Fluent NHibernate? Easy! Like so:
1 2 3 4 5 6 7 8 9 10 11 12 |
var model = new AutoPersistenceModel { Conventions = { // (...) my ordinary mapping conventions in here } } model.Conventions.ForAttribute<IndexedAttribute>(IndexedAttribute.AlterMap); model.Conventions.ForAttribute<UniqueAttribute>(UniqueAttribute.AlterMap); model.Conventions.ForAttribute<LengthAttribute>(LengthAttribute.AlterMap); model.Conventions.ForAttribute<EncryptedAttribute>(EncryptedAttribute.AlterMap); |
and then I have made the following attributes:
IndexedAttribute.cs:
1 2 3 4 5 6 7 8 |
[AttributeUsage(AttributeTargets.Property)] public class IndexedAttribute : Attribute { public static void AlterMap(IndexedAttribute attr, IProperty prop) { prop.SetAttribute("index", "IX__" + prop.Property.Name); } } |
UniqueAttribute.cs (works with ordinary as well as composite unique constraints):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
[AttributeUsage(AttributeTargets.Property)] public class UniqueAttribute : Attribute { readonly string key; public UniqueAttribute() { } public UniqueAttribute(string key) { this.key = key; } public static void AlterMap(UniqueAttribute attr, IProperty prop) { if (attr.key == null) { prop.SetAttribute("unique", "true"); } else { prop.SetAttribute("unique-key", attr.key); } } } |
LengthAttribute.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[AttributeUsage(AttributeTargets.Property)] public class LengthAttribute : Attribute { readonly int length; public LengthAttribute(int length) { this.length = length; } public static void AlterMap(LengthAttribute attr, IProperty prop) { prop.SetAttribute("length", attr.length.ToString()); } } |
EncryptedAttribute.cs:
1 2 3 4 5 6 7 8 |
[AttributeUsage(AttributeTargets.Property)] public class EncryptedAttribute : Attribute { public static void AlterMap(EncryptedAttribute attr, IProperty prop) { prop.SetAttribute("type", typeof(EncryptedStringUserType).AssemblyQualifiedName); } } |
If you are an extremist PI kinda guy you might think this is wrong – and I might agree with you a little, but I am also a pragmatic kinda guy, and I think this is a great compromise between being fairly non-intrusive but still expressive, effective and clear.