Disclaimer: This is a personal web page. Contents written here do not represent the position of my employer.

Saturday, December 23, 2006

 

Esos pelacables telemáticos

La expresión "pelacables" la aprendí en mi facultad para designar despectivamente a nuestros compañeros ingenieros de telecomunicación. Desde entonces uso el término a menudo para referirme a ellos, pero siempre desde el sentido del humor, claro está.

Pero aunque me suelen caer bien, el colectivo de los colegios oficiales de ingenieros de telecomunicación parece que se están llevando el gato al agua en sus maniobras de robo de competencias en cuanto a atribuciones oficiales a nivel estatal se refiere.

Reproduzco un comentario de la página IngenierosDePrimera.com:

Estimados colegas:

Soy un Ingenierio Técnico en Informática de Sistemas español. Eso en mi país quiere decir bastante poco. Tanto, que tengo que estar en otro donde me pagan decentemente, no sientiéndome como un inmundo paria, intentando aprender el idioma para quedarme, pues, y lo repito una vez más, la Informática (y demás aspectos) en mi país es vomitiva.

Como estoy un poco harto con los últimos acontecimientos y me sobra esta tarde, voy a contaros un cuento, breve pero intenso, sobre mi país y su desarrollo, o mejor dicho, subdesarrollo tecnológico.

Todos sabemos que nuestro país es peculiar en muchos sentidos, no vamos a descubrir América ahora. Con respecto a la Ingenería Informática se ha cometido una injusticia histórica sin precedente, con dos únicos culpables y su opresión: la Ingenería de Telecomunicaciones y la tendencia española al vivir del otro sin trabajar.

¿Cómo he llegado a estas conclusiones? No es muy difícil. De forma extraña a todos los países de la Tierra, España no tiene una Ingenería en Electrónica de primer ciclo. EEE la llaman por ahí. El que ha salido un poco de su pueblo sabe de qué estoy hablando. En cambio es un segundo ciclo y sólo se puede acceder por medio de (aquí está el truco): ¡la Ingeniería de Telecomunicaciones!. Haciendo un análisis rigurosos se puede comprobar que la Ing. de Telecomunicaciones no deja de ser una Ingeniería en Electrónica, con otro nombre.

Una vez que el desarrollo tecnológico de finales del S.XX seguía su curso, en España la Ing. de Telecomunicación, partiendo de un error de base (y por ello, condenada como estaba en el tiempo) comenzó a adquirir competencias que no le pertenecían, para poder sobrevivir. Hoy día se hacen proyectos de redes neuronales por parte de profesores de Telecomunicaciones ... ¿qué sentido tiene todo esto? Hoy día un Ingeniero en Informática no puede firmar proyectos de Seguridad Informática. ¿Cómo puede ser esto? ¿Un médico acaso hace barcos o dicta leyes? No entiendo por qué un Ingeniero en Electrónica Camuflado (Ing. de Telec) hace mi trabajo y encima, con todo el recochineo del mundo, me coharta, coacciona y me impide a mí, que soy IeI con todas las de la ley, firmar ese tipo de proyectos.

La primera consecuencia de esa incipiente extinción de los IEC's es la Ingeniería Técnica de Telec. en Telemática y su caída inminente. Como todos sabéis, Bolonia le ha leído la cartilla a esta disciplina absurda donde las haya, provocando su desaparición del catálogo. Ahora están nerviosos. Han hecho una plataforma paripé de "SI A LA TELEMÁTICA". Por lo visto no tiene mucho éxito, pues debería llamarse: "SÍ A VIVIR DEL CUENTO". Serán los primeros en desaparecer. Porque hay que preguntarse, ¿qué es la TELEMATICA? Te dirán: "Está claro, un teleco que puede trabajar de informático". Pero ahora sabemos que el concepto teleco no existe, así que sustituyamos por "Ingeniero Electrónico orientado a las Comunicaciones que puede trabajar de informático". Pero, ¿qué es informático?.... No, esa persona está queriendo decir: "Ingeniero Electrónico orientado a las Comunicaciones que puede realizar trabajos de Ingeniería Informática". Eso es hablar con propiedad y decir la verdad. Y además, hacer ver a la gente lo ridícula que era la primera frase. ¿Eres Ingeniero Informático, IEC? No. No me gusta ir diciendo por ahí que soy médico, si no lo soy.

