Muchos son los beneficios que se pueden obtener al hacer un uso correcto de la Cache de ASP.NET, pero cierto es que muchos programadores son reticentes a su uso o bien desconocen todas las opciones que ASP.NET nos ofrece con tal de Cachear objetos. Con este artículo pretendo “desmitificar” el uso de la caché de ASP.NET, mostrando ejemplos prácticos de sus diferentes usos. En este primer artículo, nos centraremos en el objeto Cache, mientras que en la segunda parte nos centraremos en la directiva OutputCache.En el desarrollo de aplicaciones web es muy importante evitar, en la medida de lo posible, los roundtrips (o idas y venidas del servidor) siempre y cuando estos sean redundantes, es decir, para obtener la misma información de nuevo. Así mismo, es importante que el tiempo de procesado de una solicitud sea el más bajo posible, ya que así aumentaremos el rendimiento del servidor, permitiendo un incremento del número de usuario concurrentes que nuestra aplicación puede soportar.

Tan importante como evitar roundtrips al servidor, es evitar el tiempo de procesado que se invierte en recuperar información persistente (almacenada en una base de datos o bien en un archivo) de forma repetitiva.

Imaginemos la siguiente situación:

Disponemos de una formulario web (Webform o página web) en el que , al pulsar el botón “Obtener datos” se muestran los datos de una tabla en un GridView. Esta tabla y los datos que contiene se generarán dinámicamente al pulsar el botón. La página se muestra en la siguiente imagen, así como el código utilizado para generar la tabla y su contenido (en esta série de ejemplos no utilizaremos ASP.NET AJAX para la comunicación asíncrona):

Diseño de la página
Diseño de la página. En esta imagen se muestra el diseño de la página con la que se pretende demostrar el uso del objeto Cache. Contiene simplemente una tabla (GridView) y un botón con el que se llena de datos la tabla.

Aquí se muestra el código inicial de la página. De hecho simplemente se muestra el evento click del botón, en el que se crea de forma dinámica una tabla y se rellena de datos con tal de bindarla, posteriormente, al GridView. Este es el código base sobre el que modificaremos y haremos uso del objeto Cache.
Aquí se muestra el código inicial de la página. De hecho simplemente se muestra el evento click del botón, en el que se crea de forma dinámica una tabla y se rellena de datos con tal de bindarla, posteriormente, al GridView. Este es el código base sobre el que modificaremos y haremos uso del objeto Cache.

Bien, si prestamos atención veremos que el contenido de la tabla que se muestra mediante el control GridView es estático (siempre el mismo) sin embargo, cada vez que pulsamos el botón “Obtener datos” invertimos un preciado tiempo de proceso a volver a generar los mismos datos. De hecho, podríamos comprobar el tiempo que se tarda en generar estos datos al mirar en el Trace de nuestra web accediendo a la página trace.axd (cabe recordar que para poder acceder a esta página debemos añadir el nodo <trace enabled=truepageOutput=false/> a nuestro archivo web.config):

Resultado de la página tras pulsar el botón Obtener datos
Resultado de la página tras pulsar el botón Obtener datos

