[NHibernate]用一个实体类对应多个数据库表(三)

书接上回,解决目前最后一个问题:根据模版mapping自动生成各个可能的XXX专用mapping。

上回简单的谈了一下思路 

 

思路 把那些xml反序列化,然后Clone出来几份,replace一些东西之后再序列化 但是,在什么时机呢,由谁来做呢?可以有很多的选择。比如说可以在编译之前手动执行一个tool做这件事,然后把新得到的那些mapping都编译进去。但是这意味着每次客户追加一个XXX的可能取值,我们都需要重新编译我们的dll。这无疑是很麻烦的。

 

鉴于目前还不会实现完全动态的加载新的mapping,我给自己设定的目标是:手动改一处配置,然后重启一下进程。另外,为了获得更好的性能,希望已经创建好的mapping在下一次不需要重新copy-replace。

于是思路变成在BuildSessionFactory之前,读配置文件中的一个list(包含XXX的可能取值),看对应的assembly是否已经存在。若不存在emit一个出来并存在硬盘上,使下一次不用再次创建。

代码示例如下

ExpandedBlockStart.gif 代码

        
private   readonly  XmlSerializer m_serializer  =   new  XmlSerializer( typeof (HbmMapping));

        
private   void  EmbeddedHbmIntoMoudle(ModuleBuilder modBuilder,  string  replaceFrom,  string  replaceTo)
        {
            var templateAsm 
=  Assembly.Load(TemplateAssemblyName);
            
foreach  (var resourceName  in  templateAsm.GetManifestResourceNames())
            {
                
if  (resourceName.StartsWith(ReplacableResourceNamePrefix))
                {
                    var mapping 
=  m_serializer.Deserialize(templateAsm.GetManifestResourceStream(resourceName))  as  HbmMapping;
                    
foreach  (var item  in  mapping.Items)
                    {
                        
if  (item  is  HbmClass)
                        {
                            ReplaceClassEntityName(item 
as  HbmClass, replaceFrom, replaceTo);
                        }
                        
else   if  (item  is  HbmJoinedSubclass)
                        {
                            ReplaceJoinSubClassEntityName(item 
as  HbmJoinedSubclass, replaceFrom, replaceTo);
                        }
                        
else
                        {
                            
// ignore
                        }
                    }
                    var stream 
=   new  MemoryStream();
                    m_serializer.Serialize(stream, mapping);
                    modBuilder.DefineManifestResource(resourceName, stream, ResourceAttributes.Public);
                }
                
else
                {
                    
// ignore
                }
            }
        }

 

 其中只处理了HbmClass和HbmJoinedSubclass(因为项目中只用到了这两种。。。),内容就是替换其entity-name,table,many-to-one的entity-name等一切包含了XXX的地方。啊,对了,不要忘记HbmJoinedSubclass的extends(我就给忘了,结果UT跑不过)。

 关于调用的时机么,恩,这里项目出现了一些变化,客户终于允许我们使用Spring,算是帮了大忙。毕竟对于我本人来说,定制Spring的行为比定制Nhibernate的行为熟悉多了。具体到这个问题,就是继承一下Spring实现的LocalSessionFactoryObject,在某个时点插入一点自己的代码就好了。具体什么时点呢?我们来看一下AfterPropertiesSet的代码(这是Spring做完注入之后调用的一个方法)