Como resultado de todo este crisol de incongruencias universitarias, se ha cometido una gran injusticia en la aplicación de las directivas en las carreras nuevas. Volvemos a quedar aparte. Somos el único caso en toda Europa. Por eso debemos pararlo antes de que lo aprueben. Es la última oportunidad para darles en los morros a esos corruptos que nos cierran todas las puertas, que no nos dan dignidad y quieren vivir a nuestra costa. Si aprueban la nueva ley, seréis unos parias de por vida. Nunca tendréis derechos reales. Aquí tenéis más información:

http://www.ingenierosdeprimera.com/node/132

¿Pero qué demonios se han creído? ¿Quién se cree esta gente? Si mañana queremos paramos el país. Ni pilotos, ni gaitas en vinagre. Si yo no hay trabajo, no hay datos. Si no hay datos, no hay comercio, no hay sanidad, no hay vuelos ... no hay nada. Sólo el caos. Ahí está lo que queríais oír: tenéis el poder. Usadlo sin complejos.

¿Pero el poder para qué? Para obtener el Colegio Nacional de Ingenieros en Informática y ganar las atribuciones. Sólo con dos estos dos hitos se habrá acabado la pesadilla. Ya no tendrás que hacer más horas extra. No tendrá que trabajar por míseros 800 euros al mes. ¿Os imagináis 200.000 personas afiliadas en la misma causa, la misma asociación? Ahora me dirás que tú no crees en las asociaciones, que si tal, que si cuál ... Bienvenido al mundo real. La vida es dura y como no defiendas lo tuyo no te van a dar nada. La época romántica de la Informática ya terminó. Así que afeitate la perilla, lávate un poco y apoya la causa. Que es TU causa, a ver si te enteras ya. ¿O quieres ser un esclavo toda tu vida?

Una buena piedra angular puede ser ésta: www.ingenierosdeprimera.com. Ni hago la página ni la apoyo. Pero estoy harto como mucha gente y todo esto debe cambiar. ¿Qué importa desde dónde? Y recordar, la unión hace la fuerza.

Que yo sea el último emigrado de la diáspora de IeI de España.

F.I.L. Viena 22/12/2006 --- 2007 Primer año del respeto a la Ingeniería Informática en España


La verdad es que estas declaraciones dan bastante respeto. No me opongo a que un ingeniero de telecomunicación se gane el pan programando o auditando la seguridad de un sistema informático. A lo que me opongo es a que se de por hecho oficialmente que este tipo de tareas no estén atribuidas a un ingeniero informático.

Parece que el comentario se ha convertido en una entrada de la web de Ingenieros de Primera. Una web que trata de centralizar los esfuerzos en pro de los intereses del conjunto de todo tipo de colectivos de profesionales informáticos en España. El punto más importante en la actualidad es la lucha contra el plan de Bolonia cuya implementación española (por parte del Ministerio de Educación y Ciencia) pretende dejar de calificar a los ingenieros en informática como ingenieros y dejarles sin atribuciones oficiales (es decir, dejarnos como ingenieros de segunda).

Actualización 06-ENE-2007: He decidido cambiar el título de la entrada (cambiando "informáticos" por "telemáticos") a cuento de una maravillosa web que he encontrado.

Actualización 14-ABR-2007: Es acojonante que se prolongue más esta situación: para acceder al Cuerpo Superior de Sistemas y Tecnologías de la Información del Estado no se requiere tener ninguna titulación informática, tan sólo ser licenciado, cuando por el contrario para los Cuerpos Superiores o Técnicos análogos de otras ramas de la Ingeniería como Industriales, Agrónomos, Arquitectos, sólo los poseedores de los correspondientes títulos pueden presentarse.

Labels: , ,


Sunday, December 03, 2006

 

Serialización binaria con NHibernate

He empezado a usar NHibernate en varios proyectos y estoy bastante impresionado por su potencia y su fiabilidad. Es un framework de mapeado objeto/relacional para .NET, bastante popular entre los de su gremio. Aunque su última versión es una beta, en mi opinión es bastante estable porque de momento no ha dado ningún tipo de problema. Podíamos haber usado la versión 1.0 estable, pero lo bueno de la versión 1.2 (beta2) es que ya tiene soporte de tipos genéricos y nulables (están migrando completamente a la API 2.0).

Sin embargo, en ciertos escenarios esta utilidad tiene que mejorar:

- Portabilidad: Al usar sólamente lenguaje administrado (cosa de la que me he cerciorado usando MoMA), en teoría debería ser multiplataforma y funcionar con Mono. Sin embargo, en una prueba rápida que he hecho, Mono ha lanzado una excepción, y parece que el bug está en alguna invocación que en concreto usa la librería DynamicProxy (parte del proyecto CastleProject), la cual es usada por NHibernate. Espero poder hacer pronto un bug report. (Recordemos, no obstante, que Mono aún no tiene soporte oficial completo de la versión 2.0 de las librerías de .NET.) [Actualización (1): ya notificado.]

- Soporte de bases de datos. Aunque soporta bastantes motores de base de datos, echo en falta la idea de poder usar DB4O por debajo, lo que le daría bastante rendimiento y facilidad de uso (ya que ni siquiera harían falta los archivos de mapeado en caso de que sólo usasemos bases de datos orientadas a objetos). Es una idea que me surgió y pregunté en alguna ocasión, pero que además mencioné en el blog de un empleado de db4objects, al que le gustó tal posibilidad también. [Actualización (4): También se ha debatido ligeramente el tema en el blog de Ayende Rahien, un crack del equipo de NHibernate.]

- Acotación de queries. Y es que me he encontrado con un escenario extraño en el que NHibernate se ha traído bastante más información de la BDD de la que debería, ya que el programador sólo estaba consultando la propiedad COUNT de una colección. Espero poner pronto también un bug report sobre esto en el JIRA de NHibernate (un programa de gestión de incidencias al estilo Bugzilla). [Actualización (2): reportado.]

- Parseo de los archivos de mappings. También he encontrado casos extraños en los que, por culpa de un error en los archivos de mapeo, NHibernate escupía una excepción de ADO.NET, en lugar de ser más escrupuloso en el parseo de estos archivos de mapeado. Esto ya lo he reportado.

[Elemento añadido en Actualización (6)]
- Interpretación errónea de algunas query's: acabo de mandar este correo a la lista de correo de NHibernate para que me digan si es un bug o no, y por tanto, si tendría que darlo de alta en su JIRA:

Hello.

I have a simple entity like this:

<?xml version="1.0" encoding="utf-8" ?<
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
<class name="Agent" table="Agents">

<id name="Id" column="IdAgent" unsaved-value="0">
<generator class="native" />
</id>
<property name="Login" column="LOGIN" length="20" not-null="false"/>
<many-to-one name="Client" column="IdClient" not-null="false" />
</class>
</hibernate-mapping>

If I create a wrong HQL query like this:

IList aTerminalGroup =
oSession.Session.CreateQuery(
"from Agent WHERE IdClient = " + 1
).List();

Shouldn't NHibernate complain about it because IdClient is a column and not a property? I guess the correct way of writing the query is using "from Agent g WHERE g.Client.Id = " + 1, don't you think? Is this a bug? Should I create a bug in JIRA?

[Elemento añadido en Actualización (7)]
Al final lo he reportado.

- Por último, y básicamente la razón de esta entrada: la serialización de objetos procedentes de las clases de las que hace uso NHibernate, que a continuación paso a explicar en detalle.

En un escenario en el que el propio desarrollo que queremos llevar a cabo es cliente/servidor, ya sea usando Remoting o Servicios Web, se llega a la situación en la que se requiere enviar información sobre los objetos de nuestro dominio, y ¿qué mejor técnica que el envío completo del objeto a la parte interesada en él? Hay gente detractora de esta técnica pero renegar de ella supone duplicar esfuerzos en la codificación para establecer un sistema de paso de mensajes que al final es una copia de las estructuras de clases de nuestro dominio. Por tanto, si optamos por el camino rápido y productivo, se requerirá la serialización de los objetos de nuestro dominio (los que recojemos y tratamos, para después manipularlos mediante NHibernate para persistirlos) para poder transportarlos de un lado a otro, operación ésta que conlleva bastantes problemas pues:

1) Si optamos por una serialización de tipo XML, nos encontraremos finalmente con que es imposible llevarla a cabo, en la mayoría de los casos, con el serializador XML predeterminado de .NET, ya que las referencias recursivas no las trata y por tanto devuelve excepciones al encontrarlas (y tener referencias recursivas en los objetos del dominio es muy común ya que los objetos hijos normalmente tienen referencia también al objeto padre). Quién sabe, quizás un día me dé por escribir un serializador XML que soporte recursividad, pero mientras tanto hay que optar por la serialización binaria ([Actualización (3): debate en la lista de mono sobre serialización XML de objetos cíclicos, la cual al parecer se soluciona con Indigo/WCF] [Actualización (6): El SoapFormatter serializa sin problemas objetos cíclicos en XML, pero ahora mi problema es que no sé cómo hacer para que un WebService use este formatter por defecto, sospecho que la solución es usar Remoting :( ]). Sin embargo siguen existiendo dos problemas adicionales: los proxies y las colecciones lazy, que paso a explicar en los siguientes puntos.

Antes de nada, una pequeña introducción: NHibernate implementa desde varias versiones atrás lo denominado "lazy queries", o bien en español (aunque el término queda bastante peor): "consultas vagas". Sin ellas, NHibernate al recoger un objeto de la BDD y empezar a formarlo, se encontraría que tendría que recorrer bastantes tablas para rellenar los campos procedentes de las claves ajenas. Si esos campos son otras entidades que se encuentran en otras tablas, las cuales a su vez tienen otras claves ajenas, podemos provocar una consulta enorme que, o bien tarde bastante tiempo, o bien se traiga toda la base de datos con una consulta (dependiendo de lo cohesionadas que estén las entidades del dominio entre sí). Por tanto, el resultado de una "consulta vaga" serán entidades vagas o entidades con hijos o colecciones vagos: lazy collections. Las colecciones vagas permiten a NHibernate traer en una consulta sólo los atributos de una tabla, y dejar sus relaciones en modo suspendido de tal forma que si se accede a ellas, es cuando NHibernate solicitará una nueva consulta. Digamos que es una introspección de datos bajo demanda. Por tanto, este tipo de colecciones, si bien no era obligatorio utilizarlas, cada vez uno se da más cuenta de que es completamente necesario, y es por esto por lo que en la versión 1.2 de NHibernate vienen activadas por defecto.

Ahora que todos sabemos lo que son las lazy collections, puedo explicar el problema que tiene la serialización con ellas. El primero es que NHibernate, para poder implementarlas en los objetos de nuestro dominio, monta sobre nuestros interfaces IList/IDictionary unas colecciones internas, de un tipo implementado en las librerías de NHibernate, que contienen una referencia a la sesión abierta en el momento del acceso (por ejemplo el tipo NHibernatePersistenGenericBag). Además, los objetos de los que sólo tiene información sobre su identificador, por ser fruto de una clave ajena, los inserta una especie de proxy que sirve para que sean refrescados cuando se acceda alguna de sus propiedades, para que NHibernate pueda hacer una consulta SQL extra a la tabla de la que procede la clave ajena y rellenar los campos que faltan. Y ahora vamos con los inconvenientes:

2) Una colección IList/IDictionary que por dentro implementa un tipo de NHibernate no podrá ser deserializada por una aplicación que no tenga acceso a las librerías de NHibernate. ¿Acaso esto es un problema? Pues relativamente sí, porque si la parte que deserializa es un cliente de un WebService, ¿por qué iba a requerir las librerías de NHibernate si él no va a tener acceso directo a la base de datos? Además, estas librerías ocupan 1MB más o menos, lo que complica el despliegue de la aplicación (sobre todo en un entorno ClickOnce como en el que me encuentro).

3) Una clase que tiene insertado un proxy no se puede serializar.