Ahora muestro una captura del Trace para esta página (accediendo a http://localhost:1312/trace.axd)

Se muestra un trace de la página inicial sin hacer uso del objeto Cache.
Se muestra un trace de la página inicial sin hacer uso del objeto Cache.

Evidentemente, la creación de una tabla dinámica con tan pocos datos es muy rápida, pero se puede observar como el tiempo transcurrido entre el primer mensaje en rojo y el segundo es de 0,000404 segundos (un tiempo insignificante). Ahora modificaremos el código mostrado anteriormente para hacer uso del objeto Cache de ASP.NET.

El objeto Cache:

ASP.NET dispone de un objeto llamado Cache, que nos facilitará mucho la inserción y recuperación de objetos de caché (además de la gestión de los mismo como veremos más adelante). Mediante el uso del objeto Cache, podremos almacenar en memoria (durante un tiempo configurarable) cualquier objeto que sea serializable.

El objeto Cache se usa de la misma forma que el objeto ViewState o Session, es decir, mediante un indexador. Este objeto siempre devuelve objetos del tipo Object, por lo que será necesario realizar un cast al tipo de objeto esperado. No se ha comentado anteriormente (y es importante), pero ASP.NET instancia un objeto Cache por cada una de las aplicaciones que maneja (es decir, no es a nivel de usuario sino de aplicación).

De forma esquemática, podemos utilizar el objeto Cache tal que así:

Cache[“Miobjeto”]=miObjeto;

O bien

Tipo miObjeto=(Tipo)Cache[“Miobjeto”];

Ahora bien, para utilizar el objeto Cache, normalmente se siguen tres pasos básicos:

  1. Comprobar si el objeto existe en la Cache
  2. Si existe, lo recuperamos de la cache.
  3. En caso contrario, creamos el objeto (es decir, realizamos la consulta a la base de datos, leemos el archivo o creamos los objetos como en este ejemplo, o lo que sea necesario para generar los datos). Finalmente, añadimos el objeto a Cache para poder utilizarlo posteriormente.

Procedemos a modificar el código anterior siguiendo estos tres pasos:

Aquí se muestra el nuevo código que hace uso de la cache siguiendo los tres pasos previamente definidos.

Aquí se muestra el nuevo código que hace uso de la cache siguiendo los tres pasos previamente definidos.

Y a continuación se muestra una segunda captura del trace para observar que hemos conseguido reducir algo que ya de por sí era de un coste muy bajo:

Se puede observar una reducción del tiempo de proceso invertido en generar la respuesta para esta petición.

Se puede observar una reducción del tiempo de proceso invertido en generar la respuesta para esta petición (marcada con el mensaje "Usamos la cache").

El efecto del uso de la cache es más evidente cuando el proceso de recolección de datos (mediante una consulta a base de datos o leyendo de un archivo) es más lento que el que se muestra como ejemplo, pero creo que este ejemplo es bastante claro y facilita asumir los conceptos.

Actualizando la cache:

Seguramente a estas alturas alguien ya habrá clamado contra el cielo: “¡Pero los datos que utilizo en mi aplicación no son estáticos! ¡La cache no me sirve!”. Pues no estás en lo cierto. Evidentemente ASP.NET ya dispone de los elementos necesarios para gestionar este escenario. Ahora entraremos a describir las CacheDependencies.

Podemos asociar nuestros objetos en Cache con otros elementos que harán que esta entrada sea eliminada, obligando a que la próxima petición que requiera ese objeto/s vuelva a obtenerlos, guardando así una versión actualizada de los mismos en Cache. Esta asociación entre nuestros objetos en Cache y elementos externos se produce a través de las CacheDependecies (o dependencias de Cache).

Escenario:
Imaginemos el siguiente escenario. Tenemos la misma tabla dinámica que hemos definido anteriormente (la tabla generica) pero serializada en un archivo con formato xml. Queremos que nuestro DataTable (el DataTable generica del ejemplo anterior) en Cache se elimine cuando alguien modifique el archivo xml que contiene sus datos. De esta forma, en la próxima petición ASP.NET se verá obligado a obtener los datos leyendo el archivo de nuevo y guardando el DataTable en Cache (así obtenemos los datos actualizados).
Para ello, hemo definido un nuevo botón al ejemplo anterior (botón que serializa el DataTable a un archivo xml, añadíendole un nuevo registro) y hemos modificado el código del botón ObtenerDatos.

Modificamos el método ObtenerDatos para que: 1) Lea la tabla del archivo. 2) Guarde la tabla en Cache con una dependencia al archivo.

Modificamos el método ObtenerDatos para que: 1) Lea la tabla del archivo. 2) Guarde la tabla en Cache con una dependencia al archivo.

En este código, añadiremos los elementos a la Cache mediante el uso del método estático Add (no utilizaremos el indexador). Este método nos permitirá definir la CacheDependency a utilizar. Deberemos especificar, además de la clave y el objeto a serializar (como en el uso con indexador) el tiempo máximo que va a permanecer en Cache el objeto (he definido DateTime.MaxValue para definir un tiempo ilimitado) y un TimeSliding (tiempo sin uso que hace que el objeto sea eliminado de la Cache. He definido que tras 5 días sin ser usado, debe eliminarse el objeto de Cache). Podeis obtener toda la información a cerca de este método aquí.

Ahora muestro el código del método ModificarArchivoXml, el cual añade un nuevo registro a la tabla y la guarda en el archivo xml, cancelando asi el objeto en Cache (por la dependencia que añadimos antes).

Método que añade un nuevo elemento a la tabla Generica y la serializa al archivo xml.

Método que añade un nuevo elemento a la tabla Generica y la serializa al archivo xml.

Ahora, el diseño de nuestra página es este:

Nuevo diseño de la interfaz gráfica de la página.

Nuevo diseño de la interfaz gráfica de la página.

