Using ConfORM with modular application

Using ConfORM with modular application

I’m getting some questions about how use  ConfORM in modular applications. The first commercial application mapped with ConfORM is a pretty complex modular application and if you have a look to the first  ConfOrm example you can find the IModuleMapping.
Here I’ll try to explain how we have used it in that application writing a new example.

The IModuleMapper

///   <summary>
///  A template to perform a mapping using ConfOrm
///   </summary>
public  interface  IModuleMapper
{
     ///   <summary>
     ///  Register domain classes, persistent-strategies, relations and so on of a spefic module
     ///   </summary>
     void DefineDomain();

     ///   <summary>
     ///  Register patterns of a module
     ///   </summary>
     void RegisterPatterns();

     ///   <summary>
     ///  Customize persistence representations
     ///   </summary>
     void Customize();

     ///   <summary>
     ///  Get all domain entities of the module.
     ///   </summary>
     ///   <returns> domain entities. </returns>
     IEnumerable< Type> GetEntities();
}
As you can see it is a simple template just to suggest how organize your mapping… nothing complicated.

The Domain

In the assembly  Acme.ModuleA
using System;
using Acme.Domain.Common;

namespace Acme.ModuleA
{
     public  class  UserEntity
    {
         public  string UserName {  getset; }
         public  string Text {  getset; }
         public  DateTime Birthday {  getset; }
    }
}
In the assembly  Acme.ModuleB
using System;
using Acme.Domain.Common;
using Acme.ModuleA;

namespace Acme.ModuleB
{
     public  class  ArticleEntity
    {
         public  DateTime PublishedAt {  getset; }
         public  string Title {  getset; }
         public  string Text {  getset; }
         public  User Author {  getset; }
    }
}

 

Some extensions

Just to not forget it later… 
public  static  class  ModuleMappingUtil
{
     public  static  IEnumerable< Type> MapModule( this  IModuleMapper mapper)
    {
        mapper.DefineDomain();
        mapper.RegisterPatterns();
        mapper.Customize();
         return mapper.GetEntities();
    }

     public  static  bool IsOfModuleContaining<T>( this  Type source)
    {
         return source.Assembly.Equals( typeof(T).Assembly);
    }
}

 

The mapping

You can organize the mapping of your modules in a single assembly, in general the same where you are generating the session factory, or in more than one assembly. In general you need just a little class to map a module so please don’t be exaggerated separating your application. 
For this example I will show three implementations of ModuleMapper all implemented in the same assembly named  Acme.Persistence.Wiring.
public  class  GeneralsPatternsModuleMapper :  IModuleMapper
{
     private  readonly  Mapper mapper;
     private  readonly  ObjectRelationalMapper orm;

     public GeneralsPatternsModuleMapper( ObjectRelationalMapper orm,  Mapper mapper)
    {
         this.orm = orm;
         this.mapper = mapper;
    }

     #region IModuleMapper Members

     public  void DefineDomain()
    {
         // map .NET4 ISet<T> as a NHibernate's set
        orm.Patterns.Sets.Add(mi => mi.GetPropertyOrFieldType().GetGenericIntercafesTypeDefinitions().Contains( typeof ( ISet<>)));
    }

     public  void RegisterPatterns()
    {
         // all strings are length 50 characters
        mapper.PatternsAppliers.Property.Add(mi =>  typeof ( string).Equals(mi.GetPropertyOrFieldType()), (mi, map) => map.Length(50));
      
         // when a DateTime seems to be a simple date apply NH's Date type
        mapper.PatternsAppliers.Property.Add(mi =>  typeof ( DateTime).Equals(mi.GetPropertyOrFieldType()) && IsDate(mi.Name), (mi, map) => map.Type( NHibernateUtil.Date));

         // when a DateTime seems to not be a simple date apply NH's UtcDateTime type
        mapper.PatternsAppliers.Property.Add(mi =>  typeof ( DateTime).Equals(mi.GetPropertyOrFieldType()) && !IsDate(mi.Name), (mi, map) => map.Type( NHibernateUtil.UtcDateTime));
    }

     public  void Customize()
    {
         // Nothing to do
    }

     public  IEnumerable< Type> GetEntities()
    {
         yield  break;
    }

     #endregion

