NHibernate: playing with mapping by code (2)
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();
}
{
public ConventionModelMapper()
: base(new SimpleModelInspector())
{
AppendDefaultEvents();
}
public void IsPersistentProperty(Func<MemberInfo, bool, bool> match)
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);
}
{
SimpleModelInspector.IsPersistentProperty(match);
}
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:- var mapper = new ConventionModelMapper();
- mapper.BeforeMapClass += (mi, t, map) => map.Table(t.Name.ToLowerInvariant());
- mapper.BeforeMapJoinedSubclass += (mi, t, map) => map.Table(t.Name.ToLowerInvariant());
- mapper.BeforeMapUnionSubclass += (mi, t, map) => map.Table(t.Name.ToLowerInvariant());
- mapper.BeforeMapProperty += (mi, propertyPath, map) =>
- {
- if (typeof(decimal).Equals(propertyPath.LocalMember.GetPropertyOrFieldType()))
- {
- map.Type(NHibernateUtil.Currency);
- }
- };
- mapper.BeforeMapBag += (mi, propPath, map) =>
- {
- map.Cascade(Cascade.All.Include(Cascade.DeleteOrphans));
- map.BatchSize(10);
- };
- mapper.BeforeMapClass += (mi, type, map) =>
- map.Id(idmap => idmap.Generator(Generators.HighLow,
- gmap => gmap.Params(new
- {
- table = "NextHighVaues",
- column = "NextHigh",
- max_lo = 100,
- where = string.Format("EntityName = '{0}'", type.Name.ToLowerInvariant())
- })));
- Func<Type, bool, bool> matchRootEntity = (type, wasDeclared)=> typeof(Entity).Equals(type.BaseType);
- var entities = Assembly.GetExecutingAssembly().GetExportedTypes()
- .Where(t => typeof(Entity).IsAssignableFrom(t) && !typeof(Entity).Equals(t)).ToList();
- mapper.IsEntity((type, wasDeclared) => entities.Contains(type));
- mapper.IsRootEntity(matchRootEntity);
- HbmMapping domainMapping = mapper.CompileMappingFor(entities);
- given a System.Type how recognize if it is an entity or not (line 32,33,34)
- given a System.Type how recognize if it is a root-entity (the top class of a hierarchy); line 31,35
- which are the classes that the ConventionModelMapper have to map (line 32, 36)
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)));
mapper.Class<Order>(map => map.Property(o=> o.EmissionDay, pm=> pm.Type(NHibernateUtil.Date)));
The result
The integration is pretty the same:- var configuration = new Configuration();
- configuration.DataBaseIntegration(c =>
- {
- c.Dialect<MsSql2008Dialect>();
- c.ConnectionString = @"Data Source=localhost\SQLEXPRESS;Initial Catalog=IntroNH;Integrated Security=True;Pooling=False";
- c.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
- c.SchemaAction = SchemaAutoAction.Create;
- });
- configuration.AddMapping(domainMapping);
- configuration.AddAuxiliaryDatabaseObject(CreateHighLowScript(Assembly.GetExecutingAssembly().GetExportedTypes().Where(t => matchRootEntity(t, false))));
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>
<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>