Como acelerar la construccion del NHibernate SessionFactory

Hola, voy a cubrirme un poco antes de que entren a leer completamente el articulo, lo que se logra con la tecnica que mostrare puede estar demas si utilizan algo como NHFluent o ConfORM.

Bueno, empecemos.

La idea de este articulo nace a raiz de un problema que tuve por los tiempos de carga de una aplicacion web construida con NHibernate 2.0, esta aplicacion tiene un conjunto considerable de entidades y fue disenada usando los clasicos archivos de mapeo (.hbm.xml). En este sentido antes de que la aplicacion entrase en su primer ciclo de produccion, percibimos que existian tiempos de carga demasiado elevados la primera vez que se navegaba en ella. Luego de examinar las posibles causas identifique que el mayor responsible era la construccion de SessionFactory y bueno era bastante comprensible por la cantidad de entidades que tenemos y leyendo los articulos que adjunto se puede ver que existe la misma preocupacion en varios lugares:

Me pregunto, sera esta una de las razones de la rapida adopcion de NHFluent? Creo que si.

En cualquier caso, continuando y resumiendo lo que encontraran en los articulos adjuntos, las soluciones que se plantean son:

  1. Crear un unico mega archivo de mapeo.
  2. Serializar y Deserializar la configuracion.
  3. Utilizar la configuracion de manera compilada, el caso de NHFluent o Loquacious o ConfORM.

El utilizar NHFluent no es desafiante a excepcion quiza de aprender una serie de convenciones para mapear por codigo, ahora hasta Entity Framework tiene cosas similares como Code-First. Pero la verdad aun no me nace ni las ganas ni el tiempo para convertir todos mis archivos .hbm.xml a codigo C#, prometo que lo hare y seguramente hare un post al menos para comentar que tal la construccion del SessionFactory, pero por ahora tengo que trabajar con lo que tengo.

He probado el tema de Serializar y Deserializar la configuracion pero mi mayor “pero” a esta tecnica es la escritura/lectura de disco en aplicaciones web, como entenderan en un entorno donde no se controla el hosting (comun en esta aplicacion) existen proveedores que no permiten la escritura directa a disco, entonces eso me desanimo practicamente de entrada.

La opcion que me quedaba y con la que me fue muy bien fue la de combinar (hacer un merge) de todos los archivos de mapeo. Pero se imaginaran que el hacerlo manualmente no suena optimo y mucho menos entretenido.

Aqui surge la solucion que se me ocurrio. Por que no hacer una plantilla T4 que haga la combinacion/merge por mi? y bueno aqui tienen el codigo de la misma.