Estos dos problemas tienen solución, pero no es trivial. La solución al problema del proxy es usar una rutina interna de NHibernate denominada "Unproxy", de la cual tuve conocimiento al preguntar en las listas de desarrollo de NHibernate y CastleProject (pues recordemos que NHibernate usa una librería de éste último proyecto). El problema de esta rutina es que sólo le quita el proxy a un objeto de nuestro dominio, pero no a sus hijos, por lo que seguimos teniendo otro problema derivado.

La solución al problema de las colecciones es transformarlas en colecciones nativas de .NET (List y Dictionary) en tiempo de ejecución, lo cual se puede hacer no sin antes encontrarse con el problema de que las colecciones aún no recuperadas no podrán convertirse en colecciones normales, por lo tanto necesitaremos un modo de marcar los objetos de nuestro dominio que estén "incompletos".

La conclusión a todo esta maremagnum de problemas y soluciones no es otra que un método que he desarrollado que podríamos bautizar como un "Unproxy recursivo", y que, mediante Reflection, recorre el objeto que queramos serializar para quitarle proxies, convertir colecciones NHibernate en nativas, y marcar objetos incompletos. Voy a exponer el código aquí y lo voy a publicar con licencia LGPL con el ánimo de que así contribuya más gente a mejorarla.

El código dista mucho de ser óptimo y estable, seguro que alguien encuentra pegas con ello. Pero seguro que entre varios podemos mejorarlo y debatir sobre él. A pesar de que es muy reciente, he comprobado su eficiencia y es bastante rápido (además he hecho algunas optimizaciones al acceso dinámico a los métodos, que se pueden notar al ver el uso de la clase RuntimeMethodHandle):


//Copyright 2006. Andres G. Aragoneses
//Code licensed under the LGPL: http://www.gnu.org/licenses/lgpl.html
using System;
using System.Collections.Generic;
using System.Text;

using System.Reflection;

using NHibernate;
using NHibernate.Proxy;

using MyApp.DataModel.DomainModel;
using MyApp.DataModel.Exceptions;

namespace MyApp.DataModel
{
public static class DataLayer
{
private static ISession oSession = null;

private static ISessionFactory oFactory = null;

public static ISession GetNewSession()
{
if (DataLayer.oSession == null)
{
//handlers initialization
fHandlerCreateGenericList = GetMethodHandle("CreateGenericList");
fHandlerCreateGenericDictionary = GetMethodHandle("CreateGenericDictionary");
fHandlerUnproxyList = GetMethodHandle("UnproxyList");
fHandlerUnproxyDictionary = GetMethodHandle("UnproxyDictionary");
fHandlerUnproxyCompletely = GetMethodHandle("UnproxyCompletely");


//NH initialization
NHibernate.Cfg.Configuration oCfg = new NHibernate.Cfg.Configuration();
oCfg.AddAssembly("DataLayer");
oCfg.AddAssembly("DomainModel");
DataLayer.oFactory = oCfg.BuildSessionFactory();
DataLayer.oSession = oFactory.OpenSession();
}
else
{
DataLayer.oSession.Close();
DataLayer.oSession = DataLayer.oFactory.OpenSession();
}
return DataLayer.oSession;
}

private static Dictionary<Type, Dictionary<int, DomainObject>> hUnproxyingOrAlreadyUnproxiedObjects = null;

//these structures are for optimizing reflection (see http://msdn.microsoft.com/msdnmag/issues/05/07/Reflection/default.aspx)
private static RuntimeMethodHandle fHandlerCreateGenericList;
private static RuntimeMethodHandle fHandlerCreateGenericDictionary;
private static RuntimeMethodHandle fHandlerUnproxyList;
private static RuntimeMethodHandle fHandlerUnproxyDictionary;
private static RuntimeMethodHandle fHandlerUnproxyCompletely;

private static RuntimeMethodHandle GetMethodHandle(string sName){
return typeof(DataLayer).GetMethod(sName,
BindingFlags.Static | BindingFlags.NonPublic).MethodHandle;
}

public static T Unproxy<T>(DomainObject maybeProxy, ISession oSession) where T : DomainObject
{
hUnproxyingOrAlreadyUnproxiedObjects = new Dictionary<Type, Dictionary<int, DomainObject>>();
oSession.Close();
return UnproxyCompletely<T>(maybeProxy);
}

private static bool IsProxy(DomainObject oProxy)
{
return (oProxy is INHibernateProxy);
}

private static T UnproxyCompletely<T>(DomainObject oProxy) where T : DomainObject
{
T oNoProxyBaseButMaybeProxyChildren = null;

if ((hUnproxyingOrAlreadyUnproxiedObjects.ContainsKey(typeof(T))
&& (hUnproxyingOrAlreadyUnproxiedObjects[typeof(T)].ContainsKey(oProxy.Id))))
{
return (T)hUnproxyingOrAlreadyUnproxiedObjects[typeof(T)][oProxy.Id];
}

//DELETEME:
//Console.WriteLine(typeof(T).ToString() + oProxy.Id);

if (DataLayer.IsProxy(oProxy))
{
//throw new InvalidOperationException("Cannot unproxy a not proxied object");
LazyInitializer oLazyInitializer =
NHibernateProxyHelper.GetLazyInitializer((INHibernateProxy)oProxy);
try
{
oNoProxyBaseButMaybeProxyChildren = (T)oLazyInitializer.GetImplementation(); //unwrap the object
}
catch (NHibernate.LazyInitializationException)
{
T oNewNeedLoadObject = Activator.CreateInstance<T>();
oNewNeedLoadObject.Id = oProxy.Id;
oNewNeedLoadObject.NeedLoad = true;

if (!hUnproxyingOrAlreadyUnproxiedObjects.ContainsKey(typeof(T)))
{
hUnproxyingOrAlreadyUnproxiedObjects[typeof(T)] = new Dictionary<int, DomainObject>();
}
hUnproxyingOrAlreadyUnproxiedObjects[typeof(T)][oProxy.Id] = oNewNeedLoadObject;
return oNewNeedLoadObject;
}
}
else
{
oNoProxyBaseButMaybeProxyChildren = (T)oProxy;
}

if (!hUnproxyingOrAlreadyUnproxiedObjects.ContainsKey(typeof(T)))
{
hUnproxyingOrAlreadyUnproxiedObjects[typeof(T)] = new Dictionary<int, DomainObject>();
}
hUnproxyingOrAlreadyUnproxiedObjects[typeof(T)][oProxy.Id] = oNoProxyBaseButMaybeProxyChildren;


//convert NHibernateGenericBag<> to List<>
foreach (PropertyInfo oProp in oNoProxyBaseButMaybeProxyChildren.GetType().GetProperties())
{
if ((!oProp.PropertyType.IsInterface) && (!oProp.PropertyType.IsClass))
{
//it is a value type, so skip
continue;
}

object oActualValue;
try
{
oActualValue = oProp.GetValue(oNoProxyBaseButMaybeProxyChildren, null);
}
catch (Exception oException)
{
if ((oException.InnerException != null) &&
(oException.InnerException is NHibernate.LazyInitializationException))
{
//set the list to null (equivalent to IList.NeedLoad == true)
oProp.SetValue(oNoProxyBaseButMaybeProxyChildren, null, null);
continue;
}
else if (oException.InnerException is IrmNeedLoadException)
{
//inform of another exception?
//(check mappings if you have a property in the class that is not in the mapping)
throw;
}
else
{
throw;
}
}

if (oActualValue == null)
{
//it is null, so skip
continue;
}

if ((oProp.PropertyType.IsGenericType))
{
MethodInfo fCreateMethod = null, fUnproxyMethod = null;

if (oProp.PropertyType.GetGenericTypeDefinition() == (typeof(IList<>)))
{
fCreateMethod =
(MethodInfo)MethodInfo.GetMethodFromHandle(fHandlerCreateGenericList);
fUnproxyMethod =
(MethodInfo)MethodInfo.GetMethodFromHandle(fHandlerUnproxyList);
}
else if (oProp.PropertyType.GetGenericTypeDefinition() == (typeof(IDictionary<,>)))
{
fCreateMethod =
(MethodInfo)MethodInfo.GetMethodFromHandle(fHandlerCreateGenericDictionary);
fUnproxyMethod =
(MethodInfo)MethodInfo.GetMethodFromHandle(fHandlerUnproxyDictionary);
}

fCreateMethod = fCreateMethod.MakeGenericMethod(oProp.PropertyType.GetGenericArguments());
fUnproxyMethod = fUnproxyMethod.MakeGenericMethod(oProp.PropertyType.GetGenericArguments());

if (fCreateMethod != null)
{
//NOT NEEDED: as we are doing it in the catch
//oProp.SetValue(oNoProxyBaseButMaybeProxyChildren, null, null);

try
{
object oChangedValue =
fCreateMethod.Invoke(null,
new object[] {
oActualValue
});
oProp.SetValue(
oNoProxyBaseButMaybeProxyChildren,
oChangedValue,
null);

oActualValue = oChangedValue;

try
{
fUnproxyMethod.Invoke(null, new object[] { oActualValue });
}
catch (Exception)//oException)
{
throw;
}
}
catch (Exception oException)
{
if ((oException.InnerException != null) &&
(oException.InnerException is LazyInitializationException))
{
oProp.SetValue(
oNoProxyBaseButMaybeProxyChildren, null, null);
//oNoProxyBaseButMaybeProxyChildren.NeedLoad = true;
}
else
{
throw;
}
}

}
else
{
throw new NotSupportedException("Generic type not supported yet");
}
}
else if (oActualValue is INHibernateProxy)
{
try
{
MethodInfo fMethod =
(MethodInfo)MethodInfo.GetMethodFromHandle(fHandlerUnproxyCompletely);
fMethod = fMethod.MakeGenericMethod(oProp.PropertyType);

oProp.SetValue(
oNoProxyBaseButMaybeProxyChildren,
fMethod.Invoke(null, new object[] { oActualValue }), null);
}
catch (Exception oException)
{
if (!(oException is LazyInitializationException))
{
throw;
}
else
{
T oNewManyToOneObject = Activator.CreateInstance<T>();
oNewManyToOneObject.Id = ((DomainObject)oActualValue).Id;
oNewManyToOneObject.NeedLoad = true;
//set a fallback object with NeedLoad as TRUE, in case of exception
oProp.SetValue(oNoProxyBaseButMaybeProxyChildren,
oNewManyToOneObject, null);
}
}
}
}

return oNoProxyBaseButMaybeProxyChildren;
}

private static List<T> CreateGenericList<T>(IList<T> aList) where T : DomainObject
{
List<T> aNewList = new List<T>(aList);
return aNewList;
}

private static Dictionary<K, V> CreateGenericDictionary<K, V>(IDictionary<K, V> hDict)
where K : DomainObject where V : DomainObject
{
Dictionary<K, V> hNewDict = new Dictionary<K,V>(hDict);
return hNewDict;
}

private static void UnproxyList<T>(List<T> aList) where T : DomainObject
{
for (int i = 0; i < aList.Count; i++)
{
aList[i] = UnproxyCompletely<T>(aList[i]);
}
}

private static void UnproxyDictionary<K,V>(Dictionary<K,V> hDict)
where K : DomainObject where V : DomainObject
{
IList<K> aKeysToRemove = new List<K>();
IDictionary<K, V> aKeysToAdd = new Dictionary<K, V>();
foreach (K oKey in hDict.Keys)
{
aKeysToAdd.Add(UnproxyCompletely<K>(oKey), UnproxyCompletely<V>(hDict[oKey]));
aKeysToRemove.Add(oKey);
}

foreach (K oKey in aKeysToAdd.Keys)
{
hDict.Add(oKey, aKeysToAdd[oKey]);
}

foreach (K oKey in aKeysToRemove)
{
hDict.Remove(oKey);
}
}
}
}


Para que la pieza de código funcione, hay que colocarla bajo una clase denominada DataLayer (o bien modificarlo para usar el nombre que queramos), y además tenemos que marcar nuestros objetos del dominio como clases derivadas de la siguiente superclase DomainObject (ídem):

namespace DataModel.DomainModel
{
[Serializable]
public abstract class DomainObject
{

public abstract int Id
{
get;
set;
}


protected bool bNeedLoad = false;

public virtual bool NeedLoad
{
get { return this.bNeedLoad; }
set { this.bNeedLoad = value; }
}

}
}


Es posible que haya mejores formas de solucionar estos problemas y, estaría encantado de oirlas. Pero mientras no sea así tendré que lidiar con este método que, evidentemente, ralentizará un poquito cada operación de serialización. Es posible que esto sea el nacimiento de algún pequeño proyecto que podría incluso donar al proyecto NHibernate como una especie de plugin. O también es posible que cuando quisiera ofrecerlo, sus programadores se den cuenta de la cosa tan horrible que he hecho, y que me informen de que se podría solucionar con métodos mejores y más eficientes simplemente modificando el core del proyecto NHibernate para proporcionarle ciertas funcionalidades extra en favor de la serialización. No lo sé, lo iremos viendo con el tiempo, pero mientras, ahí queda eso.

