Error con Windows Phone & Microsoft Expression Blend

Cuando creo un proyecto Windows Phone en Expression Blend, me aparece un error por demas inexplicable:

"The specified solution configuration "Debug|MCD" is invalid. Please specify a valid solution configuration using the Configuration and Platform properties (e.g. MSBuild.exe Solution.sln /p:Configuration=Debug /p:Platform="Any CPU") or leave those properties blank to use the default solution configuration"

Una solucion mas que escurridiza:

The only solution I found within a few hours scrapes between forums and knowledge base of Microsoft. It is as simple as opening the registry editor (Win + R and then enter Regedit) and there find the path to HKEY_LOCAL_MACHINESystemCurrentControlSetControlSession ManagerEnvironment and once there, delete the PLATFORM variable. After this, close the registry editor and restart the machine.

2012-03-15_222642

Si alguien conoce la causa, sera bienvenida la explicacion.

Saludos

Charla de Paralelismo con ASP.NET MVC

El Dia Jueves 26 de Enero tuve el placer de compartir con los miembros del CUTMS aqui en Cochabamba, una charla acerca de las ventajas de usar paralelismo con ASP.NET MVC.

Aqui les dejo algunas fotografias:

WP_000016 WP_000013
   

Aqui pueden descargarse el codigo de demostracion que utilice en esa presentacion:

https://skydrive.live.com/redir.aspx?cid=e46b27f9dbbd40a1&resid=E46B27F9DBBD40A1!429&parid=E46B27F9DBBD40A1!379&authkey=!ADcs8NahlPigLD0

Un abrazo.

Mejorando la experiencia en ASP.NET MVC (II)

Continuando con la serie de articulos, en esta ocacion les muestro como mejorar el codigo del anterior post, esta vez si aprovechando las caracteristicas de paralelismos y asincronismo de ASP.NET MVC.

En esta ocacion lo que voy a mostra es mismo codigo que accede a Rss Feeds, pero por alguna “extrana” razon voy a necesita consultar varios Rss, por lo tanto tenemos en nuestras manos un conjunto de procesos de alta duracion y que probablemente bloquearan tanto la interface de usuario como las peticiones al servidor web.

El codigo normal que escribiriamos es el siguiente:

1 public class Normal1Controller : Controller 2 { 3 public ActionResult Index2() 4 { 5 RssFeed feed = new RssFeed(); 6 IEnumerable<SyndicationItem> geeks = 7 feed.GetRssFeed("http://geeks.ms/blogs/MainFeed.aspx"); 8 9 IEnumerable<SyndicationItem> betters = 10 feed.GetRssFeed("http://feeds.feedburner.com/CodeBetter"); 11 12 IEnumerable<SyndicationItem> items = geeks.Concat(betters); 13 14 return View(items); 15 } 16 17 }

El codigo que mejora y aprovecha las caracteristicas asincronas del Framework 4.0, es el siguiente:

1 public class Parallel1Controller : AsyncController 2 { 3 public void Index2Async() 4 { 5 AsyncManager.OutstandingOperations.Increment(2); 6 7 RssFeed geekfeed = new RssFeed(); 8 geekfeed.GetRssFeedAsyncCompleted += (s, e) => 9 { 10 AsyncManager.Parameters["geeks"] = e.Items; 11 AsyncManager.OutstandingOperations.Decrement(); 12 }; 13 geekfeed.GetRssFeedAsync("http://geeks.ms/blogs/MainFeed.aspx"); 14 15 RssFeed betterfeed = new RssFeed(); 16 betterfeed.GetRssFeedAsyncCompleted += (s, e) => 17 { 18 AsyncManager.Parameters["betters"] = e.Items; 19 AsyncManager.OutstandingOperations.Decrement(); 20 }; 21 betterfeed.GetRssFeedAsync("http://feeds.feedburner.com/CodeBetter"); 22 } 23 24 public ActionResult Index2Completed(IEnumerable<SyndicationItem> geeks, IEnumerable<SyndicationItem> betters) 25 { 26 return View(geeks.Concat(betters)); 27 } 28 29 }

Es interesante observar como este codigo tan simple puede brindarnos mejores resultados de rendimiento.

Espero que les sea util, un abrazo.

Mejorando la experiencia en ASP.NET MVC (I)

Este es el primer articulo de una serie de 3 articulos, que pretenden mostrar algunos de mis experimentos y resultados con ASP.NET, tocando fundamentalmente la experiencia de respuesta al usuario. Como Uds saben no hay nada peor que un sitio/pagina que tarde demasiado en realizar una tarea y mientras mas rapido se complete la tarea mucho mejor y si la tarea involucra procesos que no podemos controlar pues hay algunas tecnicas que si podemos aplicar, una de ellas es motivo de este primer post de la serie.

Utilizando controladores asincronos (AsynController)

Vamos a pensar en una aplicacion de ejemplo irrisoriamente simple, lo unico que hara es recuperar de internet una lista de todos los post de un blog mediante RSS, para ello normalmente utilizariamos codigo como el siguiente:

1 public class RssFeed 2 { 3 public event EventHandler<RssEventArgs> GetRssFeedAsyncCompleted; 4 5 // Synchronous model methods 6 public IEnumerable<SyndicationItem> GetRssFeed(string uri) 7 { 8 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); 9 HttpWebResponse response = (HttpWebResponse)request.GetResponse(); 10 11 using (XmlReader reader = XmlReader.Create(response.GetResponseStream())) 12 { 13 SyndicationFeed feed = SyndicationFeed.Load(reader); 14 return feed.Items; 15 } 16 } 17 18 // Asynchronous model methods 19 public void GetRssFeedAsync(string uri) 20 { 21 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); 22 request.BeginGetResponse(new AsyncCallback(OnGetRssFeedAsyncCompleted), request); 23 } 24 25 private void OnGetRssFeedAsyncCompleted(IAsyncResult result) 26 { 27 HttpWebRequest request = (HttpWebRequest)result.AsyncState; 28 HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result); 29 30 using (XmlReader reader = XmlReader.Create(response.GetResponseStream())) 31 { 32 SyndicationFeed feed = SyndicationFeed.Load(reader); 33 if (GetRssFeedAsyncCompleted != null) 34 GetRssFeedAsyncCompleted(this, new RssEventArgs { Items = feed.Items }); 35 } 36 } 37 }

 

1 public class NormalController : Controller 2 { 3 public ActionResult Index() 4 { 5 Stopwatch clock = new Stopwatch(); 6 clock.Start(); 7 RssFeed feed = new RssFeed(); 8 IEnumerable<SyndicationItem> items = 9 feed.GetRssFeed("http://geeks.ms/blogs/MainFeed.aspx"); 10 clock.Stop(); 11 TimeMeasure result = new TimeMeasure(); 12 result.TimeElapsed = clock.ElapsedMilliseconds; 13 result.Data = items; 14 return View(result); 15 } 16 17 }

Por favor omitan el codigo de medicion del tiempo relacionado a la clase Stopwatch, en el codigo anterior la linea 9 es la que realiza la accion del cargado del Rss Feed. El resultado de ejecutar este controlador es un tiempo de carga aproximado de 14 segundos:

image

Ahora utilizando un Controlador Asincrono, se necesita el siguiente codigo:

1 public class ParallelController : AsyncController 2 { 3 public void IndexAsync() 4 { 5 Stopwatch clock = new Stopwatch(); 6 clock.Start(); 7 8 AsyncManager.OutstandingOperations.Increment(); 9 10 RssFeed feed = new RssFeed(); 11 feed.GetRssFeedAsyncCompleted += (s, e) => 12 { 13 AsyncManager.Parameters["items"] = e.Items; 14 AsyncManager.Parameters["clock"] = clock; 15 AsyncManager.OutstandingOperations.Decrement(); 16 }; 17 feed.GetRssFeedAsync("http://geeks.ms/blogs/MainFeed.aspx"); 18 } 19 20 public ActionResult IndexCompleted(IEnumerable<SyndicationItem> items, Stopwatch clock) 21 { 22 //ViewData["SyndicationItems"] = items; 23 clock.Stop(); 24 TimeMeasure result = new TimeMeasure(); 25 result.TimeElapsed = clock.ElapsedMilliseconds; 26 result.Data = items; 27 return View(result); 28 } 29 30 }

Como pueden observar en el codigo anterior hay algunos elementos que debemos destacar:

  • El Controlador ahora hereda de AsyncController
  • Existen dos metodos necesarios <Name>Async y su contraparte <Name>Completed, en nuestro caso son IndexAsync e IndexCompleted respectivamente.
  • El paso de parametros se lo realiza mediante una clase especifica llamada AsyncManager.
  • La invocacion simultanea de varios usuarios es controlada, mediante el incremento/decremento de un contador manipulado tambien por AsyncManager.

Finalmente el resultado obtenido es la reduccion del tiempo en 4 segundos, tal como se muestra a continuacion:

image

La reduccion no es significativa a simple vista pero para los que desarrollamos y tenemos que pensar en cuestiones de performance 4 segundos es una diferencia notable Smile Pero los AsyncControllers no han sido pensados para reducir velocidad per-se, probablemente aqui es uno de esos efectos colaterales bien deseados. La utilizacion mas importante de los AsyncControllers es evitar el bloqueo del Servidor Web, en la atencion de multiples peticiones concurrentes, esto traera repito el efecto colateral de que un servidor mas libre puede atender mas usuarios y por ende se siente mucho mas rapido.

Espero que esto les sirva a todos Uds, un abrazo y feliz 2012!!!

La podredumbre del Software, soluciona el problema!

Cada cierto tiempo me gusta volver, una y otra vez, a leer este interesante articulo, que describe la decadencia en la que estan o podrian estar algunos proyectos de software. Tampoco voy a mentir u ocultar que algun proyecto que paso por mis manos (y cayo en otras) ha llegado a “podrirse” irremediablemente. Pero si analizamos el articulo a un nivel mas general, a lo que se refiere es a la capacidad y cualidad de mantenibilidad (no se si esta palabra exista siquiera) que tiene un artefacto de software. En otras palabras una pieza de software tendera a podrirse mas rapidamente mientras menor sea su capacidad de ser mantenible

1756863567_52b429104f

Entonces. el debate que debemos enfocar en cualquier caso (creo yo), NO ES si el software debe podrirse o no, sino cuan RAPIDO debe podrirse. Porque indudablemente en algun momento ese software que tanto nos costo disenar e implementar, terminara por derrumbarse (y aqui debo hacer uso de la mala analogia con las construcciones civiles,) al igual que un edificio terminara por sucumbir a su deterioro/desgaste natural. Y los arquitectos de software, disenadores, programadores debemos asegurarnos que nuestras edificaciones sean resistentes a esa podredumbre inevitable, que trae consigo, nuestro querido amigo “el cambio”.

   
CONSTRUCCION_edificio-alambre

Nuestro software puede y debe ser resitente a factores de cambio “obvios”, es decir a aquellos factores que podemos controlar, como los que son descritos en el articulo referenciado, tales como la viscosidad, rigidez, fragilidad e inmovilidad. Por otra parte, los factores que no podemos controlar, son aquellos que produciran el deterioro “natural” de un proyecto de software. Entre los factores que esta fuera de nuestro control, puedo enumerar: Los cambios en el liderazgo, cambios de vision del proyecto, cambios tecnologicos, cambios en los recursos humanos, etc.

Yo puedo aceptar que un producto de software, se deteriore “o se vaya pudriendo” por aquellos cambios sobre los que yo no tengo control, pero no aceptare nunca que el mismo producto se derrumbe por aquellos factores en los que si pude hacer algo para evitar su caida.

   

Desde mi humilde punto de vista y como aporte a los articulos referenciados puedo decir que, muchos problemas de mantenibilidad de un producto de software se deben a la gran diferencia, entre Resolver un Problema y Solucionar un Problema.

wpa1255l

Aunque a primera vista ambas frases podrian parecer lo mismo, el concepto detras de “Resolver un problema” va ligado a un parche temporal que se aplica para corregir un problema reportado, en cambio el concepto de “Solucionar un problema” esta vinculado a un proceso mas prolongado de razonamiento e implementacion, para corregir el mismo problema. Mientras el que resuelve el problema ve solo el arbol y se avoca a eliminar de la lista de sus tareas ese incomodo elemento llamado bug, lo mas rapido posible y aplicando una correccion inmediatista, que tarde o temprano provocara o iniciara otro punto de deterioro. En su lugar el que soluciona el problema ve el bosque, toma su tiempo para analizar la implicancia de su correccion y elige la alternativa que brinde un balance entre la urgencia por solucionar el problema y la batalla interna por sostener una buena estructura futura que impida el inicio de un punto de deterioro.

   

Es por esto que en los equipos de desarrollo que he tenido el gusto de dirigir, mi sugerencia implicita o explicita en otros casos fue: En desarrollo de software, cuando encuentras un problema, por favor NO resuelvas el problema, SOLUCIONA el problema!

La podredumbre del software, se puede retrasar aplicando soluciones a los problemas que vayan apareciendo y aunque estoy consciente que en algunos escenarios no es posible tomarse mucho tiempo para razonar una solucion, siempre es posible volver hacia atras y remover ese horrendo parche que introdujimos al resolver un problema. Smile

Saludos.

Pasando objetos JSON a los Action Methods en MVC3

Hace un tiempo atras escribi un post relacionado a como evitar los postbacks haciendo uso de ajax y obviamente jquery (Articulo referenciado). De ese momento hace practicamente un anio y hoy con algo mas de experiencia vuelvo a analizar un tema similar.

Como pasar un objeto JSON a un Action Method?

Escenario del problema

En el controlador existe el siguiente metodo (Action Method):

image

 

 

 

 

La clase Person, ridiculamente simple, es como sigue:

image

Las partes mas importantes del codigo html son los 3 botones locos para las pruebas y los manejadores del evento click para cada boton:

image

La idea principal es que cuando se presione uno de los botones HTML, automaticamente se pasa desde javascript hacia el Action Method los objetos que esta esperando dicho metodo y uno de esos parametros es la clase Person que, vendra desde un objeto JSON

Soluciones posibles

Pareceria una tarea trivial, pero en MVC2 experimente dos soluciones:

  1. 1. El JsonBinder que propuse en el articulo mencionado.
  2. 2. Eduar Tomas critico correctamente el uso de un model binder y propuso usar un Value Provider, solucion perfecta para mis necesidades

El codigo para usar el model Binder o el Value Provider es:

image

image

Solucion definitiva

Por la necesidad de migrar mi aplicacion hacia MVC3 me vi en la obligacion de volver a analizar esta solucion y vi que MVC3 ya traer un value provider por lo que la solucion es bastante simple y es la siguiente:

image

image

Lo mas importante a destacar de la solucion es la utilizacion del atributo contentType y de que todos los parametros se colocan en un unico objeto JSON y luego son sometidos al JSON.stringify.

Espero que les resulte util, tambien les dejo adjunta la solucion que utilice para que puedan hacer sus propias comprobaciones.

Abrazos

Internet Explorer o Chrome cambiamos de navegador?

Con todas esas nuevas y excitantes noticias sobre Windows 8, VS 11 e IE10 y otras tantas cosas que trajo el BUILD, yo me pregunto Microsoft esta vez nos escuchara a los desarrolladores y a los usuarios en general, sobre las deficiencias que observamos aun en Internet Explorer. Mis amigos al leer este post tambien se sorprenderan y quien sabe quiza esto es una secuela de los 39 grados de temperatura que tuve ayer, unidos a un episodio de delirios concientes Smile, pero no!!! no solo es eso Smile es ya varios anios de reiterar los puntos que voy a mencionar y que no han sido escuchados por Microsoft, entonces que mejor momento que ahora antes que salga el tan esperado IE10.

Internet Explorer no alcanza muchos sitios y Chrome si.

Esto por muy sorprendente que les parezca a algunos lectores de paises desarrollados es totalmente valido en un pais tercer mundista como el mio donde dificilmente internet es comun denominador y donde las velocidades comercializadas no sobrepasan el 1 Mbps (para los simples mortales) Si uno utiliza IE y lamento decirlo, en muchas ocaciones obtiene

image

Mientras que con Chrome por mas velocidad lenta que uno tenga siempre y repito SIEMPRE obtiene la pagina, tal como aqui:

image

Podriamos echarle la culpa a mil motivos, provedor local, proveedor de la pagina, velocidad en el momento, hora pico, etc etc etc, los que se imaginen, pero bajo toda circunstancia y durante bastante tiempo que vengo probando Chrome (tenia que hacerlo jajaja) y cada vez veo el mismo comportamiento en diferentes parte IE falla, Chrome no. Por que?

Parece que el motor de renderizacion de Chrome sigue y sigue intentando recuperar la pagina, cuando el de IE se agota a los primeros intentos.

Ahora yo me imagino que pasa en paises con altas velocidades de internet? obviamente a todas luces Chrome mantendra mas aun ea ventaja.

Hacer addins o plugins para IE es frustrante y no hay muchos.

Esta paso a ser mi segunda mayor critica, por que hasta ahora es tan complicado hacer un addin para IE, en comparacion con los miles que hay para Firefox y el creciente numero (asombroso) de Chrome?

Que tan complicado es generar una buena documentacion, clara, precisa y que guie paso a paso en esta tarea?

Que tan complicado puede ser crear una API de programacion en C# (perdonen si estoy equivocado, porque hasta ahora no la vi) o bueno minimamente en Javascript?

 

Las caracteristicas de IE respecto a aprovechamiento de tarjetas graficas, renderizacion, inclusion de HTML5 y CSS3 y otras tantas cosas que se vienen en IE10 adicionalmente, pueden quedar opacadas para usuarios frustrados con la eficacia del navegador y claro luego algunos se preguntan sorprendidos el porque del crecimiento de Chrome.

No me malinterpreten, no he venido a hacerle propaganda gratuita a Chrome o Firefox, aun soy un fiel usuario de IE y Bing jajaja Pero, si Microsoft sigue sin escuchar a estas voces pronto Internet Explorer podria ser historia, por muy HTML5 o Metro que tenga. IMHO

La respuesta a la pregunta del titulo de este post es: No, todavia, aun tengo fe que Microsoft escuchara.

Alo? hay alguien ahi?….. si por favor le pasas la voz a Microsoft? Gracias.

Saludos.

Explorando NHibernate 3.0 (II)

Practicamente 6 meses despues del primer post de esta serie, me animo a escribir una nueva entrada y es que NHibernate despierta mis “amores” por lo simple de configurar que es Smile, mas alla del sarcaso, empecemos entonces: Como esta es una serie de entradas relacionadas a NHibernate, aun continuo trabajando sobre el mismo proyecto que tenia anteriormente, con las 2 entidades anteriores y una adicional “Course” tal cual se muestra en el diagrama siguiente:

image

En esta ocacion planeo explorar las diferentes desafios opciones de borrado en cascada que tiene. La idea general es que yo deseo borrar un estudiante y por lo tanto al borrar el estudiante debo borrar sus inscripciones a los diferentes cursos que hay, esto en otras palabras significa eliminar un registro de la tabla Students y muchos registros de la tabla StudentCourse, como es “logico” no debo borrar en cascada ningun registro de la tabla Courses.

Save-Update

La primera aproximacion que tome es tener los archivos de mapeo de la siguiente manera:

1 <?xml version="1.0" encoding="utf-8" ?> 2 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DemoNHibernate.Dal.Entities" namespace="DemoNHibernate.Dal.Entities"> 3 <class name="Student" table="Students"> 4 <id name="StudentId" column="StudentId"> 5 <generator class="guid" /> 6 </id> 7 <property name="StudentName" column="StudentName" /> 8 <bag name="StudentCourses" cascade="save-update" > 9 <key column="StudentId" not-null="false" /> 10 <one-to-many class="StudentCourse" /> 11 </bag> 12 </class> 13 </hibernate-mapping>

1 <?xml version="1.0" encoding="utf-8" ?> 2 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DemoNHibernate.Dal.Entities" namespace="DemoNHibernate.Dal.Entities"> 3 <class name="StudentCourse" table="StudentCourse"> 4 <id name="StudentCourseId" column="StudentCourseId"> 5 <generator class="guid" /> 6 </id> 7 <property name="RegistrationDate" column="RegistrationDate" type="DateTime" /> 8 <many-to-one name="Student" not-null="false" class="Student" cascade="save-update"> 9 <column name="StudentId" not-null="false" /> 10 </many-to-one> 11 <many-to-one name="Course" not-null="true" class="Course" cascade="save-update"> 12 <column name="CourseId" /> 13 </many-to-one> 14 </class> 15 </hibernate-mapping>

1 <?xml version="1.0" encoding="utf-8" ?> 2 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DemoNHibernate.Dal.Entities" namespace="DemoNHibernate.Dal.Entities"> 3 <class name="Course" table="Courses"> 4 <id name="CourseId" column="CourseId"> 5 <generator class="guid" /> 6 </id> 7 <property name="CourseName" column="CourseName" /> 8 <bag name="StudentCourses" inverse="true" cascade="save-update"> 9 <key column="CourseId" /> 10 <one-to-many class="StudentCourse" /> 11 </bag> 12 </class> 13 </hibernate-mapping>

Las configuraciones mas importantes en estos archivos de mapeo son:

1. La definicion de cascade=”save-update” que define que solamente se hara el control del save y de los updates, pero el borrado se lo deja al comportamiento por defecto que tiene NHibernate. Cual es ese comportamiento por defecto? Es colocar o tratar de colocar en NULL todas las llaves foraneas de la tabla StudentCourse, que pertenecen al registro de student que se esta borrando.

2. Otro elemento importante es que no se especifica inverse=”true” en la relacion one-to-many.

1 [TestMethod] 2 public void DeletingParentChildElements() 3 { 4 Execute((context, tx) => 5 { 6 var st = (from s in context.Students 7 select s).FirstOrDefault(); 8 Assert.IsNotNull(st); 9 Session.Delete(st); 10 tx.Commit(); 11 }); 12 }

Al ejecutar la prueba unitaria obtenemos el siguiente log de sentencias SQL que NHibernate ejecuto:

1 NHibernate: select TOP (@p0) student0_.StudentId as StudentId1_, student0_.StudentName as StudentN2_1_ from Students student0_;@p0 = 1 [Type: Int32 (0)] 2 NHibernate: UPDATE StudentCourse SET StudentId = null WHERE StudentId = @p0;@p0 = 812c5ef7-0f1a-4f84-a383-002586213d35 [Type: Guid (0)] 3 NHibernate: DELETE FROM Students WHERE StudentId = @p0;@p0 = 812c5ef7-0f1a-4f84-a383-002586213d35 [Type: Guid (0)]

Observen lo interesante de esta tecnica, es que se han generado dos sentencias para poder borrar el registro padre y sus dependientes, aunque en realidad no se borraron los registros de la tabla hija, sino que estos quedaron con el valor NULL en su llave foranea, en terminos de NHibernate, estos registros quedaron huerfanos. En la captura de pantalla siguiente se ve solo un registro huerfano, pero les aseguro que hay muchos mas.

image

all-delete-orphan

Como segunda opcion he analizado una forma de no dejar esos registros huerfanos en la base de datos. En los foros y documentacion la configuracion de mapeo recomendada es como sigue:

1 <?xml version="1.0" encoding="utf-8" ?> 2 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DemoNHibernate.Dal.Entities" namespace="DemoNHibernate.Dal.Entities"> 3 <class name="Student" table="Students"> 4 <id name="StudentId" column="StudentId"> 5 <generator class="guid" /> 6 </id> 7 <property name="StudentName" column="StudentName" /> 8 <bag name="StudentCourses" inverse="true" cascade="all-delete-orphan" > 9 <key column="StudentId" not-null="false" /> 10 <one-to-many class="StudentCourse" /> 11 </bag> 12 </class> 13 </hibernate-mapping>

1 <?xml version="1.0" encoding="utf-8" ?> 2 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DemoNHibernate.Dal.Entities" namespace="DemoNHibernate.Dal.Entities"> 3 <class name="StudentCourse" table="StudentCourse"> 4 <id name="StudentCourseId" column="StudentCourseId"> 5 <generator class="guid" /> 6 </id> 7 <property name="RegistrationDate" column="RegistrationDate" type="DateTime" /> 8 <many-to-one name="Student" class="Student" cascade="save-update"> 9 <column name="StudentId" /> 10 </many-to-one> 11 <many-to-one name="Course" not-null="true" class="Course" cascade="save-update"> 12 <column name="CourseId" /> 13 </many-to-one> 14 </class> 15 </hibernate-mapping>

En el anterior archivo de mapeo los elementos a los que deben prestar atencion son: la definicion de cascade=”all-delete-orphan” y al elemento que define la llave foranea como not-null=”false”. El primer elemento borrara los registros huerfanos y el segundo sirve para especificar que la llave foranea permitira valores null.

Lo que se optiene como resultado luego de ejecutar la peticion de borrado, es sorprendentemente lo siguiente:

1 NHibernate: select TOP (@p0) student0_.StudentId as StudentId1_, student0_.StudentName as StudentN2_1_ from Students student0_;@p0 = 1 [Type: Int32 (0)] 2 NHibernate: SELECT studentcou0_.StudentId as StudentId1_, studentcou0_.StudentCourseId as StudentC1_1_, studentcou0_.StudentCourseId as StudentC1_2_0_, studentcou0_.RegistrationDate as Registra2_2_0_, studentcou0_.StudentId as StudentId2_0_, studentcou0_.CourseId as CourseId2_0_ FROM StudentCourse studentcou0_ WHERE studentcou0_.StudentId=@p0;@p0 = 62b4407e-1025-4ae2-bd60-0178fcefe0c2 [Type: Guid (0)] 3 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = afe0f28e-0f68-4799-849b-018eb3d61780 [Type: Guid (0)] 4 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 24a8d202-0fc0-4895-92f6-0279715c894b [Type: Guid (0)] 5 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 81b06673-2b99-439f-bc92-0954f1f6a72d [Type: Guid (0)] 6 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 3a8101c1-fb86-4569-b62f-1a4a5f61e076 [Type: Guid (0)] 7 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = e23ead18-d36d-48f0-91b5-1e0f218a9490 [Type: Guid (0)] 8 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 55b89be1-d753-4d13-9b73-2f98e65d94a4 [Type: Guid (0)] 9 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = ad56599b-afd5-42c1-94f2-36d82186e99c [Type: Guid (0)] 10 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 8b0010d5-a3ff-4951-b908-377ac3c8b228 [Type: Guid (0)] 11 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 50f3306e-a376-4662-85a0-3b4dd021313d [Type: Guid (0)] 12 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = c5c4ad94-d764-4fc5-868f-3e90bc849223 [Type: Guid (0)] 13 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = c2835509-49b2-47ae-93f6-3fa2690de749 [Type: Guid (0)] 14 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = d97f98e8-17e2-4cb0-9472-4cd1d18291d4 [Type: Guid (0)] 15 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 45c7abf5-fdfd-4085-862f-4e1221d53cc7 [Type: Guid (0)] 16 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 0cff1150-12cf-4e7e-8bbd-4ff363295506 [Type: Guid (0)] 17 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 617af7f4-0b4b-4fbc-8f73-53bc0d72c0a7 [Type: Guid (0)] 18 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 4fe5434d-6ccf-4850-9ee2-5769812893ee [Type: Guid (0)] 19 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 326791ac-ff46-4859-b875-5bb760597dd6 [Type: Guid (0)] 20 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = a1fb2063-b779-49d0-b520-5e8c1379a4bc [Type: Guid (0)] 21 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = fe9273dd-c0b7-4c5c-8e0e-6252bd24ec01 [Type: Guid (0)] 22 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = ce4e980a-d5e5-45f9-bf48-63b30b1cdb2a [Type: Guid (0)] 23 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = b50dc1dd-7280-479b-a0d0-63dd305d27f3 [Type: Guid (0)] 24 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = cb63acdd-999f-4f89-9de9-67d213cbcb91 [Type: Guid (0)] 25 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = c4c84f6b-a1df-4468-a5c0-71e54da6f32e [Type: Guid (0)] 26 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = bc9f7bbb-6197-4324-a6e1-76eaa044b0fd [Type: Guid (0)] 27 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 48ccb3c5-95f2-4efd-9907-85a551672dec [Type: Guid (0)] 28 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 254cd9ae-179d-4ab7-9147-8d2da2fe50de [Type: Guid (0)] 29 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 370a76db-01e2-4e63-8010-8fdf232066c0 [Type: Guid (0)] 30 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 539d2b31-ec71-474f-911b-915e174ad929 [Type: Guid (0)] 31 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 5bc0ff81-1517-4f28-91e5-91e482b6a8e1 [Type: Guid (0)] 32 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 037d8dfd-4f5a-437f-baf9-9fb852fe1af0 [Type: Guid (0)] 33 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 60f4a979-d5e8-4321-80ae-a02db446011b [Type: Guid (0)] 34 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 839d4d5d-8c37-4962-87e5-a25f048f4308 [Type: Guid (0)] 35 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 0f9f6b77-b181-403d-8c8f-a6f2d5da36d1 [Type: Guid (0)] 36 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 9d469ea0-b078-43d7-8377-b209c6755305 [Type: Guid (0)] 37 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 4ae8090a-d4b0-4022-bc69-b66c558c970a [Type: Guid (0)] 38 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = f066dc0b-12d8-41bf-aee9-be2e571bc80f [Type: Guid (0)] 39 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = a909893f-9395-4172-a327-bf3d3cf91465 [Type: Guid (0)] 40 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 592c002f-9ff2-4891-98e6-c221a6314884 [Type: Guid (0)] 41 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 7349388b-9443-4388-8591-c6bb6740fa8b [Type: Guid (0)] 42 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = d6e9da25-1353-4266-8a36-c6f40833fa5c [Type: Guid (0)] 43 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 877123a5-198b-4889-af38-c81adf0cb9c9 [Type: Guid (0)] 44 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = e188ff07-0022-43a5-a132-d03715ce52d3 [Type: Guid (0)] 45 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 315f4dfa-41be-47a5-8f1a-d9d08af64def [Type: Guid (0)] 46 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = b3c97332-3283-4662-8e76-da4c92a975c8 [Type: Guid (0)] 47 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 86e7cfdc-387e-4111-9f3b-dadbb2b72868 [Type: Guid (0)] 48 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = cf9618ab-6d57-4daf-be3a-dbe69a271853 [Type: Guid (0)] 49 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = d6494f0a-31ed-4a9c-a200-ec5883531d6c [Type: Guid (0)] 50 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 0f7c5562-0ce6-4e56-bdcb-f28effa44b89 [Type: Guid (0)] 51 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = ab76283b-d7c2-4ad6-a633-f369bf52a715 [Type: Guid (0)] 52 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 1248e956-3fc0-4cee-afc4-f861e8ac5bed [Type: Guid (0)] 53 NHibernate: DELETE FROM Students WHERE StudentId = @p0;@p0 = 62b4407e-1025-4ae2-bd60-0178fcefe0c2 [Type: Guid (0)]

Dije sorprendentemente lento, porque primero ha generado una sentencia select para recuperar todos los registros hijos (linea 2: imaginen si fuesen cientos de registros!!!) y luego itera sobre esa lista generando tambien multiples sentencias DELETE para los registros de esa tabla. Realmente ineficiente.

Las malas noticias del uso de all-delete-orphan no terminan ahi, si utilizamos una configuracion de mapeo como la siguiente:

1 <?xml version="1.0" encoding="utf-8" ?> 2 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DemoNHibernate.Dal.Entities" namespace="DemoNHibernate.Dal.Entities"> 3 <class name="Student" table="Students"> 4 <id name="StudentId" column="StudentId"> 5 <generator class="guid" /> 6 </id> 7 <property name="StudentName" column="StudentName" /> 8 <bag name="StudentCourses" cascade="all-delete-orphan" > 9 <key column="StudentId" not-null="false" /> 10 <one-to-many class="StudentCourse" /> 11 </bag> 12 </class> 13 </hibernate-mapping>

1 <?xml version="1.0" encoding="utf-8" ?> 2 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DemoNHibernate.Dal.Entities" namespace="DemoNHibernate.Dal.Entities"> 3 <class name="StudentCourse" table="StudentCourse"> 4 <id name="StudentCourseId" column="StudentCourseId"> 5 <generator class="guid" /> 6 </id> 7 <property name="RegistrationDate" column="RegistrationDate" type="DateTime" /> 8 <many-to-one name="Student" class="Student" cascade="save-update"> 9 <column name="StudentId" /> 10 </many-to-one> 11 <many-to-one name="Course" not-null="true" class="Course" cascade="save-update"> 12 <column name="CourseId" /> 13 </many-to-one> 14 </class> 15 </hibernate-mapping>

El resultado es el siguiente:

1 NHibernate: select TOP (@p0) student0_.StudentId as StudentId1_, student0_.StudentName as StudentN2_1_ from Students student0_;@p0 = 1 [Type: Int32 (0)] 2 NHibernate: SELECT studentcou0_.StudentId as StudentId1_, studentcou0_.StudentCourseId as StudentC1_1_, studentcou0_.StudentCourseId as StudentC1_2_0_, studentcou0_.RegistrationDate as Registra2_2_0_, studentcou0_.StudentId as StudentId2_0_, studentcou0_.CourseId as CourseId2_0_ FROM StudentCourse studentcou0_ WHERE studentcou0_.StudentId=@p0;@p0 = 3df163e6-9e0c-4ea4-bbab-024d8a826478 [Type: Guid (0)] 3 NHibernate: UPDATE StudentCourse SET StudentId = null WHERE StudentId = @p0;@p0 = 3df163e6-9e0c-4ea4-bbab-024d8a826478 [Type: Guid (0)] 4 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = ed22628f-70fb-49da-8e9d-017cb8804153 [Type: Guid (0)] 5 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 823132a9-81dd-4d15-8bc5-0560002b616b [Type: Guid (0)] 6 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = df8a6f0d-e437-4033-adfe-1226b7e6ccc5 [Type: Guid (0)] 7 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = e4e217f2-6035-49b7-93ed-1625a33bebf2 [Type: Guid (0)] 8 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = ff4367c5-e113-408d-afe5-17cde78b098d [Type: Guid (0)] 9 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 76939286-24cb-446c-95a7-19c12c1cacc7 [Type: Guid (0)] 10 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 406e5eab-3ee3-4425-9d41-1c435916cab7 [Type: Guid (0)] 11 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 943ebddb-6ceb-4f53-a0a3-2df9514578ee [Type: Guid (0)] 12 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 48a95446-1b72-4e1a-aaca-311b74788f22 [Type: Guid (0)] 13 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = d5f4b24a-ac78-4538-aed1-3c48c41267ef [Type: Guid (0)] 14 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 4005baf4-fed6-4a6b-8970-3ec1cd8b6e6b [Type: Guid (0)] 15 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = bf5a3bec-4870-4765-a2c0-42f01ac649d9 [Type: Guid (0)] 16 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = c3a18b48-62bc-4aae-86fa-486f041271c2 [Type: Guid (0)] 17 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 87ebe587-f0bc-4ea4-84b0-48db75689bc9 [Type: Guid (0)] 18 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 90cf3e31-689e-45a8-a28e-4ba673dfd411 [Type: Guid (0)] 19 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 0e9ca64c-b451-4113-a445-4c6c060fe0f4 [Type: Guid (0)] 20 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 168b732b-de67-4a6c-9261-5318dae239ae [Type: Guid (0)] 21 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = dc2263c8-44d3-4aa0-9c7e-58d1e13f7764 [Type: Guid (0)] 22 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = b055aa2e-d8c2-4b9d-9c73-58f1aff63e3a [Type: Guid (0)] 23 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 20283918-7c19-48e4-acd1-5d7c680df30a [Type: Guid (0)] 24 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 4d62c9e8-e36e-4c6c-85ed-60dac8218b34 [Type: Guid (0)] 25 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = fec90de0-c22a-4cf1-b47c-63e55b0b34b4 [Type: Guid (0)] 26 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 59a946c9-6a99-4f82-bdc0-64202a9c2eb3 [Type: Guid (0)] 27 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 13c240f1-9808-47cf-ba10-653fb444991d [Type: Guid (0)] 28 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 34c13a9d-0394-4984-85e7-66e2ee9f8f55 [Type: Guid (0)] 29 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 59ac31ed-2636-49b4-a5e1-69f636f5dffa [Type: Guid (0)] 30 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = f44d99fb-3548-4bff-9df7-70c8dddccf9e [Type: Guid (0)] 31 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = b651ee73-b30b-4e0e-a86b-76d8158f408c [Type: Guid (0)] 32 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = c8ac848a-2a1a-4155-9b83-7ba18ed0ab40 [Type: Guid (0)] 33 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = a8a62220-e5d2-44f0-b7cd-7cbc54de1638 [Type: Guid (0)] 34 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 4604521f-484f-4bcc-bc75-80c27070e3e4 [Type: Guid (0)] 35 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 179b5622-66b1-4bf3-bbe6-820f682aad87 [Type: Guid (0)] 36 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = a76f36d2-9d5f-48f5-b9f4-8c1cdf05da7c [Type: Guid (0)] 37 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = db4600e1-3782-4986-8548-99e53fad2e7d [Type: Guid (0)] 38 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = e7e99834-5f65-4f85-9302-a4695ef45141 [Type: Guid (0)] 39 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 28b13fe5-1daf-448c-80f6-a6d8de81eedd [Type: Guid (0)] 40 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = ad44e417-af7c-4db8-9d65-a884b494e0c0 [Type: Guid (0)] 41 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 90815e58-8d09-43df-bee7-af0c484b4a9b [Type: Guid (0)] 42 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 03c92453-75f7-455e-8c7f-ba043c6e4960 [Type: Guid (0)] 43 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = db405dcc-33d6-457f-b534-c1128a6f1646 [Type: Guid (0)] 44 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = ebf07a1c-1de3-4b4c-a231-c2e8a59b0a7d [Type: Guid (0)] 45 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 19ade8a2-132e-42fa-a9e3-c513950ffd11 [Type: Guid (0)] 46 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = fb0eb4d3-aacd-457e-9c6c-cf09aadbf814 [Type: Guid (0)] 47 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = adcb700e-a3a9-4126-a2f2-d742559a1c4b [Type: Guid (0)] 48 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 57c21829-56bc-474a-80d5-e359d4f02a62 [Type: Guid (0)] 49 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = c1960cce-931e-4969-924e-ec295d1da75b [Type: Guid (0)] 50 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = b17205cf-f08f-4601-97fa-ee851e0d8ae9 [Type: Guid (0)] 51 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = eab5b833-a03d-4f72-a75e-eeb29fe7ae3c [Type: Guid (0)] 52 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = c362251d-ce69-4f2e-b716-f6c6a33f7ac0 [Type: Guid (0)] 53 NHibernate: DELETE FROM StudentCourse WHERE StudentCourseId = @p0;@p0 = 9e7e6b92-3115-4312-bbe1-fbac3b2c2574 [Type: Guid (0)] 54 NHibernate: DELETE FROM Students WHERE StudentId = @p0;@p0 = 3df163e6-9e0c-4ea4-bbab-024d8a826478 [Type: Guid (0)]

Lo unico diferente en estos mapeos es la definicion del inverse =»true», lo cual ha generado una sentencia UPDATE adicional que se observa en la linea 3. Ahora esto me hace pensar cuantas personas tendran un mapeo que funciona, borra, pero lo hace cada vez de manera ineficiente?. Con razon mi carino especial a NHibernate.

On Delete = “Cascade”

Finalmente la opcion que yo recomiendo utilizar, aunque cabe decir que en algunos foros no la aconsejan, es utilizar las caracteristicas de borrado en cascada de la base de datos. Esta opcion consiste en definir los archivos de mapeo como siguen:

1 <?xml version="1.0" encoding="utf-8" ?> 2 <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DemoNHibernate.Dal.Entities" namespace="DemoNHibernate.Dal.Entities"> 3 <class name="Student" table="Students"> 4 <id name="StudentId" column="StudentId"> 5 <generator class="guid" /> 6 </id> 7 <property name="StudentName" column="StudentName" /> 8 <bag name="StudentCourses" inverse="true" cascade="save-update" > 9 <key column="StudentId" not-null="false" on-delete="cascade" /> 10 <one-to-many class="StudentCourse" /> 11 </bag> 12 </class> 13 </hibernate-mapping>

En este archivo el punto mas importante es la definicion de on-delete=”cascade” y la especificacion de la relacion inversa con inverse=”true”, si esto ultimo no se coloca, al momento de utilizar la coleccion saldra una excepcion indicando que es necesario especificar este atributo.

A continuacion el resultado de la ejecucion de la peticion de borrado, en esta ocacion como se puede apreciar solo se tiene dos sentencias SQL generadas por NHibernate, una para recuperar el primer registro de la tabla estudiantes y la segunda sentencia para realizar el borrado fisico, no existe ninguna otra sentencia adicional y luego de que se produce el borrado no han quedado registros huerfanos.

1 NHibernate: select TOP (@p0) student0_.StudentId as StudentId1_, student0_.StudentName as StudentN2_1_ from Students student0_;@p0 = 1 [Type: Int32 (0)] 2 NHibernate: DELETE FROM Students WHERE StudentId = @p0;@p0 = 51aa39c1-3929-4970-8401-00e4cc1e20a6 [Type: Guid (0)]

Es necesario advertir que los temas de rendimiento (performance) al momento de borrar son tema de otro articulo, pero probablemente la opcion mas recomendable sigue siendo la tercera opcion, al menos por que no genera sentencias SQL explicitas y confia en la definicion de la base de datos.

Espero que estos “hallazgos” les sea de utilidad.

Saludos.

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.