1 <#@ template language="C#v3.5" debug="false" hostspecific="true" language="C#" #> 2 <#@ output extension=".hbm.xml" #> 3 <#@ assembly name="System.Core.dll" #> 4 <#@ import namespace="System" #> 5 <#@ import namespace="System.IO" #> 6 <#@ import namespace="System.Linq" #> 7 <#@ import namespace="System.Collections.Generic" #> 8 <#@ import namespace="System.Text.RegularExpressions" #> 9 <?xml version="1.0" encoding="utf-8" ?> 10 <# 11 var structure= GetMappingStructure(); 12 13 WriteLine(structure.StartTag); 14 foreach(var c in structure.MappingContent) 15 { 16 WriteLine(c.ClassContent); 17 } 18 foreach(var c in structure.MappingContent) 19 { 20 WriteLine(c.OqlContent); 21 } 22 foreach(var c in structure.MappingContent) 23 { 24 WriteLine(c.SqlContent); 25 } 26 WriteLine(structure.EndTag); 27 #> 28 29 <#+ 30 private MappingStructure GetMappingStructure() 31 { 32 var structure= new MappingStructure(); 33 string[] filePaths = Directory.GetFiles(Host.ResolvePath(""),"*.hbm.xml", SearchOption.AllDirectories); 34 var files = from fp in filePaths 35 where !fp.Contains("Unique.hbm.xml") 36 select fp; 37 var i=0; 38 structure.MappingContent= new List<MappingContent>(); 39 structure.EndTag="</hibernate-mapping>"; 40 foreach (var f in files) 41 { 42 var content=GetFileContent(f); 43 if (i==0) 44 structure.StartTag =GetMappingContent(content,"open"); 45 var inside=GetMappingContent(content,"cnt"); 46 var mapContent= new MappingContent(); 47 mapContent.ClassContent =GetClassContent(inside); 48 mapContent.OqlContent= GetOqlContent(inside); 49 mapContent.SqlContent=GetSqlContent(inside); 50 structure.MappingContent.Add(mapContent); 51 i++; 52 } 53 return structure; 54 } 55 56 public string GetFileContent(string fileName) 57 { 58 StreamReader streamReader = new StreamReader(fileName); 59 string text = streamReader.ReadToEnd(); 60 streamReader.Close(); 61 return text; 62 } 63 64 private string GetMappingContent(string fullContent, string groupName) 65 { 66 return GetContent(fullContent, groupName,"(?<open><hibernate-mapping.[^<>]*>)(?<cnt>.*)(?<close></hibernate-mapping>)"); 67 } 68 69 private string GetContent(string content, string groupName, string pattern) 70 { 71 string resultString = null; 72 try { 73 resultString = Regex.Match(content, pattern, RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.Multiline).Groups[groupName].Value; 74 } catch (ArgumentException ex) { 75 // Bad practice but for this sample... 76 } 77 return resultString; 78 } 79 80 private string GetClassContent(string content) 81 { 82 return GetContent(content, "classes","(?<classes><class.*</class>)"); 83 } 84 85 private string GetOqlContent(string content) 86 { 87 return GetContent(content, "oqls","(?<oqls><query.*</query>)"); 88 } 89 90 private string GetSqlContent(string content) 91 { 92 return GetContent(content, "sqls","(?<sqls><sql-query.*</sql-query>)"); 93 } 94 95 private class MappingContent 96 { 97 public string ClassContent{get;set;} 98 public string OqlContent{get;set;} 99 public string SqlContent{get;set;} 100 } 101 102 private class MappingStructure 103 { 104 public string StartTag{get;set;} 105 public string EndTag{get;set;} 106 public List<MappingContent> MappingContent{get;set;} 107 } 108 #> 109

 

Las condiciones y/o consideraciones que deben seguir/tener presente para usar esta plantilla son:

  1. La plantilla combinara todos los archivos .hbm.xml que esten en la carpeta del proyecto, no importara si estos archivos NO estan en incluidos en el proyecto, asi que cuidado, que en algun caso se me olvido borrar un archivo antiguo y me toco generar un archivo de mapeo inconsistente con el modelo.
  2. Las entidades deben encontrarse bajo un unico y mismo namespace, uffa que huevada diran y bueno tendria que pagarse algo por esto o no? Como la plantilla T4 toma cabecera del primer archivo .hbm.xml que encuentre, se esta asumiendo que todos comparten la misma estructura, repito estan bajo el mismo namespace y el mismo ensamblado. En algun momento hare una plantilla para generar varios archivos dependiendo de la cabecera de los archivos de mapeo.
  3. Como sabemos, los archivos hbm.xml debe ser incluidos en el proyecto como recurso embebido, pero como en este caso tendremos un unico archivo, todos a excepcion del archivo Unique.hbm.xml deben ser removidos de esta caracteristica.

image

Finalmente el template se ejecutara a peticion (Run Custom Tool), aun no necesito que se ejecute antes del proceso de compilacion. Bueno la idea de esto es que no tenga que estar ejecutando algun comando para que el mega-archivo de mapeo se genere, lo ideal seria que este archivo se genere cada vez que se modifica alguno de los otros archivos de mapeo o al menos cuando se solicita compilar el proyecto. Como dije no lo averigue aun, pero si alguien gentilmente quiere pasarme la informacion, bienvenida.

image

Esperando que esto le sirva a alguno de uds, seguramente me servira a mi en mi futura charla sobre NHibernate Smile, me despido cordialmente.

Descargar el codigo:

Saludos.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *