Almacenar datos en caché al iniciar la aplicación (C#)

por Scott Mitchell

Descargar PDF

En cualquier aplicación Web, algunos datos se utilizarán con frecuencia y otros con poca. Podemos mejorar el rendimiento de nuestra aplicación de ASP.NET cargando con antelación los datos usados con frecuencia, una técnica conocida como almacenamiento en caché. En este tutorial se muestra un enfoque para la carga proactiva, que consiste en cargar datos en la memoria caché durante el inicio de la aplicación.

Introducción

Los dos tutoriales anteriores examinaron los datos de almacenamiento en caché en las capas de presentación y almacenamiento en caché. En Almacenamiento en caché de datos con el ObjectDataSource, hemos examinado el uso de las características de almacenamiento en caché de ObjectDataSource para almacenar en caché los datos en la capa de presentación. Almacenamiento en caché de datos en la arquitectura examinó el almacenamiento en caché en una nueva capa de almacenamiento en caché independiente. Ambos tutoriales usaban la carga reactiva en el trabajo con la caché de datos. Con la carga reactiva, cada vez que se solicitan los datos, el sistema comprueba primero si está en la memoria caché. Si no es así, toma los datos del origen de origen, como la base de datos y, a continuación, lo almacena en la memoria caché. La principal ventaja de la carga reactiva es su facilidad de implementación. Una de sus desventajas es su rendimiento desigual en todas las solicitudes. Imagine una página que usa la capa de almacenamiento en caché del tutorial anterior para mostrar información del producto. Cuando esta página se visita por primera vez, o se visita por primera vez después de que los datos almacenados en caché hayan sido desalojados debido a restricciones de memoria o a que se haya alcanzado la caducidad especificada, los datos deben recuperarse de la base de datos. Por lo tanto, estas solicitudes de usuarios tardarán más tiempo que las solicitudes de los usuarios que la memoria caché puede atender.

La carga proactiva proporciona una estrategia alternativa de administración de caché que suaviza el rendimiento entre las solicitudes cargando los datos almacenados en caché antes de que sea necesario. Normalmente, la carga proactiva usa algún proceso que comprueba periódicamente o se notifica cuando se ha producido una actualización de los datos subyacentes. A continuación, este proceso actualiza la memoria caché para mantenerla actualizada. La carga proactiva es especialmente útil si los datos subyacentes proceden de una conexión de base de datos lenta, un servicio web o algún otro origen de datos especialmente lento. Pero este enfoque para la carga proactiva es más difícil de implementar, ya que requiere crear, administrar e implementar un proceso para comprobar los cambios y actualizar la memoria caché.

Otro tipo de carga proactiva y el tipo que exploraremos en este tutorial es cargar datos en la memoria caché en el inicio de la aplicación. Este enfoque es especialmente útil para almacenar en caché datos estáticos, como los registros de las tablas de búsqueda de base de datos.

Nota:

Para obtener una visión más detallada de las diferencias entre la carga proactiva y reactiva, así como listas de ventajas, desventajas e recomendaciones de implementación, consulte la sección Administración del contenido de una memoria caché de la Guía de arquitectura de almacenamiento en caché de para aplicaciones de .NET Framework.

Paso 1: determinar qué datos almacenar en caché en el inicio de la aplicación

Los ejemplos de almacenamiento en caché que usan la carga reactiva que hemos examinado en los dos tutoriales anteriores funcionan bien con datos que pueden cambiar periódicamente y que no tardan exorbitantemente en generarse. Pero si los datos almacenados en caché nunca cambian, la expiración utilizada por la carga reactiva es superflua. Del mismo modo, si los datos que se almacenan en caché tardan mucho tiempo en generarse, los usuarios cuyas solicitudes encuentran la memoria caché vacía tendrán que soportar una espera larga mientras se recuperan los datos subyacentes. Considere la posibilidad de almacenar en caché datos estáticos y datos que tardan mucho tiempo en generarse al iniciar la aplicación.

Aunque las bases de datos tienen muchos valores dinámicos y que cambian con frecuencia, la mayoría también tienen una cantidad razonable de datos estáticos. Por ejemplo, prácticamente todos los modelos de datos tienen una o varias columnas que contienen un valor determinado de un conjunto fijo de opciones. Una tabla de base de datos Patients puede tener una columna PrimaryLanguage, cuyo conjunto de valores podría ser inglés, español, francés, ruso, japonés, etc. A menudo, estos tipos de columnas se implementan mediante tablas de búsqueda. En lugar de almacenar la cadena inglés o francés en la tabla Patients, se crea una segunda tabla que tiene, normalmente, dos columnas: un identificador único y una descripción de cadena, con un registro para cada valor posible. La columna PrimaryLanguage de la tabla Patients almacena el identificador único correspondiente en la tabla de búsqueda. En la ilustración 1, el paciente John Doe es inglés, mientras que Ed Johnson es el ruso.

The Languages Table is a Lookup Table Used by the Patients Table

Ilustración 1: la tabla de Languages es una tabla de búsqueda usada por la tabla de Patients

La interfaz de usuario para editar o crear un nuevo paciente incluiría una lista desplegable de idiomas permitidos rellenados por los registros de la tabla Languages. Sin almacenamiento en caché, cada vez que se visita esta interfaz, el sistema debe consultar la tabla Languages. Esto es inútil e innecesario, ya que los valores de la tabla de búsqueda cambian con muy poca frecuencia, si es que cambian alguna vez.

Podríamos almacenar en caché los datos Languages mediante las mismas técnicas de carga reactivas que se examinaron en los tutoriales anteriores. Sin embargo, la carga reactiva usa una expiración basada en el tiempo, que no es necesaria para los datos de la tabla de búsqueda estática. Aunque el almacenamiento en caché mediante carga reactiva sería mejor que no utilizarlo en absoluto, lo mejor sería cargar proactivamente los datos de la tabla de búsqueda en la caché al iniciar la aplicación.

En este tutorial veremos cómo almacenar en caché los datos de la tabla de búsqueda y otra información estática.

Paso 2: examinar las distintas formas de almacenar en caché los datos

La información se puede almacenar en caché mediante programación en una aplicación de ASP.NET mediante diversos enfoques. Ya hemos visto cómo usar la caché de datos en tutoriales anteriores. Como alternativa, los objetos se pueden almacenar en caché mediante programación mediante miembros estáticos o estado de aplicación.

Al trabajar con una clase, normalmente se debe crear una instancia de la clase antes de que se pueda acceder a sus miembros. Por ejemplo, para invocar un método desde una de las clases de nuestra capa lógica de negocios, primero debemos crear una instancia de la clase:

ProductsBLL productsAPI = new ProductsBLL();
productsAPI.SomeMethod();
productsAPI.SomeProperty = "Hello, World!";

Para poder invocar SomeMethod o trabajar con SomeProperty, primero debemos crear una instancia de la clase mediante la palabra clave new. SomeMethod y SomeProperty están asociados a una instancia determinada. La duración de estos miembros está vinculada a la duración de su objeto asociado. Los miembros estáticos, por otro lado, son variables, propiedades y métodos que se comparten entre todas las instancias de la clase y, en consecuencia, tienen un tiempo de vida tan largo como la clase. Los miembros estáticos se denotan mediante la palabra clave static.

Además de los miembros estáticos, los datos se pueden almacenar en caché mediante el estado de la aplicación. Cada aplicación ASP.NET mantiene una colección name/value que se comparte entre todos los usuarios y páginas de la aplicación. Se puede acceder a esta colección utilizando la propiedad HttpContext de la clase Application, y utilizarla desde una clase de código subyacente de una página ASP.NET de la siguiente manera:

Application["key"] = value;
object value = Application["key"];

La caché de datos proporciona una API mucho más completa para el almacenamiento en caché de datos, lo que proporciona mecanismos para expiraciones basadas en tiempo y dependencias, prioridades de elementos de caché, etc. Con los miembros estáticos y el estado de la aplicación, el desarrollador de páginas debe agregar manualmente estas características. Sin embargo, al almacenar en caché los datos en el inicio de la aplicación durante la vigencia de la aplicación, las ventajas de la memoria caché de datos son irrelevantes. En este tutorial veremos el código que usa las tres técnicas para el almacenamiento en caché de datos estáticos.

Paso 3: almacenamiento en caché de los datos de tabla Suppliers

Las tablas de base de datos Northwind implementadas hasta la fecha no incluyen ninguna tabla de búsqueda tradicional. Las cuatro DataTables implementadas en nuestra DAL todas las tablas del modelo cuyos valores no son estáticos. En lugar de dedicar tiempo a agregar una nueva DataTable a la DAL y, a continuación, a una nueva clase y métodos al BLL, para este tutorial vamos a fingir simplemente que los datos de la tabla Suppliers son estáticos. Por lo tanto, podríamos almacenar en caché estos datos en el inicio de la aplicación.

Para empezar, cree una nueva clase denominada StaticCache.cs en la carpeta CL.

Create the StaticCache.cs Class in the CL Folder

Ilustración 2: crear la clase StaticCache.cs en la carpeta CL

Es necesario agregar un método que cargue los datos al iniciarse en el almacén de caché adecuado, así como métodos que devuelvan datos de esta memoria caché.

[System.ComponentModel.DataObject]
public class StaticCache
{
    private static Northwind.SuppliersDataTable suppliers = null;
    public static void LoadStaticCache()
    {
        // Get suppliers - cache using a static member variable
        SuppliersBLL suppliersBLL = new SuppliersBLL();
        suppliers = suppliersBLL.GetSuppliers();
    }
    [DataObjectMethodAttribute(DataObjectMethodType.Select, true)]
    public static Northwind.SuppliersDataTable GetSuppliers()
    {
        return suppliers;
    }
}

El código anterior usa una variable de miembro estática, suppliers, para contener los resultados del método GetSuppliers() de la clase SuppliersBLL, al que se llama desde el método LoadStaticCache(). El método LoadStaticCache() está diseñado para llamarse durante el inicio de la aplicación. Una vez cargados estos datos en el inicio de la aplicación, cualquier página que necesite trabajar con los datos del proveedor puede llamar al método GetSuppliers() de la clase StaticCache. Por lo tanto, la llamada a la base de datos para obtener los proveedores solo se produce una vez, al iniciar la aplicación.

En lugar de usar una variable de miembro estática como almacén de caché, podríamos haber usado alternativamente el estado de la aplicación o la caché de datos. En el siguiente código se muestra la clase retocada para usar el estado de la aplicación:

[System.ComponentModel.DataObject]
public class StaticCache
{
    public static void LoadStaticCache()
    {
        // Get suppliers - cache using application state
        SuppliersBLL suppliersBLL = new SuppliersBLL();
        HttpContext.Current.Application["key"] = suppliersBLL.GetSuppliers();
    }
    [DataObjectMethodAttribute(DataObjectMethodType.Select, true)]
    public static Northwind.SuppliersDataTable GetSuppliers()
    {
        return HttpContext.Current.Application["key"] as Northwind.SuppliersDataTable;
    }
}

En LoadStaticCache(), la información del proveedor se almacena en la clave variable de aplicación. Se devuelve como el tipo adecuado (Northwind.SuppliersDataTable) de GetSuppliers(). Aunque se puede tener acceso al estado de la aplicación en las clases de código subyacente de páginas ASP.NET mediante Application["key"], en la arquitectura debemos usar HttpContext.Current.Application["key"] para obtener el HttpContext actual.

Del mismo modo, la caché de datos se puede usar como almacén de caché, como se muestra en el siguiente código:

[System.ComponentModel.DataObject]
public class StaticCache
{
    public static void LoadStaticCache()
    {
        // Get suppliers - cache using the data cache
        SuppliersBLL suppliersBLL = new SuppliersBLL();
        HttpRuntime.Cache.Insert(
          /* key */                "key", 
          /* value */              suppliers, 
          /* dependencies */       null, 
          /* absoluteExpiration */ Cache.NoAbsoluteExpiration, 
          /* slidingExpiration */  Cache.NoSlidingExpiration, 
          /* priority */           CacheItemPriority.NotRemovable, 
          /* onRemoveCallback */   null);
    }
    [DataObjectMethodAttribute(DataObjectMethodType.Select, true)]
    public static Northwind.SuppliersDataTable GetSuppliers()
    {
        return HttpRuntime.Cache["key"] as Northwind.SuppliersDataTable;
    }
}

Para agregar un elemento a la caché de datos sin expiración basada en el tiempo, use los valores System.Web.Caching.Cache.NoAbsoluteExpiration y System.Web.Caching.Cache.NoSlidingExpiration como parámetros de entrada. Esta sobrecarga concreta del método Insert de la caché de datos se seleccionó para poder especificar la prioridad del elemento de caché. La prioridad se usa para determinar qué elementos se van a recuperar de la memoria caché cuando la memoria disponible se ejecuta bajo. Aquí usamos la prioridad NotRemovable, lo que garantiza que este elemento de caché no se guardará.

Nota:

Esta descarga del tutorial implementa la clase StaticCache mediante el enfoque de variables miembro estáticas. El código de las técnicas de estado de aplicación y caché de datos está disponible en los comentarios del archivo de clase.

Paso 4: ejecutar código en el inicio de la aplicación

Para ejecutar código cuando se inicia una aplicación web por primera vez, es necesario crear un archivo especial denominado Global.asax. Este archivo puede contener controladores de eventos para eventos de aplicación, sesión y nivel de solicitud, y aquí se puede agregar código que se ejecutará cada vez que se inicie la aplicación.

Agregue el archivo Global.asax al directorio raíz de la aplicación web haciendo clic con el botón derecho en el nombre del proyecto de sitio web en el Explorador de soluciones de Visual Studio y seleccionando Agregar nuevo elemento. En el cuadro de diálogo Agregar nuevo elemento, seleccione el tipo de elemento Clase de aplicación global y, a continuación, haga clic en el botón Agregar.

Nota:

Si ya tiene un archivo Global.asax en el proyecto, el tipo de elemento Clase de aplicación global no aparecerá en el cuadro de diálogo Agregar nuevo elemento.

Add the Global.asax File to Your Web Application's Root Directory

Ilustración 3: Agregar el archivo Global.asax al directorio raíz de la aplicación web (haga clic para ver la imagen de tamaño completo)

La plantilla de archivo Global.asax predeterminada incluye cinco métodos dentro de una etiqueta<script> del lado servidor:

  • Application_Start se ejecuta cuando se inicia la aplicación web por primera vez
  • Application_End se ejecuta cuando la aplicación se apaga
  • Application_Error se ejecuta cada vez que una excepción no controlada alcanza la aplicación
  • Session_Start se ejecuta cuando se crea una nueva sesión
  • Session_End se ejecuta cuando una sesión ha expirado o abandonado

El controlador de eventos Application_Start se llama solo una vez durante el ciclo de vida de una aplicación. La aplicación inicia la primera vez que se solicita un recurso ASP.NET desde la aplicación y continúa ejecutándose hasta que se reinicie la aplicación, lo que puede ocurrir modificando el contenido de la carpeta /Bin, modificando Global.asax, modificando el contenido de la carpeta App_Code o modificando el archivo Web.config, entre otras causas. Consulte Información general del ciclo de vida de la aplicación ASP.NET para obtener una explicación más detallada sobre el ciclo de vida de la aplicación.

En estos tutoriales solo es necesario agregar código al método Application_Start, así que no dude en quitar los demás. En Application_Start, simplemente llame al método LoadStaticCache() de la clase StaticCache, que cargará y almacenará en caché la información del proveedor:

<%@ Application Language="C#" %>
<script runat="server">
    void Application_Start(object sender, EventArgs e) 
    {
        StaticCache.LoadStaticCache();
    }
</script>

Eso es todo. En el inicio de la aplicación, el método LoadStaticCache() capturará la información del proveedor de BLL y lo almacenará en una variable miembro estática (o cualquier almacén de caché que haya terminado usando en la clase StaticCache). Para comprobar este comportamiento, establezca un punto de interrupción en el método Application_Start y ejecute la aplicación. Tenga en cuenta que el punto de interrupción se alcanza al iniciar la aplicación. Sin embargo, las solicitudes posteriores no hacen que se ejecute el método Application_Start.

Use a Breakpoint to Verify that the Application_Start Event Handler is Being Executed

Iistración 4: usar un punto de interrupción para comprobar que el controlador de eventos Application_Start se está ejecutando (Haga clic para ver la imagen a tamaño completo)

Nota:

Si no alcanza el punto de interrupción Application_Start al iniciar la depuración por primera vez, se debe a que la aplicación ya se ha iniciado. Fuerce el reinicio de la aplicación modificando los archivos Global.asax o Web.config y vuelva a intentarlo. Simplemente puede agregar (o quitar) una línea en blanco al final de uno de estos archivos para reiniciar rápidamente la aplicación.

Paso 5: mostrar los datos almacenados en caché

En este momento, la clase StaticCache tiene una versión de los datos del proveedor almacenados en caché durante el inicio de la aplicación a los que se puede acceder a través de su método GetSuppliers(). Para trabajar con estos datos desde la capa de presentación, podemos usar un ObjectDataSource o invocar mediante programación el método GetSuppliers() de la clase StaticCache desde una clase de código subyacente de la página ASP.NET. Echemos un vistazo al uso de los controles ObjectDataSource y GridView para mostrar la información del proveedor almacenada en caché.

Para empezar, abra la página AtApplicationStartup.aspx en la carpeta Caching. Arrastre un control GridView desde el cuadro de herramientas al diseñador y establezca su propiedad ID en Suppliers. A continuación, en la etiqueta inteligente GridView, elija crear un objeto ObjectDataSource denominado SuppliersCachedDataSource. Configure ObjectDataSource para que utilice el método GetSuppliers() de la clase StaticCache.

Configure the ObjectDataSource to use the StaticCache Class

Ilustración 5: configurar ObjectDataSource para usar la clase StaticCache (Haga clic para ver la imagen a tamaño completo)

Use the GetSuppliers() Method to Retrieve the Cached Supplier Data

Ilustración 6: usar el método GetSuppliers() para recuperar los datos de proveedor almacenados en caché (Haga clic para ver la imagen a tamaño completo)

Después de completar el asistente, Visual Studio agregará automáticamente BoundFields para cada uno de los campos de datos de SuppliersDataTable. El marcado declarativo GridView y ObjectDataSource debe tener un aspecto similar al siguiente:

<asp:GridView ID="Suppliers" runat="server" AutoGenerateColumns="False" 
    DataKeyNames="SupplierID" DataSourceID="SuppliersCachedDataSource" 
    EnableViewState="False">
    <Columns>
        <asp:BoundField DataField="SupplierID" HeaderText="SupplierID" 
            InsertVisible="False" ReadOnly="True" 
            SortExpression="SupplierID" />
        <asp:BoundField DataField="CompanyName" HeaderText="CompanyName" 
            SortExpression="CompanyName" />
        <asp:BoundField DataField="Address" HeaderText="Address" 
            SortExpression="Address" />
        <asp:BoundField DataField="City" HeaderText="City" 
            SortExpression="City" />
        <asp:BoundField DataField="Country" HeaderText="Country" 
            SortExpression="Country" />
        <asp:BoundField DataField="Phone" HeaderText="Phone" 
            SortExpression="Phone" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="SuppliersCachedDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetSuppliers" TypeName="StaticCache" />

En la ilustración 7 se muestra la página cuando se ve a través de un explorador. La salida es la misma que hemos extraído los datos de la clase SuppliersBLL de BLL, pero el uso de la clase StaticCache devuelve los datos del proveedor que se almacenan en caché durante el inicio de la aplicación. Puede establecer puntos de interrupción en el método GetSuppliers() de la clase StaticCache para comprobar este comportamiento.

The Cached Supplier Data is Displayed in a GridView

Ilustración 7: los datos de proveedores almacenados en caché se muestran en un control GridView (Haga clic para ver la imagen a tamaño completo)

Resumen

La mayoría de los modelos de datos contienen una cantidad razonable de datos estáticos, normalmente implementados en forma de tablas de búsqueda. Dado que esta información es estática, no hay ninguna razón para acceder continuamente a la base de datos cada vez que se debe mostrar esta información. Además, debido a su naturaleza estática, al almacenar en caché los datos no es necesario que expire. En este tutorial hemos visto cómo tomar estos datos y almacenarlos en caché en la caché de datos, el estado de la aplicación y a través de una variable de miembro estática. Esta información se almacena en caché durante el inicio de la aplicación y permanece en la memoria caché durante toda la vigencia de la aplicación.

En este tutorial y en los dos últimos, hemos examinado los datos de almacenamiento en caché durante la duración de la aplicación, así como el uso de expiraciones basadas en el tiempo. Sin embargo, al almacenar en caché los datos de la base de datos, una expiración basada en el tiempo puede ser menor que la ideal. En lugar de vaciar periódicamente la memoria caché, sería óptimo expulsar solo el elemento almacenado en caché cuando se modifican los datos de la base de datos subyacentes. Este ideal es posible mediante el uso de dependencias de caché de SQL, que examinaremos en el siguiente tutorial.

¡Feliz programación!

Acerca del autor

Scott Mitchell, autor de siete libros de ASP/ASP.NET y fundador de 4GuysFromRolla.com, ha estado trabajando con tecnologías web de Microsoft desde 1998. Scott trabaja como consultor independiente, instructor y escritor. Su último libro es Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Puede ponerse en contacto con él en mitchell@4GuysFromRolla.com. o a través de su blog, http://ScottOnWriting.NET.

Agradecimientos especiales a

Esta serie de tutoriales fue revisada por muchos revisores de gran ayuda. Los revisores principales de este tutorial fueron Teresa Murphy y Zack Jones. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.