Tecnocrata

Explorando NHibernate 3.0 (I)

Las personas que me conocen seguramente se sorprenderán por este y los posteriores post que escribiré con relación a NHibernate, bueno todo tiene una explicación y esa para este caso podrían ser varias: Quizá la primera y más importante el “bendito” Karma que me persigue para usar NHibernate desde hace varios proyectos y no me malinterpreten no es que esté en contra de NHibernate pero la verdad es que tiene una curva de aprendizaje bastante empinada y aunque hay mucho soporte por ahí (incluso comercial) pues me mandan a la batalla con prácticamente cero de esa armadura, otra razón puede ser que me he involucrado en proyectos en los que no quieren comprar un EF Provider para Oracle y tampoco quieren esperar a la próxima liberación de este provider “gratuito” por parte de Oracle; sea cual fuese la razón aquí me tienen escribiendo sobre NHibernate.

Como todos mis post, incluso aquellos que pongo en twitter, lo hago como una guía auto-recordatoria en un 70% y por esta razón también lanzo un ligero aviso antes de continuar leyendo. En este post, aquellos que ya conocen NHibernate no encontraran algo nuevo, quizá encuentren algunos errores míos o algo malo que esté haciendo, son libres de criticar y corregirlos, por favor.

También he decidido explorar las nuevas características de NHibernate 3.0 entre ellas probablemente la más esperada es la integración de LINQ to NHibernate (que según dicen, fue reescrito desde cero) veamos:

Entidades

Tengo inicialmente las siguientes entidades, debo mencionar que quizá la relación parezca algo extraña por que debería ser una relacion many-to-many , pero la entidad StudentCourse va a convertirse “pronto” en esa tabla intermedia, por lo que permítanme la libertad de una relación one-to-many

image

Mapeo

La configuración de los archivos de mapeo inicialmente es

 

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" /> 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="all"> 9 <column name="StudentId" /> 10 </many-to-one> 11 </class> 12 </hibernate-mapping>

Pruebas

