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 12-24-2009, Last revision by: Chris Nicola on 12-25-2009

Customizing Fluent Nhibernate's AutoPersistenceModel Conventions

(this was originally blogged about here)

One of the things I get a fair bit of use out of is the fluent nHibernate project and the AutoPersistenceModel.  If you are not very familiar with fluentNH or AutoPersistenceModel then I would suggest checking out their wiki, as what I am about to discuss, while not difficult, is some relatively advanced usage.

The purpose of AutoPersistenceModel is to automatically generate the nHibernate configuration (the HBM files if you are currently used to XML configuration) directly from your model based on Convention over Configuration, you can hear James Kovacs discuss this concept on .NET Rocks.  With tools like SchemaExport and SchemaUpdate it is even possible to automatically generate and execute DDL scripts against your database to keep the schema in sync with your model.  This can be a bit rails like in it's use, in fact Adam Dymitruk is currently working on a utility to extend the SchemaUpdate to allow the creation of versioned database scripts like rails has (ok Adam it's official, now you actually have to finish it!).

The default AutoPersistenceModel conventions are kept quite simple, so often you will need to do a bit of customization.  One way to do this is through overrides which I showed how to use in this post, were I implemented an override for a self-referencing tree relationship.  However, whenever possible, it is preferable to use fluentNH's conventions to do this.  Conventions can be used define custom behavior in very flexible ways.  Below I am going to show how this can be done to customize database constraints like unique and index. [more]

S#arp Architecture provides a [DomainSignature] attribute which you can apply to your entity's properties.   The attribute is used to denote the set of properties that uniquely define the entity and is similar to the concept of a business key often used in SQL database design.  It is important to point out that the DomainSignature should not be considered a primary key and that you should always use a surrogate primary key generated either by the database or nHibernate (hilo and guid.comb are the two I prefer).

The domain signature can ensures that entities can be compared using the properties decorated with [DomainSignature].  This is useful if say one object was loaded from the repository using nHibernate but another was constructed and I want to determine if I should treat them as the same entity.  It is also useful to determine if a new entity will violate a uniqueness constraint you want to enforce.

A good example of a useful DomainSignature would be the slug of a blog post.  A post also has an id for it's primary key, in most cases a guid, but the slug also uniquely identifies a post as well and no two posts can have the same slug.  The only problem now is that when I generate my DDL I can see that nHibernate has no notion that it should enforce my uniqueness constraint.

What I want is for nHibernate and hence the database schema should be aware that DomainSignature implies a uniqueness constraint.  Fortunately, fluent nHibernate conventions make this is quite easy.  Fluent nHibernate defines an AttributePropertyConvention<T> base class for exactly this purpose and we can extend it like this:

  1. public class DomainSignatureConvention : AttributePropertyConvention<DomainSignatureAttribute> {
  2.     protected override void Apply(DomainSignatureAttribute attribute, IPropertyInstance instance) {
  3.         instance.UniqueKey(instance.EntityType.Name + "DomainSignature");    
  4.     }
  5. }

The DomainSignatureConvention tells fluent that all properties that are decorated with [DomainSignature] should form a unique key.  Now if I then define my entity like this:

  1. public class Price : Entity {
  2.     [DomainSignature]
  3.     public virtual Security Security { get; set; }
  4.     [DomainSignature]
  5.     public virtual DateTime PriceDate { get; set; }
  6.     [DomainSignature]
  7.     public virtual bool CanadianPrice { get; set; }
  8.     public virtual decimal Bid { get; set; }
  9.     public virtual decimal Ask { get; set; }
  10.     public virtual decimal Close { get; set; }
  11. }

 

then SchemaExport will generate DDL like this:

create table Prices (
    Id INTEGER not null,
   PriceDate DATETIME,
   CanadianPrice INTEGER,
   Bid NUMERIC,
   Ask NUMERIC,
   Close NUMERIC,
   SecurityFk INTEGER,
   primary key (Id),
  unique (PriceDate, CanadianPrice)
)

Except we have a small problem here.  The foreign key for the many-to-one relationship on Security was not included in the unique constraint.  To be quite honest I am not exactly sure why but I am guessing that the AttributePropertyConvention does not work with a reference.  Instead I will need to add something to my ReferenceConvention which is provided by default with s#arp architecture.

  1. public class ReferenceConvention : IReferenceConvention {
  2.     public void Apply(FluentNHibernate.Conventions.Instances.IManyToOneInstance instance) {
  3.         instance.Column(instance.Property.Name + "Fk");
  4.         if (Attribute.IsDefined(instance.Property, typeof (DomainSignatureAttribute)))
  5.             instance.UniqueKey("DomainSignature");
  6.     }
  7. }

I don't really like this as I don't think the attribute should be ignored, but it does at least change the above to unique(PriceDate, CanadianPrice, SecurityFk).

There are obviously many other types of constraints we could use, you could create an attribute for defining indexing on certain properties like [Indexable("IndexName")] and create a convention like this:

  1. public class IndexableAttribute : Attribute {
  2.     private readonly string _name;
  3.  
  4.     public IndexableAttribute(string name) { _name = name; }
  5.  
  6.     public string GetName() { return _name; }
  7. }
  8.  
  9. public class IndexableConvention : AttributePropertyConvention<IndexableAttribute> {
  10.     protected override void Apply(IndexableAttribute attribute, IPropertyInstance instance) {
  11.         instance.Index(attribute.GetName());
  12.     }
  13. }

Now I can create indicies by using the [Indexable("Name")] attribute.  Properties with the same "Name" will be part of the same index constraint and indexed together. 

I found it is actually a good idea to always index many-to-one relationships, something I briefly mentioned in a previous post.  Now that I am using fluent nhibernate however I can't set the index property in the HBM files so I will need a convention for that.  It also makes sense to set this indexing on all many-to-one relationships so there is a better way to do this that does not involve the use of an attribute, instead we implement IRefrenceConvention.  S#arp architecture already includes this convention by default so we can just edit it:

  1. public class ReferenceConvention : IReferenceConvention {
  2.         public void Apply(FluentNHibernate.Conventions.Instances.IManyToOneInstance instance) {
  3.             instance.Column(instance.Property.Name + "Fk");
  4.             if (Attribute.IsDefined(instance.Property, typeof (DomainSignatureAttribute)))
  5.                 instance.UniqueKey("DomainSignature");
  6.             else
  7.                 instance.Index(instance.Property.Name + "Index");
  8.         }
  9.     }

Notice the if.else, if we have already defined UniqueKey defining Index would be redundant as unique key's are already indexed.  When defining indexes you will typically see something like like the following DDL output from SchemaExport:

create index SecurityIndex on Prices (SecurityFk)

It is pretty easy to customize the behavior of the fluent nhibernate AutoPersistenceModel using conventions and I find it can be very useful to have this type of fine grained control over your database schema generation.

© NHibernate Community 2024