viernes, 12 de noviembre de 2010

Updating a persistent object transparently

En NHibernate, un objeto devuelto medianta búsqueda por identificador, como por ejemplo el método session.Get<T>(int id) o por cualquier otra consulta realizada a través de NHibernate, se asocia al contexto de la session y es lo que llamamos primera cache de NHibernate, first-level cache o session-level cache. En definitiva, una instancia persistente siempre se asocia a una session de NHibernate y tiene un comportamiento transaccional.

using (ISession s = factory.OpenSession())
using (s.BeginTransaction())
{
   var user = s.CreateQuery("from User")
               .SetMaxResults(1)
               .UniqueResult<User>();
   s.Get<User>(user.Id); //query no executed. Instance object exists in session
   s.Transaction.Commit();         
}
Dichas instancias participan en las transacciones y su estado se mantiene sincronizado al final de la transacción, pero también podría ocurrir antes, ya que se puede dar el caso de que NHibernate tenga la necesidad de sincronizar antes de ejecutar una consulta.
Desde luego, NHibernate no modifica todas las filas que tiene almacenadas en ISession al finalizar cada transacción, esto sería una burrada; sino que sólo lo hace de aquellas que detecta que han cambiado. A este mecanismo se le conoce como "automatic dirty checking". Un objeto que ha sido modificado y aun no se ha sincronizado con la base de datos se le considera "dirty".
using (ISession s = factory.OpenSession())
using (s.BeginTransaction())
{
    var users = s.CreateQuery("from User")
                    .SetMaxResults(2)
                    .List<User>();

    User userNoUpdated = users[0];
    User userUpdated = users[1];
    userUpdated.Name = "name-updated";
                    
    s.Transaction.Commit();
}


Este ejemplo genera las siguientes SQL


Observamos varias cosas:

1. Sólo se modifica el objeto que ha cambiado
En el ejemplo, se obtiene de base de datos dos objetos que almacena en la session, pero sólo se modifica uno por lo que NH lo marca como "dirty" para sincronizarlo al final de la transacción.

2. NHibernate hace un update de todas las columnas
NHibernate es consciente de las columnas que se han modificado y se puede configurar (mediante el uso de dynamic-update="true" en los ficheros de mapeo) para que genere los "update" teniendo en cuenta esas columnas, pero la mejora es tan insignificante que no merece la pena usar, ya que cada vez que tenga que modificar ese objeto va a tener que generar esa sentencia para ese caso particular. Esto es muy costoso comparándolo con la alternativa por defecto, donde se ejecuta el "update" con todas las columnas; en este caso, al inicializar la aplicación, NHibernate genera y cachea la sentencia "update" y, simplemente, se dedica a usarla cada vez que quiera guardar el objeto.

3. La modificación del objeto en base de datos es transparente para el desarrollador
Para modificar el objeto (y con esto me refiero a hacer un "update" en base de datos) no he tenido que llamar a "session.Update();". Para mí, esta funcionalidad es de las más importante que tiene NHibernate. Como he dicho anteriormente, NHibernate es conciente de los objetos que tiene en su session y está atento de los cambios que se producen en ellos. Aquel objeto fuera de su ámbito de session (detached object) no lo monitoriza para observar si cambia o no, y es en este caso especifico, donde se le puede dar uso al "session.Update();". Aquí tenéis un enlace a la referencia de NH. En general, es raro hacer uso del "session.Update()" en patrones "session-per-request", ya que lo normal es recuperar el objeto mediante el "session.Get<T>()" y luego, una vez en la session, trabajar con el.

En definitiva, el hecho de obtener los objetos usando la session de NHiberate nos abstrae de la base de datos y provoca un efecto en el cual podemos trabajar con colecciones de objetos del dominio como si estuvieran en memoria.

¿No os suena está frase? Pues es una definición prácticamente calcada del patrón Repository.

sábado, 6 de noviembre de 2010

Batch size and StatelessSession in NHibernate

Hoy vamos a comprobar dos cosas:

Nuestro dominio será el siguiente

    public class CrudTest
    {
        public virtual Guid Id { get; set; }
        public virtual string Description { get; set; }
    }

El fichero de mapeo

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="NHibernateInfo.Console"
                   namespace="NHibernateInfo.Console.Domain">
  <class name="CrudTest" table="CrudTest">
    <id name="Id" column="Id" type="guid" >
      <generator class="assigned" />
    </id>
    <property name="Description"
              length="200"
              not-null="true" />
  </class>