Ahora pasamos a ejecutar la página. Mostraré capturas del Trace para que pueda comprobarse tanto el uso de la cache, como de las dependencias que hemos declarado asi como para comprobar cómo el hecho de guardar los datos en un archivo implica un mayor tiempo de procesado y cómo el uso de la Cache lo reduce drásticament.

Primero, pulsamos el botón ObtenerDatos para leer los datos del archivo:

La primera petición necesita leer los datos del archivo, lo que consume mayor tiempo de proceso que en el ejemplo anterior.

La primera petición necesita leer los datos del archivo, lo que consume mayor tiempo de proceso que en el ejemplo anterior.

Observad que la petición ha tardado 0,005340 segundos en procesarse. Ahora  pulsamos el botón de nuevo para obtener los datos de Cache.

Ahora obtenemos los datos de la cache. Observar la reducción de tiempo.

Ahora obtenemos los datos de la cache. Observad la reducción de tiempo.

Al pasar por Cache, la petición ha tardado 0.000803 (un 150% más rápido). Ahora pulsamos el botón ModificarArchivoXml:

Puede verse cómo pasamos por el método que añade una nueva fila a la tabla y la serializa al archivo xml.

Puede verse cómo pasamos por el método que añade una nueva fila a la tabla y la serializa al archivo xml.

Por último, volvemos a pulsar el botón ObtenerDatos. Ahora podremos ver como vuelve a leer los datos del archivo (nosotros en el código en ningún momento hemos cancelado la Cache o eliminado el objeto de la misma. Lo único que hemos hecho es modificar el archivo xml y, mediante la dependencia, la Cache a borrado el objeto, con lo que obliga al método a volver a leer el archivo para deserializar la tabla).

Hemos tenido que volver a leer el archivo, ya que la cache había caducado por la dependencia.

Hemos tenido que volver a leer el archivo, ya que la cache había caducado por la dependencia.

Creando dependencias con tablas de bases de datos

Una vez llegados a este punto, seguro que alguien ha vuelto a clamar al cielo: “¡Pero mis datos no son ni estáticos ni los tengo en un archivo, los tengo en una base de datos! La caché no me sirve”. Pues vuelves a equivocarte. Con la llegada al mundo del framework 2.0,  el equipo de desarrollo de ASP.NET solucionó este problema. Ahora podemos crear dependencias con tablas de base de datos, de forma que mientras la tabla siga inalterada, el objeto permanecerá en Cache, pero en el momento en que esa tabla sea modificada, entonces la Cache para el objeto quedará caducada, y la siguiente petición deberá volver a acceder a la base de datos para actualizar el objeto en Cache.
Para ello haremos uso del objeto SqlCacheDependency. Este objeto, al igual que el objeto CacheDependency hacía con un archivo, nos permitirá crear una dependencia entre un objeto en Cache y una tabla en una base de datos concreta.

Lo primero que debemos hacer es añadir a nuestro archivo web.config, la siguiente entrada para:

  1. Habilitar las dependencias de Cache con Sql
  2. Especificar la cadena de conexión (o las cadenas de conexión si hay varias) con las que se comprobará la modificación de la tabla a supervisar.

El código quedaría tal que así:

Muestra el código xml necesario para activar las dependencias de Cache a SQL

Muestra el código xml necesario para activar las dependencias de Cache a SQL

Ahora, modificaremos el código del método ObtenerDatos para que :

  1. Realice la consulta a la base de datos mediante LinQ (no es requisito ya que puede hacerse mediante cualquier otro método de ADO.NET).
  2. Cree la dependencia entre la base de datos y la Cache.

Aquí está el código necesario para hacerlo:

Código necesario para crear la dependencia entre Cache y sql.

Código necesario para crear la dependencia entre Cache y sql.

Finalmente, muestro el resultado de una consulta. Los tiempos de ejecución son análogos a los del ejemplo anterior, por lo que omito colocar las capturas del Trace.

Resultado de la consulta de Artistas.

Resultado de la consulta de Artistas.

Y eso es todo. Espero haberos ayudado a desmitificar el tema de Cache en ASP.NET. Como podeis comprobar es muy simple de utilizar y nos aporta grandes beneficios. Cabe recordar que no hay que confundir el uso del objeto Cache con el uso de la directiva OutputCache, que trato en otro artículo distinto. Pese que los dos hablan de Cachear datos, uno se refiere a Cachear ciertas partes de una página mientras que el otro se refiere a Cachear todo el resultado de una petición.

Como siempre, espero vuestro comentarios! Gracias!😉