Try fast search NHibernate

17 July 2011

NHibernate: playing with mapping by code (2)

galletto-alla-mediterranea
In the previous post you saw a simple example about a way to use the new mapping-by-code of NHibernate 3.2.0.
The class-by-class mapping will be, probably, the most used way just because it is very similar to the XML mapping and the “feeling of loss of control” is near to zero (the quiet of sense).
If you want experiment more adrenaline you can try ConfORM but if you like just a little bit of more adrenaline you can use the NHibernate’s ConventionModelMapper.


Inside the ConventionModelMapper

To understand what really is the ConventionModelMapper let me show you a piece of its code:
public class ConventionModelMapper : ModelMapper
{
    public ConventionModelMapper()
        : base(new SimpleModelInspector())
    {
        AppendDefaultEvents();
    }
clear! no? The ConventionModelMapper is just a specialization of a ModelMapper where, instead the ExplicitlyDeclaredModel, we are injecting another implementation of IModelInspector. To “simplify” its usage, and obscure to you the real power of Dependency-Injection, the ConventionModelMapper exposes some methods as, for instance:
public void IsPersistentProperty(Func<MemberInfo, bool, bool> match)
What are those three parameters of the delegate ?
The name of the method is the question : is it a persistent property ?
The MemberInfo is the subject of the question. The bool parameter is an “help” to take a decision and it represent what was explicitly defined (we will see later where was defined). The last bool is the answer of the question.
What happen if we have a look to the implementation of the above method ?
public void IsPersistentProperty(Func<MemberInfo, bool, bool> match)
{
    SimpleModelInspector.IsPersistentProperty(match);
}
As you can see the implementation is completely passed to the SimpleModelInspector, nothing more than an “simplification” to allow you the usage of just one class.


ConventionModelMapper vs class-by-class mapping

This is not a matter. All these ways to perform the mapping-task, in NHibernate 3.2.0, are not one versus others because you can use all together. You can organize your mapping as you feel more confortable. There will be users who prefer explicit-class-by-class for the whole mapping; there will be users who prefer the usage of ConventionModelMapper and the method-based-mapping to specify convention-exceptions/overrides; there will be users who prefer the usage of ConventionModelMapper and the class-by-class as an organization of convention-exceptions/overrides; there will be users who implements their IModelInspector based on attributes; there will be users who implements their IModelInspector based on a custom DSL.


The new mapping

Taking the domain of the previous post, and some conventions already defined there, we can start from something like this:
  1. var mapper = new ConventionModelMapper();
  2.  
  3. mapper.BeforeMapClass += (mi, t, map) => map.Table(t.Name.ToLowerInvariant());
  4. mapper.BeforeMapJoinedSubclass += (mi, t, map) => map.Table(t.Name.ToLowerInvariant());
  5. mapper.BeforeMapUnionSubclass += (mi, t, map) => map.Table(t.Name.ToLowerInvariant());
  6.  
  7. mapper.BeforeMapProperty += (mi, propertyPath, map) =>
  8. {
  9.     if (typeof(decimal).Equals(propertyPath.LocalMember.GetPropertyOrFieldType()))
  10.     {
  11.         map.Type(NHibernateUtil.Currency);
  12.     }
  13. };
  14.  
  15. mapper.BeforeMapBag += (mi, propPath, map) =>
  16. {
  17.     map.Cascade(Cascade.All.Include(Cascade.DeleteOrphans));
  18.     map.BatchSize(10);
  19. };
  20.  
  21. mapper.BeforeMapClass += (mi, type, map) =>
  22.     map.Id(idmap => idmap.Generator(Generators.HighLow,
  23.         gmap => gmap.Params(new
  24.         {
  25.             table = "NextHighVaues",
  26.             column = "NextHigh",
  27.             max_lo = 100,
  28.             where = string.Format("EntityName = '{0}'", type.Name.ToLowerInvariant())
  29.         })));
  30.  
  31. Func<Type, bool, bool> matchRootEntity = (type, wasDeclared)=> typeof(Entity).Equals(type.BaseType);
  32. var entities = Assembly.GetExecutingAssembly().GetExportedTypes()
  33.     .Where(t => typeof(Entity).IsAssignableFrom(t) && !typeof(Entity).Equals(t)).ToList();
  34. mapper.IsEntity((type, wasDeclared) => entities.Contains(type));
  35. mapper.IsRootEntity(matchRootEntity);
  36. HbmMapping domainMapping = mapper.CompileMappingFor(entities);
More then the starting point (line 1), where I’m using just the constructor of ConventionModelMapper, the difference, so far, is from line 31 to 36. Because I have completely removed any kind of explicit mapping (that “incomplete class-by-class mapping”) I have to inform the ConventionModelMapper about:
  1. given a System.Type how recognize if it is an entity or not (line 32,33,34)
  2. given a System.Type how recognize if it is a root-entity (the top class of a hierarchy); line 31,35
  3. which are the classes that the ConventionModelMapper have to map (line 32, 36)
Which is the advantage ? With the mapping of the previous post each time I’m adding a class to the domain I have to add its mapping; using this new mapping I can add a class to the domain without touch the mapping.
The mapping process is not completed because in the removed class-by-class mapping there was two specifications outside our required conventions: the Customer.TaxId as natural-id and the Order.EmissionDay as Date. To specify only these two convention-exceptions I can use the method-based-mapping in this way:
mapper.Class<Customer>(map => map.NaturalId(nm => nm.Property(c => c.TaxId)));
mapper.Class<Order>(map => map.Property(o=> o.EmissionDay, pm=> pm.Type(NHibernateUtil.Date)));
You can put the two lines in any place before call mapper.CompileMappingFor(entities).


The result

The integration is pretty the same:
  1. var configuration = new Configuration();
  2. configuration.DataBaseIntegration(c =>
  3. {
  4.     c.Dialect<MsSql2008Dialect>();
  5.     c.ConnectionString = @"Data Source=localhost\SQLEXPRESS;Initial Catalog=IntroNH;Integrated Security=True;Pooling=False";
  6.     c.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
  7.     c.SchemaAction = SchemaAutoAction.Create;
  8. });
  9. configuration.AddMapping(domainMapping);
  10. configuration.AddAuxiliaryDatabaseObject(CreateHighLowScript(Assembly.GetExecutingAssembly().GetExportedTypes().Where(t => matchRootEntity(t, false))));
The difference is just at line 10.
After build the session-factory we will have again

And the XML will be (please look it closer):
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                   xmlns:xsd="http://www.w3.org/2001/XMLSchema"
                   namespace="PlayWithMappingByCode"
                   assembly="PlayWithMappingByCode"
                   xmlns="urn:nhibernate-mapping-2.2">
  <class name="Customer" table="customer">
    <id name="Id" type="Int32">
      <generator class="hilo">
        <param name="table">NextHighVaues</param>
        <param name="column">NextHigh</param>
        <param name="max_lo">100</param>
        <param name="where">EntityName = 'customer'</param>
      </generator>
    </id>
    <natural-id>
      <property name="TaxId" />
    </natural-id>
    <property name="CommercialName" />
    <bag name="Orders" cascade="all,delete-orphan" batch-size="10">
      <key column="Customer" />
      <one-to-many class="Order" />
    </bag>
  </class>
  <class name="Order" table="order">
    <id name="Id" type="Int32">
      <generator class="hilo">
        <param name="table">NextHighVaues</param>
        <param name="column">NextHigh</param>
        <param name="max_lo">100</param>
        <param name="where">EntityName = 'order'</param>
      </generator>
    </id>
    <many-to-one name="Customer" />
    <property name="EmissionDay" type="Date" />
    <bag name="Items" cascade="all,delete-orphan" batch-size="10">
      <key column="Order" />
      <one-to-many class="OrderItem" />
    </bag>
  </class>
  <class name="OrderItem" table="orderitem">
    <id name="Id" type="Int32">
      <generator class="hilo">
        <param name="table">NextHighVaues</param>
        <param name="column">NextHigh</param>
        <param name="max_lo">100</param>
        <param name="where">EntityName = 'orderitem'</param>
      </generator>
    </id>
    <many-to-one name="Order" />
    <property name="Product" />
    <property name="Price" type="Currency" />
  </class>
</hibernate-mapping>
The mapping is correct and it works but just for a “casualty”…

7 comments:

  1. 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
  2. 1. How to map component using ConventionModelMapper

    2. When I create abstract class for creating Id, it will also generate table for abstract class, how to avoid it?

    public abstract class BaseDomain
    {
    public virtual Guid Id {get;set;}
    }

    public class Person : BaseDomain
    {
    ...
    }

    ReplyDelete
  3. Hi Fabio.

    I'm with a doubt!

    I am having difficulty to mapping a relation like this

    class Person {int id, string name, IList PeopleTelephones}
    class Phone {Id, Number}
    class PeopleTelephone{People, Telephone}

    Notice that the relationship between person and telephone (PeopleTelephone) results on the IList type PeopleTelephone. But I do not want that!

    Bag (x => x.PeopleTelephones , map => map.Key (km => km.Column ("IdPeople")), map => map.OneToMany ());

    I want him to be of type Phone.

    class Person {int id, string name, IList Phones}

    I managed to do this with linq

    public IList Phones
    {
    get { return this.PeopleTelephones.Select(x => c.Phone).ToList(); }
    }

    but how the Phone property is not in the NH mapping, I can not use this to make queries.

    ReplyDelete
  4. class Person {int id, string name, IList Phones}

    That is all.

    ReplyDelete
  5. Ok Fabio !

    But the mapping ?? How i map the Phones Ilist ?

    Bag (x => x.Phones, .... ???

    ReplyDelete
  6. This comment has been removed by the author.

    ReplyDelete
  7. Hi Fabio,

    I am having issues with namespaces and ConventionModelMapper. I have an entity "Item" defined in two different namespaces. So I am getting ambiguous exceptions.

    Do you have any suggestions?

    ReplyDelete