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

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

上回简单的谈了一下思路 

 

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

 

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

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

代码示例如下

代码

        
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做完注入之后调用的一个方法)

代码
        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<stringstring> typedProperties = new  Dictionary<stringstring>();
                
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
<stringstring> typedParamMap = new Dictionary<stringstring>();
                    
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
<stringstring> genericHibernateProperties = new Dictionary<stringstring>();
                
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;
        }

 

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

代码
        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这个方法。

代码
    public class SessionFactory : LocalSessionFactoryObject
    {
        
public IMappingAssemblyGenerator MappingAssemblyGenerator { getset; }

        
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并且保存至磁盘的方法

代码
        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的基础上。 

posted @ 2010-08-03 17:46  jiaxingseng  阅读(775)  评论(0编辑  收藏  举报