NHibernate: playing with mapping by code (2)

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();
    }
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< MemberInfoboolbool> 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< MemberInfoboolbool> 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<Typeboolbool> 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”…

你可能感兴趣的:(Hibernate)