P.D.: Aprovecho la entrada para saludar a los lectores de PlanetaCodigo, los cuales en teoría serán capaces de leerme a partir de ahora cuando publique una entrada en mi categoría Programación.

Actualización 14-DIC-2006: Corregido comentario sobre licencia. Corregido bug en el método (comentada una línea). Añadido enlace a bug de Mono encontrado.

Actualización 09-ENE-2007: Añadido enlace al bug de acotación de queries.

Actualización 16-ENE-2007: Añadido enlace a debate sobre serialización XML de objetos cíclicos.

Actualización 29-ENE-2007: Añadido enlace a blog de Ayende Rahien. Interesante conversación en la lista de correo de NHibernate Hispano sobre el tema.

Actualización 17-ABR-2007: Vaya, parece que Ayende Rahien (uno de los desarrolladores de NHibernate) está en desacuerdo con la técnica de compartir un modelo de datos en todo el desarrollo. Tendré que refinar más mi idea y debatirlo abiertamente.

Actualización 20-JUL-2007: Añadido comentario sobre SoapFormatter y un nuevo inconveniente de NHibernate (¿bug?).

Actualización 20-AGO-2007: Al final el inconveniente que encontré en la última actualización de esta entrada lo he reportado como bug.

Actualización 28-SEP-2007: He encontrado otra limitación, que ya exploré en su día y no hay solución elegante posible de momento: HQL recursivo.

Actualización 08-DIC-2007: Parece que la nueva versión de NHibernate (2.0 en fase Alpha) tiene un nuevo parser de HQL que propiciará una mayor mantenibilidad para poder arreglar bugs y crear nuevas funcionalidades más fácilmente, como por ejemplo la que ya he mencionado en la anterior actualización de esta entrada: HQL recursivo.

Actualización 26-DIC-2007: El "Recursive HQL" ya está en JIRA.

Actualización 14-FEB-2008: En un artículo muy interesante de Ayende Rahien se habla de posibles workarounds a la implementación de jerarquías en BDD para evitar la limitación de NHibernate (y de las BDD en general, claro, pues no todas tienen estas capacidades de búsqueda recursiva y por tanto no todas soportarían la nueva funcionalidad que se desarrollare en NHibernate).

Actualización 15-ABR-2008: Al parecer se han corregido ciertos problemas en la rama trunk de NHibernate (la que sera la proxima version 2.0) que prevenian que la gente pudiera compilarlo con Mono: NH-1242, NH-1243, NH-1244 y NH-1245. Ahora es mas facil ser colaborador sin usar Microsoft :)

Labels: , , ,


This page is powered by Blogger. Isn't yours?

Categories

RSS of the category Gnome
RSS of the category Mono
RSS of the category C#
RSS of the category Programming
RSS of the category Mozilla
RSS of the category Web Development
RSS of the category Security
RSS of the category Open Source
RSS of the category Engineering
RSS of the category Misc
RSS of the category Politics

Contact with me:
aaragonesNOSPAMes@gnNOSPAMome.org

Archive
My Photo
Name:
Location: Hong Kong, Hong Kong
Follow me on Twitter