lunes, 20 de febrero de 2012

NHibernate y Entity Framework

A diferencia del mundo Java, donde Hibernate es un estándar de facto, los ORMs en .Net no gozaron de fama hasta la llegada de Entity Framework.
A pesar de la existencia de NHibernate, Microsoft decidió crear un su propio ORM. Y aunque Entity Framework no gozó de buena salud hasta el lanzamiento de su segunda versión (EF4), su popularidad y su uso ha sido muy superior a un producto mucho más maduro como NHibernate.

Desde mi modesto punto de vista el uso de EF tiene dos peligros:
  • Las herramientas y el Api de EF son muy intuitivos y permiten a un desarrollador sin experiencia trabajar sin preocuparse de lo que sucede por debajo.
  • Un desarrollador familiarizado con (N)Hibernate puede pensar que los dos ORM se rigen por los mismos principios y se comportan de manera similar.
Cualquiera de los dos casos nos lleva a un escenario, donde el desconocimiento el comportamiento del ORM podría terminar desencovando en  una aplicación con pies de barro.

El otro día, comiendo con un compañero que está desarrollando una aplicación con EF, hablamos de mis post sobre (N)Hibernate y las diferencias que había entre los dos ORMs tras leer mis post y comparar con su experiencia en el trabajo. Lo cierto es que me sorprendió lo diferente que se pueden llegar a comportar.

Como ya hemos visto con anterioridad, NHibernate utiliza una session para comunicarse con la base de datos. Se podría decir que el Object Context de Entity Framework es el equivalente a la session de NHibernate, pero en ocaciones las medias verdades son muy peligrosas.

Conexiones a base de datos
Abrir y cerrar conexiones contra la base de datos es algo muy costoso.
La sessión de NHibernate la abre cuando la necesita y la cierra al terminar una session o al hacer commit en una transacción. Desgraciadamente el comportamiento por defecto de Entity Framework dista mucho de ser el mismo, segun la msdn: "By default, the object context manages connections to the database. The object context opens and closes connections as needed. For example, the object context opens connection to execute a query, and then closes the connection when all the result sets have been processed."
Es decir que por defecto se abre una conexión por cada consulta que se lanza. Afortunadamente la mayoría de proveedores de ADO.Net incluyen un pool de conexiones, pero de todas formas, es importante ser consciente que a nivel de ADO.Net EF abre y cierra una conexión por defecto.

Recuperando objetos
En anteriores entradas ya hemos visto como recupera los objetos NHibernate, la session tiene una caché de primer nivel, donde se almacenan todos los objetos, NHibernate decide si cuando le piden un objeto debe ir a buscarlo a la base de datos o no.

La política de recuperación de objetos del Objet Context de EF es diferente. Veamos un poco de código:

    static void Main(string[] args)
    {
        NHibernateEFEntities dbContex = new DBContext ();

        Pelicula pelicula1 = dbContex.Peliculas.First<pelicula>();
        Console.WriteLine(pelicula1.Titulo);

        pelicula1.Titulo = "Modificado";
                        
        Pelicula pelicula2 = dbContex.Peliculas
                                .Where(x => x.IdPelicula == pelicula1.IdPelicula).First<pelicula>();

        Console.WriteLine("¿son el mismo objeto? {0}", pelicula1.Equals(pelicula2));

        Console.WriteLine(pelicula2.Titulo);
            
        Console.ReadLine();
    }
¿Cómo se comportará EF con este código? Supongamos que la primera película que se recupera de la base de datos es Casablanca. En la línea 8 le cambiamos el nombre por "Modificado" y después la recuperamos de la base de datos. Al ejecutar la línea 12 el programa dice que pelicula1 y pelicula2 son iguales y al ejecutar la línea 14, por consola aparece "Modificado". Parece que esta vez EF y NH se comportan igual, pero consultemos el Log de EF:
EntityFramework.NHibernateEFEntities Information: 0 : Executing 1: 
SELECT TOP (1) [c].[IdPelicula] AS [IdPelicula], [c].[Titulo] AS [Titulo], [c].[IdGenero] AS [IdGenero], [c].[Ano] AS [Ano] FROM [dbo].[Peliculas] AS [c]

EntityFramework.NHibernateEFEntities Information: 0 : Finished 1 in 00:00:00.0008783: [DbDataReader(IdPelicula:int, Titulo:varchar, IdGenero:int, Ano:int)]

EntityFramework.NHibernateEFEntities Information: 0 : Executing 2: 
SELECT TOP (1) [Extent1].[IdPelicula] AS [IdPelicula], [Extent1].[Titulo] AS [Titulo], [Extent1].[IdGenero] AS [IdGenero], [Extent1].[Ano] AS [Ano] FROM [dbo].[Peliculas] AS [Extent1] WHERE [Extent1].[IdPelicula] = @p__linq__0 { p__linq__0=[Int32,0,Input]1 }

EntityFramework.NHibernateEFEntities Information: 0 : Finished 2 in 00:00:00.0002692: [DbDataReader(IdPelicula:int, Titulo:varchar, IdGenero:int, Ano:int)]

¿Sorprendido? EF ha ejecutado dos consultas, pero en los resultados parece como si supiera que ya tenía en memoria el objeto que se cargó en la primera query. La respuesta de lo que ha pasado está, como casi siempre, está en la msdn.
EF se comporta de forma muy diferente a NH a la hora de recuperar los objetos en de la base de datos. Lo más importante que debemos saber es que siempre va a realizar la consulta contra la base de datos. Lo que condiciona el comportamiento de EF a la hora de devolver el objeto es el MergeOption de la consulta:
  • AppendOnly: Es la opción por defecto, si una entidad recuperada de una consulta ya existen en el contexto, se devuelve la del contexto y no la recuperada de la base de datos
  • OverwriteChanges: Como su propio nombre indica si el objeto existe en el contexto, se sustituye por el que se ha recuperado de la base de datos.
  • PreserveChanges: si se recupera de la base de datos una entidad cargada previamente pueden ocurrir dos cosas:
    • Si el objeto no había sido modificado, se sustituye por el nuevo
    • Si se había modificado se mantienen los cambios en el objeto asociado al contexto.
  • NoTracking: EF no hace seguimiento del objeto.
En definitiva EF siempre hace las consultas de la base de datos y según el MergeOption se recuperan unos datos u otros, si a esto le sumamos a que por defecto el ObjectContext abre una conexión por cada consulta. Si el programador que usa EF no conoce estas características (ya sea por desconocimiento o suposiciones incorrectas) puede tener problemas de rendimiento en determinados escenario a la hora de poner un proyecto en producción. Recordad, los ORMs no son magia, y siempre hay una base de datos por debajo.

No hay comentarios:

Publicar un comentario