     public  static  bool IsDate( string name)
    {
         return name.EndsWith( "Date") || name.StartsWith( "Date") || name.EndsWith( "Day") || name.EndsWith( "day") || name.StartsWith( "Day");
    }
}
The GeneralsPatternsModuleMapper register just part of my common convention for the current application. Then the mapping of my  Acme.ModuleA:
public  class  ModuleAMapper :  IModuleMapper
{
     private  readonly  Mapper mapper;
     private  readonly  ObjectRelationalMapper orm;

     public ModuleAMapper( ObjectRelationalMapper orm,  Mapper mapper)
    {
         this.orm = orm;
         this.mapper = mapper;
    }

     public  void DefineDomain()
    {
         // register all classes of my module (ConfORM will use it to discover polymorphic associations)
        orm.AddToDomain( typeof( User).Assembly.GetExportedTypes());

         // defines the persistence strategy for each root of each hierarchy
        orm.TablePerClass< User>();
    }

     public  void RegisterPatterns()
    {
         // no specific patterns for this module
    }

     public  void Customize()
    {
         // map a specific size (20) for a specific property of a specific class
        mapper.Class< User>(x => x.Property(user => user.UserName, map => map.Length(20)));
    }

     public  IEnumerable< Type> GetEntities()
    {
         // all entities of this modules
         return  typeof( User).Assembly.GetExportedTypes().Where(t=>  typeof( Entity).IsAssignableFrom(t));
    }
}
The mapping of  Acme.ModuleB:
public  class  ModuleBMapper :  IModuleMapper
{
     private  readonly  Mapper mapper;
     private  readonly  ObjectRelationalMapper orm;

     public ModuleBMapper( ObjectRelationalMapper orm,  Mapper mapper)
    {
         this.orm = orm;
         this.mapper = mapper;
    }

     public  void DefineDomain()
    {
         // register all classes of my module (ConfORM will use it to discover polymorphic associations)
        orm.AddToDomain( typeof( Article).Assembly.GetExportedTypes());

         // defines the persistence strategy for each root of each hierarchy
        orm.TablePerClassHierarchy< Article>();
    }

     public  void RegisterPatterns()
    {
         // patterns for this module

         // when a string property is named "Text" then apply StringClob type  (note just for Acme.ModuleB)
        mapper.PatternsAppliers.Property.Add(
            mi =>  "text".Equals(mi.Name.ToLowerInvariant()) &&  typeof ( string).Equals(mi.GetPropertyOrFieldType()) && mi.DeclaringType.IsOfModuleContaining< Article>(),
            (mi, map) => { map.Type( NHibernateUtil.StringClob); map.Length( int.MaxValue); });
    }

     public  void Customize()
    {
         // nothing to do
    }

     public  IEnumerable< Type> GetEntities()
    {
         // all entities of this modules
         return  typeof( Article).Assembly.GetExportedTypes().Where(t =>  typeof( Entity).IsAssignableFrom(t));
    }
}

 

The NHibernate’s initialization

First you need a method to get all modules of your application, for simplicity here is a simple implementation:
private  static  IEnumerable< IModuleMapper> GetAllModulesMappers( ObjectRelationalMapper orm,  Mapper mapper)
{
     yield  return  new  GeneralsPatternsModuleMapper(orm, mapper);
     yield  return  new  ModuleAMapper(orm, mapper);
     yield  return  new  ModuleBMapper(orm, mapper);
}
and now you have all is needed to have the session factory:
  1. var orm = new ObjectRelationalMapper();
  2. var patternSet = new CoolPatternsAppliersHolder(orm);
  3. var mapper = new Mapper(orm, patternSet);
  4.  
  5. HbmMapping mappings = mapper.CompileMappingFor(GetAllModulesMappers(orm, mapper).SelectMany(x => x.MapModule()));
  6.  
  7. var configure = new Configuration();
  8. configure.SessionFactoryName("Demo");
  9. configure.DataBaseIntegration(db =>
  10. {
  11.     db.Dialect<MsSql2008Dialect>();
  12.     db.ConnectionStringName = "ToAcmeDb";
  13. });
  14.  
  15. configure.AddDeserializedMapping(mappings, "AcmeApplicationDomain");
  16.  
  17. ISessionFactory factory = configure.BuildSessionFactory();
In the line 5 I’m getting just one mapping document for the whole domain. You can use the method mapper.CompileMappingForEach without problems. The main matter here is the fact that I’m using just one instance of ObjectRelationalMapper and Mapper to give the whole picture of the domain of the application to ConfORM.

你可能感兴趣的:(application)