</hibernate-mapping>
Test 1 : Insertamos 20mil objetos sin usar Batch size ni StatelessSession
                Configuration configuration = new Configuration().Configure("NHibernate.config");
                ISessionFactory factory = configuration.BuildSessionFactory();
                
                Stopwatch time = Stopwatch.StartNew();

                using (ISession s = factory.OpenSession())
                using (s.BeginTransaction())
                {
                    foreach (var testObject in CreateTestObjects(20000))
                        s.Save(testObject);
                    
                    s.Transaction.Commit();
                }

                System.Console.WriteLine("Elapsed {0} ms", time.ElapsedMilliseconds);

Para crear los objetos hacemos uso del siguiente metodo

        private static IEnumerable CreateTestObjects(int count)
        {
            for (var i = 0; i < count; i++)
                yield return new CrudTest
                                 {
                                     Id = Guid.NewGuid(),
                                     Description = Guid.NewGuid().ToString()
                                 };
        }
Elapsed 2699 ms

 

Test 2: Activamos el batch size
<property name="adonet.batch_size">100</property>
Elapsed 1600 ms

 

Test 3: Activamos el StatelessSession
                Configuration configuration = new Configuration().Configure("NHibernate.config");
                ISessionFactory factory = configuration.BuildSessionFactory();
                
                Stopwatch time = Stopwatch.StartNew();

                using (IStatelessSession s = factory.OpenStatelessSession())
                using (s.BeginTransaction())
                {
                    foreach (var testObject in CreateTestObjects(20000))
                        s.Insert(testObject);
                    
                    s.Transaction.Commit();
                }

                System.Console.WriteLine("Elapsed {0} ms", time.ElapsedMilliseconds);
Elapsed 1191 ms

 

Conclusiones (resumidas de los posts de Davy Brion y de la documentación de NH)

Hay que poner especial atención al usar la Session para inserciones masivas. Podemos tener problemas de 'OutOfMemoryException' ya que NHibernate cachea todas las instancias en la cache de nivel de session. Una solución propuesta por la documentación de NHibernate es la de ir volcando y liberando memoria cada ciertos objetos creados, en función del parámetro que le hayamos puesto al parámetro ADO batch size.

Asignar un valor adecuado al parámetro de configuración adonet.batch_size. El razonable deberá estar entre 10 y 50 (el 100 del ejemplo está puesto a modo didáctico).

Con la activación del batch_size a 100 conseguimos agrupar las sentencias de 100 en 100. Esto permite reducir los accesos a base de datos de 20.000 a 200, lo que se traduce en una mejora en cuanto al rendimiento.

Esto solo aplica a sentencias "insert", "update" y "delete", siempre y cuando los generadores de identidad no sean de tipo post-insercion. Si usamos generadores de ese tipo, NH necesitará acceder a la base de datos para recuperar el identificador, por lo que esta agrupación no aplicaría para los "insert"; aunque sí sería válido para los "update" y "delete". En la documentación dicen lo siguiente "Note that NHibernate disables insert batching at the ADO level transparently if you use an identiy identifier generator"

Si deseamos agrupar varias querys en una sola llamada a base de datos, podemos usar el soporte MultiQuery de NHibernate, que sirvió de base para las características de Future<T> y FutureValue<T>

Por último, si nos piden que hagamos operaciones de insercción masiva partiendo de un modelo que ya tenemos implementado (infrastructura, mapping file, etc) habría que planterse la opción de hacerlo con IStatelessSession. El hecho de no usar la session de NHibernate provoca que el proceso gestione menos memoria (ya que no almacena valores en la cache de primer nivel ni lanza los eventos asociados) y es una opción bastante considerable a tener en cuenta.

miércoles, 3 de noviembre de 2010

Viewing SQL generated using NHibernate Interceptor

Existe una forma muy cómoda de poder visualizar las consultas que realiza NHibernate mediante el uso de un profiler llamado NHProf. Este profiler, implementado por un "crack" llamado Ayende Rahien, tiene el único inconveniente de que una sola licencia cuesta 200€; y para un particular eso es bastante dinero.

Si todavía no has conseguido que tu empresa te compre la licencia, ahí va mi alternativa (como primera aproximación) mediante el uso de interceptores sobre-escribiendo el método OnPrepareStatement: El único inconveniente es que las consultas no llevan los valores y las visualizaremos de la siguiente forma: Podremos usar esta forma de visualizar las consultas usando httpModules para visualizar que sentencias SQL se están ejecutando cuando cargamos una página. Podéis ver un ejemplo para ASP.NET con WebForm en este enlace, aunque tambien valdría en ASP.NET MVC

martes, 2 de noviembre de 2010