Como podrán ver el mapeo y las entidades no son la gran cosa, pero aquí veamos cómo se comportan algunas pruebas que quiero hacer, fundamentalmente para borrar entidades hijas a través del update del padre (quizá esté hablando en chino para algunos pero les pido paciencia, al leer el código del UpdatingChildEntities se entenderá un poco más.

Explicare algunos elementos que verán en el código:

  • RandomText, es una clase de Markov que genera aleatoriamente frases/oraciones /parrafos
  • Estoy usando algo métodos anónimos para encapsular la llamada a la inicialización del contexto y de la transacción, el método Execute.
  • Estoy usando el patron TestDataBuilder como lo explique en un anterior post.
  • El código está en un solo archivo por ahora, por simplicidad, en la versión final lo dividiré correctamente
1 namespace DemoNHibernate.Dal.Tests 2 { 3 [TestClass] 4 public class PersistenceTests 5 { 6 private RandomText generator; 7 private ISession session; 8 protected ISession Session 9 { 10 get { return session ?? (session = NHibernateHelper.OpenSession()); } 11 } 12 13 [TestInitialize] 14 public void GenerateData() 15 { 16 generator = new RandomText(); 17 18 Execute((context, tx) => 19 { 20 var st = new StudentTestDataBuilder() 21 { 22 StudentName = generator.Sentance(), 23 StudentCourses = new StudentCoursesTestBuilder().BuildList(3) 24 }.Build(); 25 26 Session.Save(st); 27 tx.Commit(); 28 }); 29 } 30 31 [TestMethod] 32 public void DeletingChildElement() 33 { 34 Execute((context, tx) => 35 { 36 var st = (from s in context.Students 37 select s).FirstOrDefault(); 38 Assert.IsNotNull(st); 39 Assert.IsNotNull(st.StudentCourses); 40 Assert.IsTrue(st.StudentCourses.Count > 0); 41 st.StudentName = "Deleted at "+ DateTime.Now; 42 st.StudentCourses.RemoveAt(0); 43 Session.Update(st); 44 tx.Commit(); 45 }); 46 } 47 48 public void Execute(Action<BaseModelContext, ITransaction> method) 49 { 50 using (var context = new BaseModelContext(Session)) 51 using (var trx = Session.BeginTransaction()) 52 { 53 method(context, trx); 54 } 55 } 56 } 57 58 public class StudentCoursesTestBuilder 59 { 60 public virtual Student Student { get; set; } 61 public virtual DateTime RegistrationDate { get; set; } 62 63 public IList<StudentCourse> BuildList(int quantity) 64 { 65 var result = new List<StudentCourse>(); 66 for (int i = 0; i < quantity; i++) 67 { 68 StudentCourse sc = new StudentCoursesTestBuilder() 69 { 70 RegistrationDate = DateTime.Now 71 }.Build(); 72 result.Add(sc); 73 } 74 return result; 75 } 76 77 public StudentCourse Build() 78 { 79 return new StudentCourse() 80 { 81 RegistrationDate = this.RegistrationDate, 82 Student = this.Student 83 }; 84 } 85 } 86 87 public class StudentTestDataBuilder 88 { 89 public virtual string StudentName { get; set; } 90 public virtual IList<StudentCourse> StudentCourses { get; set; } 91 public Student Build() 92 { 93 return new Student() 94 { 95 StudentName = this.StudentName, 96 StudentCourses = this.StudentCourses 97 }; 98 } 99 } 100 } 101

Resultados

La prueba que me trae aquí es la que se encuentra en la línea 42, donde verán que estoy eliminando un elemento de la colección de StudentCourses, mediante el RemoveAt y luego en la línea 43 hago un update de la entidad padre, esto ocasiona un comportamiento probablemente esperado por muchos de los que ya conocen NHibernate:

  1. La entidad padre se actualiza correctamente
  2. La entidad hija borrada de la colección, NO se borra físicamente de la base de datos, simplemente se coloca un valor NULL en su llave foránea, lo cual lo desvincula a su entidad padre, digamos que hace algún tipo de borrado lógico.
  3. Porque este comportamiento? Se debe a que el la definición del mapeo, en la línea 8, del primer archivo (Student.hbm.xml) se encuentra la definición del borrado en cascada, de la siguiente manera: cascade="save-update", es decir no se ha definido ningún comportamiento especial para el borrado.
  4. Como resultado de algunas pruebas he determinado que el método LastOrDefault() no está soportado aun por Linq to NHibernate 3.0, no es el gran problema, pero aun evidencia que no hay una implementación completa de LINQ, lastima.

    image

    image

    En un siguiente post, mas sobre el comportamiento de borrado de entidades hijas, mediante el update del padre.

    Saludos

    Posted: 21/2/2011 0:51 por Enrique Ortuño | con 4 comment(s)
    Archivado en: ,,
    Comparte este post:

    Comentarios

    Omar del Valle Rodríguez ha opinado:

    Mucha suerte con NH, es verdad q tiene una curva de aprendizaje elevada pero cuando lo aprendes, encontraras pocas preguntas del tipo "cómo hago esto" ;)

    Un consejo q siempre me dieron cuando empezaba con NH pero creo q va con todo ORM q se respete, es pensar siempre las entidades y sus relaciones desde el punto de vista de objetos q es lo q son y no como asociaciones con tablas a la base de datos.

    Uno de los desarrolladores de NH (fabio) simple decía, "vamos a pensar como resolvemos este caso desde el punto de vista de negocio, como se almacene en base de datos ya luego será otra cosa"

    Lo dicho, suerte y ánimo. Te gustará ;)

    # February 21, 2011 7:49 AM

    Manu ha opinado:

    Llevo bastante tiempo usando NHibernate y es excelente. Sí requiere un tiempo de aprendizaje pero no es tan exagerado. Mi recomendación es utilizar alguno de los siguientes libros:

    - NHibernate 2.x Beginners Guide.

    - NHibernate 3.0 Cookbook.

    # February 21, 2011 11:41 AM

    Enrique Ortuño ha opinado:

    Omar y Manu, gracias por los comentarios y los deseos

    COmo dije ya llevo tiempo programando con NHibernate, no es mi primera experiencia, solo que cada vez que empiezo un nuevo proyecto, siempre me encuentro con algo que no sabia y siempre resulta ser algo ridiculamente simple que bien podria haber estado documentado en algun lugar jajajaj en fin pa que renegar si mi karma me manda a programar con NHibernate... jajajaj

    Nuevamente gracias.

    # February 21, 2011 4:42 PM