Mejorando el rendimiento de tu aplicación MVC con Mini Profiler

Posted on Posted in Uncategorized

Entity Framework es una herramienta que facilita el trabajo para nosotros los desarrolladores, nos ayuda a mapear una estructura en una base de datos a clases en la solución o al revés, nos ayuda a crear la estructura en la base de datos usando código, nos facilita las operaciones en la base de datos usando Linq, hace mas rápido el desarrollo, etc. Pero al usar EF en nuestra solución, perdemos de vista que es lo que realmente esta pasando por debajo del agua, y justamente para recuperar el control, tomar buenas decisiones que nos ayuden a evitar fugas de memoria, llamadas dobles, mejorar tus consultas a DB y mejorar el rendimiento de tu aplicación MVC en general, es que uso Mini Profiler.

Que es Mini Profiler

Es una herramienta que nos ayudara a aumentar el rendimiento de nuestra aplicación, no es que al usarla por si sola lo haga, solamente nos mostrara los tiempos de ejecución de una llamada, un método, una parte del código en concreto e incluso nos muestra cuales son las consultas que Entity Framework esta creando por nosotros. De esta forma, podemos darnos cuenta donde esta el cuello de botella, o si en algún lado podemos optimizar alguna rutina o si estamos repitiendo consultas mas de una vez, te podrías llegar a sorprender cuando sepas realmente cuantas llamadas a base de datos estas haciendo.

Puedes ver mas acerca de esta herramienta en GitHub o en su pagina

Instalando los NuGet

Hay que bajar 3 de ellos, básicamente los que aparecen en la imagen:

  • MiniProfiler: es el Core
  • MiniProfiler.EF6: Para ver las consultas a base de datos
  • MiniProfiler.MVC: Nos ayudara a implementarlo en nuestro proyecto MVC, si usas MVC5 te recomiendo que tambien uses este, ya que hay otro pero no pude hacerlo funcionar
NuGet de Mini Profiler a instalar

Iniciando los componentes

Para arrancar MiniProfiler, nos deberemos de agregar las siguientes lineas de código que no tengamos en el archivo Global.asax.cs:

protected void Application_Start()
{
	/*
	...
	Codigo que ya tenias
	...
	*/
	MiniProfilerEF6.Initialize();
}

protected void Application_BeginRequest()
{
	// Solamente se mostraran los datos si estas en el localhost
	// Si quieres verlo en produccion, podrias usar una cookie y verificar si existe, como el codigo comentado
	if (/*Request.Cookies["developer"] != null || */Request.IsLocal)
	{
		// Descomenta la linea de abajo si quieres ver los queries con los parametros que realmente esta mandando
		// Es útil cuando quieres hacer pruebas con ciertos datos, así que mientras no ocupes eso te servira mas comentada
		//MiniProfiler.Settings.SqlFormatter = new StackExchange.Profiling.SqlFormatters.InlineFormatter();
		MiniProfiler.Start();
	}
}

protected void Application_EndRequest()
{
	MiniProfiler.Stop(); // Siempre detenlo cuando termine la llamada
}

Y en Views/Shared/_Layout.cshtml la siguiente linea justo antes de cerrar el body:

		@StackExchange.Profiling.MiniProfiler.RenderIncludes()
	</body>
</html>

Ahora si corres tu solución deberías de poder ver algo así en la esquina izquierda superior:

Ventana del Mini Profiler
Ventana del Mini Profiler

Pero, si no puedes verlo, entonces posiblemente es porque  algunos recursos de Mini Profiler estan regresando un 404, para arreglarlo solamente hay que hacer lo que esta respuesta de Bennett Dill dice, agregar lo siguiente en el web.config:

<system.webServer>
...
  <handlers>
    <add name="MiniProfiler" path="mini-profiler-resources/*" verb="*" type="System.Web.Routing.UrlRoutingModule" resourceType="Unspecified" preCondition="integratedMode" />
  </handlers>
</system.webServer>

Y una vez hecho esto ahora si deberías de poder verlo correctamente.

Mejorando el rendimiento de tu aplicación MVC

Ahora que tenemos funcionando Mini Profiler, podemos hacer uso de sus bondades. Como puedes ver en la imagen de abajo nos indica cuanto tiempo se tardo haciendo que cosa, específicamente quiero que veas la columna que dice SQL, en numeros rojos esta indicando que SE TARDO 20 SEGUNDOS!!!, como puede tardar tanto???

Consulta SQL de 20 segundos
Consulta SQL de 20 segundos

Si hacemos click justamente en esa parte roja, veremos una lista de las consultas que se hicieron a la base de datos, se mostrara algo como la siguiente imagen:

Consultas duplicadas
Consultas duplicadas

Esa parte es super interesante, ya que nos dice el tiempo que se tardo en cada consulta, cuando inicio y la consulta que ejecuto.

Solamente con esa información podemos ver claramente que hay algo mal, exacto, las consultas son consultas duplicadas… y por si no lo habías notado, viene la leyenda de DUPLICATE en rojo.

Porque tengo consultas duplicadas?

Es sencillo de responder, a pesar de que estoy haciendo solamente una consulta para traer una lista de entidades, cada vez que accedo a una propiedad de mi entidad, que es otra entidad (navigation properties Ej. Usuario.Perfil.Nombre), si la propiedad no esta cargada, EF hará una llamada a base de datos para traer la información.

Como evito las consultas duplicadas en Entity Framework?

Es sencillo, solamente tienes que usar la el método .Inlcude en tu consulta de Linq en todas las propiedades de navegación que vayas a usar:

using System.Data.Entity;

db.ChargeAttempts.Include(x => x.Charge) // Incluye la propiedad Charge
                 .OrderByDescending(x => x.RecordDate) // Ordenalo del mas nuevo al mas viejo
                 .Take(300) // Toma solamente los primeros 300 registros

Este código lo que hará, sera hacerle un join a la tabla Charge, ordenar los resultados y solamente traer los primeros 300 registros mas recientes:

Consulta SQL con Join
Consulta SQL con Join

Esto, resulta en solamente una consulta y no N consultas por carga, mejorando nuestro tiempo de respuesta de +20 segundos a solo 0.18 segundos:

Tiempo mejorado en Mini Profiler
Tiempo mejorado en Mini Profiler

Cuidado al insertar y eliminar varios registros

Asi como tenemos varias consultas duplicadas en algunos SELECT que requieren varias tablas, cuando insertamos o eliminamos mas de un registro, Entity Framework esconde un obscuro secreto…. genera una consulta independiente por cada entidad a insertar o eliminar:

Inserts duplicados
Inserts duplicados

Para evitar esto, podrías hacer uso de SQL Bulk copy ya sea usando un DataTable como en ese ejemplo, o usando una lista de objetos y para los delete lo mejor seria escribir la consulta directa:

DELETE FROM Tabla WHERE RoleID = @RoleID

Recuerda parametrizar para evitar SQL Injection.