NH 3.0 Lazy Load Properties

En enero de este año, Ayende nos sorprende con una nueva característica implementada en NH3.0 llamada "Lazy Properties"

Referente a esta característica existen los siguientes enlaces:

  • [NH-429] - Lazy load columns
  • [NH-2148] - Not possible to call methods on Proxy for lazy-property

El objetivo consiste en no cargar aquella propiedad del objeto marcada como "lazy" mientras no sea necesario. Se me ocurre algún contexto donde puede ser útil, como por ejemplo, alguna propiedad que almacene una imagen o alguna que guarde textos muy grandes (comentarios, descripciones, etc).


Para probarlo supongamos el siguiente fichero de mapeo donde establecemos la propiedad Name del User con el atributo lazy a true:


Con el siguiente ejemplo podremos comprobar como funciona esta nueva característica. Para ello realizo una consulta obteniendo el primer usuario que encuentre; en esta consulta se puede observar que no obtiene datos para la columna Name. Posteriormente se accede a la propiedad Name del objeto recuperado y es en este momento cuando necesita realizar una segunda consulta, puesto que en la primera no se había traído el dato.
Si no ves correctamente la imagen pulsa sobre ella o pulsa en este enlace.

Posiblemente le daremos bastante uso a esta nueva mejora !!!!

domingo, 31 de octubre de 2010

Enable IntelliSense for NHibernate

Preparando una ejemplo para probar NHibernate 3.0 me he dado cuenta que no tenía habilitado el Intellisense (ni para los ficheros de mapeo ni para la propia configuración). Buscando en blogs me doy cuenta que la solución pasa por copiar los ficheros "nhibernate-mapping.xsd" y "nhibernate-configuration.xsd" a la carpeta correspondiente del Visual Studio. Incluso en la propia referencia de NH lo recomienda:

Tip: to enable IntelliSense for mapping and configuration files, copy the appropriate .xsd files to \Common7\Packages\schemas\xml if you are using Visual Studio .NET 2003, or to\Xml\Schemas for Visual Studio 2005.

Recuerdo leer a Fabio en uno de sus comentarios que él nunca suele hacer eso y repasando el fichero de releasenotes.txt de NH3 me doy cuenta que existe una mejora en la Build 3.0.0.Alpha3 (rev5226) llamada [NH-2321] - Recommended method for xml intellisense

Aquí leo que actualmente la recomendación es la de incluir los ficheros de validación de esquemas como parte de la solución y efectivamente funciona. Así pues, click derecho sobre la solución, add y elegimos "Existing item...". Buscamos los dos ficheros xsd y los seleccionamos con lo que tendremos esto:


Ahora ya tendremos activado el Intellisense





jueves, 21 de octubre de 2010

Activar Quick Launch Bar en Win7

Sé que está escrito en millones de sitios pero así no tengo que buscarlo mas.
1. Click derecho en la barra de tareas y elegimos "New toolbar..."


2. En el campo de texto escribimos

%SystemDrive%\Users\%username%\AppData\Roaming\Microsoft\Internet Explorer\Quick Launch


3. La barra de inicio rápido aparecerá a la derecha de la barra de tareas, para ubicarlo en el lugar deseado es necesario primero desbloquear la barra de tareas desmarcando "Lock the taskbar" y desactivando que muestre el título y el texto.

viernes, 4 de junio de 2010

NStore: Otra tienda virtual web

He tenido problemas donde tenía alojado mi blog, ya que por error eliminé el directorio donde estaba la estructura física del wordpress, por lo que vuelvo a activar este a ver si por fin me animo a escribir mas.

He creado un proyecto en codeplex llamado NStore (http://nstore.codeplex.com) donde trataré de crear una tienda virtual web hecha integramente en ASP.NET MVC 2. El objetivo consistirá en familiarizarse con esta nueva tecnología, así que no me critiquéis por no tener ideas originales, al fin y al cabo hoy en día está casi todo inventado.

De momento solamente tengo implementados dos o tres "user stories":
1. As I [anonymous user] I want [register] So that [log in in the system]
2. As I [admin user authenticated] I want [management product] So that [show a list of products]
3. As I [user authenticated] I want [view a list of product] So that [buy a product]

En un futuro la idea es integrarlo con herramientas de pago:

  • Usar Paypal
  • Usar Authorize.NET basado en Payment Gateway
  • Usar servicios de tipo SWREG donde la gestión del pago queda fuera del dominio de la aplicación.

Habrá que estudiar las tres opciones y elegir alguna de ellas en función del criterio que nos impongamos.