Try fast search NHibernate

13 April 2011

NHibernate 3.2: mapping by code conventions

GallettiAlloSpiedo

After see some characteristic of one of the new NHibernate 3.2 feature it’s the time to see if the mapping by-code gives support to a convention based mapping. In NHibernate there isn’t a real difference between declarative-mapping and convention-based-mapping because every thing live together since the first commit.

What you will see in this post is defined, by some people, as “auto-mapping”. We don’t have such pretention, and we don’t need to sell something so we are far away to call it auto-mapping but you are free to call it as you want.

For the coming soon version we took the decision to avoid to override the tables/columns naming-convention provided by NHibernate XML mapping; the same for cascade.

The domain model

The model was provided by a NHibernate user: Luka (cluka23)

NHConventions

Well… not so complicated but even not so simple Winking smile

The mapping

Luka have sent me the conformist-mapping so I can understand which is his target. Only to have a little bit of fun I have added two fields for two collections (see class Application). The whole mapping with conventions and registration in NHibernate’s configuration is:

  1.     var mapper = new ConventionModelMapper();
  2.  
  3.     var baseEntityType = typeof(Entity);
  4.     mapper.IsEntity((t, declared) => baseEntityType.IsAssignableFrom(t) && baseEntityType != t && !t.IsInterface);
  5.     mapper.IsRootEntity((t, declared) => baseEntityType.Equals(t.BaseType));
  6.  
  7.     mapper.BeforeMapManyToOne += (insp, prop, map) => map.Column(prop.LocalMember.GetPropertyOrFieldType().Name + "Id");
  8.     mapper.BeforeMapManyToOne += (insp, prop, map) => map.Cascade(Cascade.Persist);
  9.     mapper.BeforeMapBag += (insp, prop, map) => map.Key(km => km.Column(prop.GetContainerEntity(insp).Name + "Id"));
  10.     mapper.BeforeMapBag += (insp, prop, map) => map.Cascade(Cascade.All);
  11.  
  12.     mapper.Class<Entity>(map =>
  13.     {
  14.         map.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
  15.         map.Version(x => x.Version, m => m.Generated(VersionGeneration.Always));
  16.     });
  17.  
  18.     mapper.Class<Person>(map => map.Property(x => x.Name, pm => pm.Column("PersonName")));
  19.     mapper.Class<Tenant>(map => map.Bag(x => x.AppTenants, cm => cm.Inverse(true), r => { }));
  20.     mapper.Class<Customer>(map => map.Bag(x => x.Tenants, cm => cm.Inverse(true), r => { }));
  21.     mapper.Class<Application>(map =>
  22.     {
  23.         map.Bag(x => x.AppFeatures, cm => cm.Inverse(true), r => { });
  24.         map.Bag(x => x.AppTenants, cm => cm.Inverse(true), r => { });
  25.         map.Bag(x => x.Users, cm => cm.Inverse(true), r => { });
  26.     });
  27.  
  28.     var mapping = mapper.CompileMappingFor(baseEntityType.Assembly.GetExportedTypes().Where(t => t.Namespace.EndsWith("Model")));
  29.  
  30.     nhConf.AddDeserializedMapping(mapping,"LukaModel");

Line 4: predicate to recognize entities.

Line 5: predicate to recognize root entities.

From line 7 to 10: some conventions.

From line 12 to 16: some specific mapping of some properties strongly mapped using the base Entity class

From line 18 to 26: some specific mapping of some properties for others entities

Line 28: the creation of the mapping for the whole domain

Line 30: registration of the mapping in the NHibernate configuration.

As you can see there isn’t something special to override conventions, it is all the same you can use with explicit mapping.

If you can’t resist to have a specific class to override a convention you can use something like this:

public class ApplicationMapOverride: ClassMapping<Application>
{
    public ApplicationMapOverride()
    {
        Bag(x => x.AppFeatures, cm => cm.Inverse(true), r => { });
        Bag(x => x.AppTenants, cm => cm.Inverse(true), r => { });
        Bag(x => x.Users, cm => cm.Inverse(true), r => { });
    }
}

“Ehy!! Fabio, you are wrong!! that class is for conformist-mapping.” No, I’m not wrong. You can use the same identical classes to override your conventions, the same classes you can use for declarative mapping. Not only this but can even mix altogether:

mapper.Class<Person>(map => map.Property(x => x.Name, pm => pm.Column("PersonName")));
mapper.Class<Tenant>(map => map.Bag(x => x.AppTenants, cm => cm.Inverse(true), r => { }));
mapper.Class<Customer>(map => map.Bag(x => x.Tenants, cm => cm.Inverse(true), r => { }));
mapper.AddMapping<ApplicationMapOverride>();

“and the two fields ?” those two fields was mapped, don’t worry. If you want see a chuck of the XML:

<bag name="AppFeatures" access="nosetter.camelcase" inverse="true" cascade="all">
  <key column="ApplicationId" />
  <one-to-many class="AppFeature" />
</bag>
<bag name="AppTenants" access="nosetter.camelcase-underscore" inverse="true" cascade="all">
  <key column="ApplicationId" />
  <one-to-many class="AppTenant" />
</bag>

 NHibernate 3.2 too sexy by far!!!

11 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. @Hazzik
    Keep it cool, mate!

    @Fabio
    Post/upload its classes somewhere, Plz.

    ReplyDelete
  3. @hazzik
    NH is like the Red Light District in Amsterdam.
    You have a lot of options to map your domain, you can choose the one you like or feels more comfortable.

    ReplyDelete
  4. @Fabio Maullo
    There is a little "error" in line 7:
    mapper.BeforeMapManyToOne += (insp, prop, map) => map.Column(prop.LocalMember.GetPropertyOrFieldType().Name + "Id");

    If lets say I have I class that has two User properties ie: CreatedyByUser and UpdatetByUser that are both typeof(User) i will get an error because nhibernate tries to map both them as UserId.
    So I assume this has to be changed to:
    mapper.BeforeMapManyToOne += (insp, prop, map) => map.Column(prop.LocalMember.Name + "Id");

    so that it will map CreatedByUserId and UpdatedByUserId.
    Of course we can do override mapping for this scenario.

    Anyway this new mapping and the flexibility it offers are great.

    ReplyDelete
  5. It's quite verbose, I still like Castle's ActiveRecord way, even if it's not pure DDD.

    ReplyDelete
  6. There is a bug in the example (I'm using the latest 3.2 build from NuGet):

    mapper.IsEntity((t, declared) => baseEntityType.IsAssignableFrom(t) && baseEntityType != t && !t.IsInterface);

    When selecting a root entity, each potential type needs to pass both IsEntity and IsRootEntity. So to fix this, you would need to remove the "&& baseEntityType != t" condition from IsEntity above. If you don't you'll receive exceptions about trying to extend an unmapped class.

    ReplyDelete
  7. expanding on what I mentioned above, I realized it was creating subclasses because your IsRootEntity predicate is also off.

    I changed it to this, and now it works as expected:

    mapper.IsRootEntity((t, declared) => t.BaseType != null && t.BaseType == baseEntityType);

    ReplyDelete
  8. Will the same function properly in a multi level inheritance, the example here shows a single level of inheritance.

    ReplyDelete
  9. A fully working skeleton for sexy Loquacious NH on NHForge wiki - http://nhforge.org/wikis/howtonh/a-fully-working-skeleton-for-sexy-loquacious-nh.aspx

    ReplyDelete
  10. Hi! I'm having problems when my Base Entity is Generic by Example BaseEntity, where T defines the Primary Key Type.
    I've changed it with a non-generic class and this work fine. Is it recomended to use non-generic base entity?

    ReplyDelete