ExpandedBlockStart.gif 代码
         public   virtual   void  AfterPropertiesSet()
        {
            
//  Create Configuration instance.
            Configuration config  =  NewConfiguration();

            
if  ( this .dbProvider  !=   null )
            {
                config.SetProperty(Environment.ConnectionString, dbProvider.ConnectionString);
                config.SetProperty(Environment.ConnectionProvider, 
typeof (DbProviderWrapper).AssemblyQualifiedName);
                configTimeDbProvider 
=   this .dbProvider;
            }

            
if  (ExposeTransactionAwareSessionFactory)
            {
                
//  Set ICurrentSessionContext implementation,
                
//  providing the Spring-managed ISession s current Session.
                
//  Can be overridden by a custom value for the corresponding Hibernate property
                config.SetProperty(Environment.CurrentSessionContextClass,  typeof (SpringSessionContext).AssemblyQualifiedName);
            }

            
if  ( this .entityInterceptor  !=   null )
            {
                
//  Set given entity interceptor at SessionFactory level.
                config.SetInterceptor( this .entityInterceptor);
            }

            
if  ( this .namingStrategy  !=   null )
            {
                
//  Pass given naming strategy to Hibernate Configuration.
                config.SetNamingStrategy( this .namingStrategy);
            }

#if  NH_2_1
            
if  ( this .typeDefinitions  !=   null )
            {
                
//  Register specified Hibernate type definitions.
                IDictionary < string string >  typedProperties  =   new   Dictionary < string string > ();
                
foreach  (DictionaryEntry entry  in  hibernateProperties)
                {
                    typedProperties.Add((
string ) entry.Key, ( string ) entry.Value);
                }

                Dialect dialect 
=  Dialect.GetDialect(typedProperties);
                Mappings mappings 
=  config.CreateMappings(dialect);
                
for  ( int  i  =   0 ; i  <   this .typeDefinitions.Length; i ++ )
                {
                    IObjectDefinition typeDef 
=   this .typeDefinitions[i];
                    Dictionary
< string string >  typedParamMap  =   new  Dictionary < string string > ();
                    
foreach  (DictionaryEntry entry  in  typeDef.PropertyValues)
                    {
                        typedParamMap.Add((
string ) entry.Key, ( string ) entry.Value);
                    }
                    mappings.AddTypeDef(typeDef.ObjectTypeName, typeDef.ObjectTypeName, typedParamMap);
                }
            }
#endif

            
if  ( this .filterDefinitions  !=   null )
            {
                
//  Register specified NHibernate FilterDefinitions.
                 for  ( int  i  =   0 ; i  <   this .filterDefinitions.Length; i ++ )
                {
                    config.AddFilterDefinition(
this .filterDefinitions[i]);
                }
            }

#if  NH_2_1
            
//  check whether proxy factory has been initialized
             if  (config.GetProperty(Environment.ProxyFactoryFactoryClass)  ==   null
                
&&  (hibernateProperties  ==   null   ||   ! hibernateProperties.Contains(Environment.ProxyFactoryFactoryClass)))
            {
                
//  nothing set by user, lets use Spring.NET's proxy factory factory
                 #region  Logging
                
if  (log.IsInfoEnabled)
                {
                    log.Info(
" Setting proxy factory to Spring provided one as user did not specify any " );
                }
                
#endregion
                config.Properties.Add(
                    Environment.ProxyFactoryFactoryClass, 
typeof (Bytecode.ProxyFactoryFactory).AssemblyQualifiedName);
            }
#endif

            
if  ( this .hibernateProperties  !=   null )
            {
                
if  (config.GetProperty(Environment.ConnectionProvider)  !=   null   &&
                    hibernateProperties.Contains(Environment.ConnectionProvider))
                {
                    
#region  Logging
                    
if  (log.IsInfoEnabled)
                    {
                        log.Info(
" Overriding use of Spring's Hibernate Connection Provider with [ "   +
                                 hibernateProperties[Environment.ConnectionProvider] 
+   " ] " );
                    }
                    
#endregion  
                    config.Properties.Remove(Environment.ConnectionProvider);
                }

                Dictionary
< string string >  genericHibernateProperties  =   new  Dictionary < string string > ();
                
foreach  (DictionaryEntry entry  in  hibernateProperties)
                {
                    genericHibernateProperties.Add((
string ) entry.Key, ( string ) entry.Value);
                }
                config.AddProperties(genericHibernateProperties);
            }
            
if  ( this .mappingAssemblies  !=   null )
            {
                
foreach  ( string  assemblyName  in  mappingAssemblies)
                {
                    config.AddAssembly(assemblyName);
                }
            }

            
if  ( this .mappingResources  !=   null )
            {
                IResourceLoader loader 
=   this .ResourceLoader;
                
if  (loader  ==   null )
                {
                    loader 
=   this .applicationContext;
                }
                
foreach  ( string  resourceName  in  mappingResources)
                {
                    config.AddInputStream(loader.GetResource(resourceName).InputStream);
                }
            }

            
if  (configFilenames  !=   null )
            {
                
foreach  ( string  configFilename  in  configFilenames)
                {
                    config.Configure(configFilename);
                }
            }

#if  NH_2_1
            
//  Tell Hibernate to eagerly compile the mappings that we registered,
            
//  for availability of the mapping information in further processing.
            PostProcessMappings(config);
            config.BuildMappings();

            
if  ( this .entityCacheStrategies  !=   null )
            {
                
//  Register cache strategies for mapped entities.
                 foreach  ( string  className  in   this .entityCacheStrategies.Keys)
                {
                    String[] strategyAndRegion 
=  StringUtils.CommaDelimitedListToStringArray( this .entityCacheStrategies.GetProperty(className));
                    
if  (strategyAndRegion.Length  >   1 )
                    {
                        config.SetCacheConcurrencyStrategy(className, strategyAndRegion[
0 ], strategyAndRegion[ 1 ]);
                    }
                    
else   if  (strategyAndRegion.Length  >   0 )
                    {
                        config.SetCacheConcurrencyStrategy(className, strategyAndRegion[
0 ]);
                    }
                }
            }

            
if  ( this .collectionCacheStrategies  !=   null )
            {
                
//  Register cache strategies for mapped collections.
                 foreach  ( string  collRole  in  collectionCacheStrategies.Keys)
                {
                    
string [] strategyAndRegion  =  StringUtils.CommaDelimitedListToStringArray( this .collectionCacheStrategies.GetProperty(collRole));
                    
if  (strategyAndRegion.Length  >   1 )
                    {
                        
throw   new  Exception( " Collection cache concurrency strategy region definition not supported yet " );
                        
// config.SetCollectionCacheConcurrencyStrategy(collRole, strategyAndRegion[0], strategyAndRegion[1]);
                    }
                    
else   if  (strategyAndRegion.Length  >   0 )
                    {
                        config.SetCollectionCacheConcurrencyStrategy(collRole, strategyAndRegion[
0 ]);
                    }
                }
            }

#endif

            
if  ( this .eventListeners  !=   null
            {
                
//  Register specified NHibernate event listeners.
                 foreach  (DictionaryEntry entry  in  eventListeners)
                {
                    ListenerType listenerType;
                    
try
                    {
                        listenerType 
=  (ListenerType) Enum.Parse( typeof  (ListenerType), ( string ) entry.Key);
                    }
                    
catch
                    {
                        
throw   new  ArgumentException( string .Format( " Unable to parse string '{0}' as valid {1} " , entry.Key,  typeof  (ListenerType)));
                    }

                    
object  listenerObject  =  entry.Value;
                    
if  (listenerObject  is  ICollection) 
                    {
                        ICollection listeners 
=  (ICollection) listenerObject;
                        EventListeners listenerRegistry 
=  config.EventListeners;

                        
//  create the array and check that types are valid at the same time
                        ArrayList items  =   new  ArrayList(listeners);
                        
object [] listenerArray  =  ( object [])items.ToArray(listenerRegistry.GetListenerClassFor(listenerType));
                        config.SetListeners(listenerType, listenerArray);
                    }
                    
else  
                    {
                        config.SetListener(listenerType, listenerObject);
                    }
                }
            }

            
//  Perform custom post-processing in subclasses.
            PostProcessConfiguration(config);

#if  NH_2_1
            
if  (BytecodeProvider  !=   null )
            {
                
//  set custom IBytecodeProvider
                Environment.BytecodeProvider  =  BytecodeProvider;
            }
            
else
            {
                
//  use Spring's as default
                
//  Environment.BytecodeProvider = new Bytecode.BytecodeProvider(this.applicationContext);
            }
#endif

            
//  Build SessionFactory instance.
            log.Info( " Building new Hibernate SessionFactory " );
            
this .configuration  =  config;
            
this .sessionFactory  =  NewSessionFactory(config);

            AfterSessionFactoryCreation();

            
//  set config time DB provider back to null
            configTimeDbProvider  =   null ;
        }

 

有点长,是不是?简单说来,这个方法做了如下的事情

ExpandedBlockStart.gif 代码
         public   virtual   void  AfterPropertiesSet()
        {
            
//  Create Configuration instance.
            Configuration config  =  NewConfiguration();

            
//  set一些基本的东西,跟Mapping息息相关
            
//  ...

            
//  Tell Hibernate to eagerly compile the mappings that we registered,
            
//  for availability of the mapping information in further processing.
            PostProcessMappings(config);
            config.BuildMappings();

            
//  set跟Mapping没啥关系的东西

            PostProcessConfiguration(config);

            
//  set另一些我们不关系的东西

            
//  Build SessionFactory instance.
            log.Info( " Building new Hibernate SessionFactory " );
            
this .configuration  =  config;
            
this .sessionFactory  =  NewSessionFactory(config);

            AfterSessionFactoryCreation();

            
//  set config time DB provider back to null
            configTimeDbProvider  =   null ;
        }

 

由此,我们就可以看出来,在config.BuildMappings()之前,我们必须完成我们的工作。我选择了override PostProcessMappings这个方法。

ExpandedBlockStart.gif 代码
     public   class  SessionFactory : LocalSessionFactoryObject
    {
        
public  IMappingAssemblyGenerator MappingAssemblyGenerator {  get set ; }

        
protected   override   void  PostProcessMappings(Configuration config)
        {
            
base .PostProcessMappings(config);
            
foreach  (var assembly  in  MappingAssemblyGenerator.Generate())
            {
                config.AddAssembly(assembly);
            }
        }
    }

 

其中MappingAssemblyGenerator是一个用于copy-replace的类,核心的代码就是上面贴过的EmbeddedHbmIntoMoudle方法。

再贴一下调用EmbeddedHbmIntoMoudle并用emit创建dll并且保存至磁盘的方法

ExpandedBlockStart.gif 代码
         public  IList < Assembly >  Generate()
        {
            var rv 
=   new  List < Assembly > ();
            var templateDllPath 
=   new  StringBuilder(DllPath).Append(TemplateAssemblyName).Append( " .dll " ).ToString();
            var templateAssemblyFileLastWriteTime 
=  GetTemplateAssemblyFileLastWriteTime(templateDllPath);
            
foreach  (var replaceTo  in  ReplaceToList)
            {
                var sb 
=   new  StringBuilder(TargetAssemblyName).Append( " . " ).Append(replaceTo);
                var asmName 
=  sb.ToString();
                var dllName 
=  sb.Append( " .dll " ).ToString();
                var dllPath 
=  sb.Insert( 0 , DllPath).ToString();
                
if  (NeedGenerate(dllPath, templateAssemblyFileLastWriteTime))
                {
                    var asmBuilder 
=  AppDomain.CurrentDomain.DefineDynamicAssembly( new  AssemblyName(asmName), AssemblyBuilderAccess.RunAndSave, DllPath);
                    var modBuilder 
=  asmBuilder.DefineDynamicModule(asmName, dllName);
                    EmbeddedHbmIntoMoudle(modBuilder, ReplaceFrom, replaceTo);
                    asmBuilder.Save(dllName);
                }
                rv.Add(Assembly.Load(File.ReadAllBytes(dllPath)));
            }
            
return  rv;
        }

 

这个系列到这里就算完结了,虽然没有能够动态的实现创建mapping并加载,但还算是大体达到了客户的要求,也达到了我对自己的一个要求(手动改一处配置,然后重启一下进程就可以完成增加mapping),还算满意吧。

最后说一下,这个系列建立在NH2.1和Spring1.3的基础上。 

转载于:https://www.cnblogs.com/jiaxingseng/archive/2010/08/03/1780851.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值