Armazenar dados em cache na inicialização do aplicativo (C#)
por Scott Mitchell
Em qualquer aplicativo Web, alguns dados serão usados com frequência e alguns dados serão usados com pouca frequência. Podemos melhorar o desempenho de nosso aplicativo ASP.NET carregando com antecedência os dados usados com frequência, uma técnica conhecida como Cache. Este tutorial demonstra uma abordagem para o carregamento proativo, que é carregar dados no cache na inicialização do aplicativo.
Introdução
Os dois tutoriais anteriores analisaram o cache de dados nas Camadas de Apresentação e Cache. Em Armazenar dados em cache com o ObjectDataSource, examinamos o uso dos recursos de cache do ObjectDataSource para armazenar dados em cache na Camada de Apresentação. O cache de dados na arquitetura examinou o cache em uma nova camada de cache separada. Ambos os tutoriais usaram o carregamento reativo no trabalho com o cache de dados. Com o carregamento reativo, sempre que os dados são solicitados, o sistema primeiro verifica se eles estão no cache. Caso contrário, ele captura os dados da fonte de origem, como o banco de dados, e os armazena no cache. A vantagem main para o carregamento reativo é a facilidade de implementação. Uma de suas desvantagens é seu desempenho desigual entre solicitações. Imagine uma página que usa a Camada de Cache do tutorial anterior para exibir informações do produto. Quando essa página é visitada pela primeira vez ou visitada pela primeira vez depois que os dados armazenados em cache são removidos devido a restrições de memória ou à expiração especificada que foi atingida, os dados devem ser recuperados do banco de dados. Portanto, essas solicitações de usuários levarão mais tempo do que as solicitações dos usuários que podem ser atendidas pelo cache.
O carregamento proativo fornece uma estratégia alternativa de gerenciamento de cache que suaviza o desempenho entre solicitações carregando os dados armazenados em cache antes que eles sejam necessários. Normalmente, o carregamento proativo usa algum processo que verifica periodicamente ou é notificado quando há uma atualização para os dados subjacentes. Esse processo atualiza o cache para mantê-lo atualizado. O carregamento proativo é especialmente útil se os dados subjacentes vierem de uma conexão de banco de dados lenta, um serviço Web ou alguma outra fonte de dados particularmente lenta. Mas essa abordagem para carregamento proativo é mais difícil de implementar, pois requer a criação, o gerenciamento e a implantação de um processo para marcar para alterações e atualização do cache.
Outro tipo de carregamento proativo e o tipo que exploraremos neste tutorial é carregar dados no cache na inicialização do aplicativo. Essa abordagem é especialmente útil para armazenar dados estáticos em cache, como os registros em tabelas de pesquisa de banco de dados.
Observação
Para obter uma visão mais detalhada das diferenças entre o carregamento proativo e reativo, bem como as listas de prós, contras e recomendações de implementação, consulte a seção Gerenciando o conteúdo de um cache do Guia de Arquitetura de Cache para aplicativos .NET Framework.
Etapa 1: Determinando quais dados armazenar em cache na inicialização do aplicativo
Os exemplos de cache usando o carregamento reativo que examinamos nos dois tutoriais anteriores funcionam bem com dados que podem mudar periodicamente e não demoram muito para serem gerados. Mas se os dados armazenados em cache nunca forem alterados, a expiração usada pelo carregamento reativo será supérflua. Da mesma forma, se os dados que estão sendo armazenados em cache levarem um tempo extremamente longo para serem gerados, os usuários cujas solicitações localizarem o cache vazio terão que suportar uma longa espera enquanto os dados subjacentes são recuperados. Considere armazenar em cache dados estáticos e dados que levam um tempo excepcionalmente longo para serem gerados na inicialização do aplicativo.
Embora os bancos de dados tenham muitos valores dinâmicos e com alteração frequente, a maioria também tem uma quantidade justa de dados estáticos. Por exemplo, praticamente todos os modelos de dados têm uma ou mais colunas que contêm um valor específico de um conjunto fixo de opções. Uma Patients
tabela de banco de dados pode ter uma PrimaryLanguage
coluna cujo conjunto de valores pode ser inglês, espanhol, francês, russo, japonês e assim por diante. Geralmente, esses tipos de colunas são implementados usando tabelas de pesquisa. Em vez de armazenar a cadeia de caracteres em inglês ou francês na Patients
tabela, uma segunda tabela é criada com, normalmente, duas colunas - um identificador exclusivo e uma descrição de cadeia de caracteres - com um registro para cada valor possível. A PrimaryLanguage
coluna na Patients
tabela armazena o identificador exclusivo correspondente na tabela de pesquisa. Na Figura 1, o idioma principal do paciente John Doe é o inglês, enquanto o de Ed Johnson é russo.
Figura 1: a Languages
tabela é uma tabela de pesquisa usada pela Patients
tabela
A interface do usuário para editar ou criar um novo paciente incluiria uma lista suspensa de idiomas permitidos preenchidos pelos registros na Languages
tabela. Sem o cache, sempre que essa interface for visitada, o sistema deverá consultar a Languages
tabela. Isso é um desperdício e desnecessário, pois os valores da tabela de pesquisa mudam com muita pouca frequência, se nunca.
Poderíamos armazenar em cache os Languages
dados usando as mesmas técnicas de carregamento reativo examinadas nos tutoriais anteriores. O carregamento reativo, no entanto, usa uma expiração baseada em tempo, que não é necessária para dados de tabela de pesquisa estática. Embora o cache usando o carregamento reativo seja melhor do que nenhum cache, a melhor abordagem seria carregar proativamente os dados da tabela de pesquisa no cache na inicialização do aplicativo.
Neste tutorial, examinaremos como armazenar em cache dados da tabela de pesquisa e outras informações estáticas.
Etapa 2: Examinando as diferentes maneiras de armazenar dados em cache
As informações podem ser armazenadas em cache programaticamente em um aplicativo ASP.NET usando uma variedade de abordagens. Já vimos como usar o cache de dados em tutoriais anteriores. Como alternativa, os objetos podem ser armazenados em cache programaticamente usando membros estáticos ou o estado do aplicativo.
Ao trabalhar com uma classe, normalmente a classe deve primeiro ser instanciada antes que seus membros possam ser acessados. Por exemplo, para invocar um método de uma das classes em nossa Camada de Lógica de Negócios, primeiro devemos criar uma instância da classe :
ProductsBLL productsAPI = new ProductsBLL();
productsAPI.SomeMethod();
productsAPI.SomeProperty = "Hello, World!";
Antes de podermos invocar SomeMethod ou trabalhar com SomeProperty, primeiro devemos criar uma instância da classe usando o new
palavra-chave. SomeMethod e SomeProperty estão associados a uma instância específica. O tempo de vida desses membros está vinculado ao tempo de vida de seu objeto associado. Os membros estáticos, por outro lado, são variáveis, propriedades e métodos que são compartilhados entre todas as instâncias da classe e, consequentemente, têm um tempo de vida, desde que a classe. Os membros estáticos são indicados pelo palavra-chave static
.
Além dos membros estáticos, os dados podem ser armazenados em cache usando o estado do aplicativo. Cada aplicativo ASP.NET mantém uma coleção de nome/valor que é compartilhada entre todos os usuários e páginas do aplicativo. Essa coleção pode ser acessada usando a HttpContext
propriedade da Application
classe e usada de uma classe code-behind de uma página ASP.NET da seguinte forma:
Application["key"] = value;
object value = Application["key"];
O cache de dados fornece uma API muito mais rica para armazenar dados em cache, fornecendo mecanismos para expirações baseadas em tempo e dependência, prioridades de item de cache e assim por diante. Com membros estáticos e o estado do aplicativo, esses recursos devem ser adicionados manualmente pelo desenvolvedor da página. No entanto, ao armazenar dados em cache na inicialização do aplicativo durante o tempo de vida do aplicativo, as vantagens do cache de dados são discutíveis. Neste tutorial, examinaremos o código que usa todas as três técnicas para armazenar dados estáticos em cache.
Etapa 3: Armazenando em cache os dados daSuppliers
tabela
As tabelas de banco de dados Northwind que implementamos até o momento não incluem tabelas de pesquisa tradicionais. Os quatro DataTables implementados em nosso DAL todas as tabelas de modelo cujos valores são não estáticos. Em vez de gastar tempo para adicionar um novo DataTable ao DAL e, em seguida, uma nova classe e métodos à BLL, para este tutorial, vamos apenas fingir que os Suppliers
dados da tabela são estáticos. Portanto, poderíamos armazenar esses dados em cache na inicialização do aplicativo.
Para começar, crie uma nova classe chamada StaticCache.cs
na CL
pasta .
Figura 2: Criar a StaticCache.cs
classe na CL
pasta
Precisamos adicionar um método que carrega os dados na inicialização no repositório de cache apropriado, bem como métodos que retornam dados desse cache.
[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;
}
}
O código acima usa uma variável de membro estático, suppliers
, para manter os resultados do SuppliersBLL
método da GetSuppliers()
classe, que é chamado do LoadStaticCache()
método . O LoadStaticCache()
método deve ser chamado durante o início do aplicativo. Depois que esses dados forem carregados na inicialização do aplicativo, qualquer página que precise trabalhar com dados do fornecedor poderá chamar o StaticCache
método da GetSuppliers()
classe. Portanto, a chamada para o banco de dados para obter os fornecedores só acontece uma vez, no início do aplicativo.
Em vez de usar uma variável de membro estático como o repositório de cache, poderíamos ter usado, como alternativa, o estado do aplicativo ou o cache de dados. O código a seguir mostra a classe retoolada para usar o estado do aplicativo:
[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;
}
}
No LoadStaticCache()
, as informações do fornecedor são armazenadas na chave de variável de aplicativo. Ele é retornado como o tipo apropriado (Northwind.SuppliersDataTable
) de GetSuppliers()
. Embora o estado do aplicativo possa ser acessado nas classes code-behind de ASP.NET páginas usando Application["key"]
, na arquitetura, devemos usar HttpContext.Current.Application["key"]
para obter o atual HttpContext
.
Da mesma forma, o cache de dados pode ser usado como um repositório de cache, como mostra o seguinte 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 adicionar um item ao cache de dados sem expiração baseada em tempo, use os System.Web.Caching.Cache.NoAbsoluteExpiration
valores e System.Web.Caching.Cache.NoSlidingExpiration
como parâmetros de entrada. Essa sobrecarga específica do método do Insert
cache de dados foi selecionada para que pudéssemos especificar a prioridade do item de cache. A prioridade é usada para determinar quais itens serão retirados do cache quando a memória disponível for baixa. Aqui, usamos a prioridade NotRemovable
, que garante que esse item de cache não seja armazenado em scaveng.
Observação
O download deste tutorial implementa a StaticCache
classe usando a abordagem de variável de membro estático. O código para o estado do aplicativo e as técnicas de cache de dados está disponível nos comentários no arquivo de classe.
Etapa 4: Executando código na inicialização do aplicativo
Para executar o código quando um aplicativo Web é iniciado pela primeira vez, precisamos criar um arquivo especial chamado Global.asax
. Esse arquivo pode conter manipuladores de eventos para eventos de nível de aplicativo, sessão e solicitação, e é aqui que podemos adicionar código que será executado sempre que o aplicativo for iniciado.
Adicione o Global.asax
arquivo ao diretório raiz do aplicativo Web clicando com o botão direito do mouse no nome do projeto do site no Gerenciador de Soluções do Visual Studio e escolhendo Adicionar Novo Item. Na caixa de diálogo Adicionar Novo Item, selecione o tipo de item Classe de Aplicativo Global e clique no botão Adicionar.
Observação
Se você já tiver um Global.asax
arquivo em seu projeto, o tipo de item Classe de Aplicativo Global não será listado na caixa de diálogo Adicionar Novo Item.
Figura 3: Adicionar o Global.asax
arquivo ao diretório raiz do aplicativo Web (clique para exibir a imagem em tamanho real)
O modelo de arquivo padrão Global.asax
inclui cinco métodos dentro de uma marca do lado <script>
do servidor:
Application_Start
é executado quando o aplicativo Web é iniciado pela primeira vezApplication_End
é executado quando o aplicativo está sendo desligadoApplication_Error
executa sempre que uma exceção sem tratamento atinge o aplicativoSession_Start
é executado quando uma nova sessão é criadaSession_End
é executado quando uma sessão está expirada ou abandonada
O Application_Start
manipulador de eventos é chamado apenas uma vez durante o ciclo de vida de um aplicativo. O aplicativo inicia a primeira vez que um recurso de ASP.NET é solicitado do aplicativo e continua a ser executado até que o aplicativo seja reiniciado, o que pode acontecer modificando o conteúdo da /Bin
pasta, modificando Global.asax
, modificando o conteúdo na App_Code
pasta ou modificando o Web.config
arquivo, entre outras causas. Consulte ASP.NET Visão geral do Ciclo de Vida do Aplicativo para obter uma discussão mais detalhada sobre o ciclo de vida do aplicativo.
Para esses tutoriais, só precisamos adicionar código ao Application_Start
método , portanto, fique à vontade para remover os outros. No Application_Start
, basta chamar o StaticCache
método da LoadStaticCache()
classe, que carregará e armazenará em cache as informações do fornecedor:
<%@ Application Language="C#" %>
<script runat="server">
void Application_Start(object sender, EventArgs e)
{
StaticCache.LoadStaticCache();
}
</script>
Isso é tudo! Na inicialização do aplicativo, o LoadStaticCache()
método obterá as informações do fornecedor da BLL e as armazenará em uma variável de membro estático (ou em qualquer repositório de cache que você acabou usando na StaticCache
classe). Para verificar esse comportamento, defina um ponto de interrupção no Application_Start
método e execute seu aplicativo. Observe que o ponto de interrupção é atingido no início do aplicativo. No entanto, as solicitações subsequentes não fazem com que o Application_Start
método seja executado.
Figura 4: usar um ponto de interrupção para verificar se o Application_Start
manipulador de eventos está sendo executado (clique para exibir a imagem em tamanho real)
Observação
Se você não atingir o Application_Start
ponto de interrupção ao iniciar a depuração pela primeira vez, isso ocorre porque seu aplicativo já foi iniciado. Force o aplicativo a reiniciar modificando seus Global.asax
arquivos ou Web.config
e tente novamente. Você pode simplesmente adicionar (ou remover) uma linha em branco no final de um desses arquivos para reiniciar rapidamente o aplicativo.
Etapa 5: Exibindo os dados armazenados em cache
Neste ponto, a StaticCache
classe tem uma versão dos dados do fornecedor armazenados em cache na inicialização do aplicativo que podem ser acessados por meio de seu GetSuppliers()
método. Para trabalhar com esses dados da Camada de Apresentação, podemos usar um ObjectDataSource ou invocar programaticamente o StaticCache
método da GetSuppliers()
classe de uma classe code-behind de uma página ASP.NET. Vamos examinar o uso dos controles ObjectDataSource e GridView para exibir as informações do fornecedor armazenado em cache.
Comece abrindo a AtApplicationStartup.aspx
página na Caching
pasta . Arraste um GridView da Caixa de Ferramentas para o designer, definindo sua ID
propriedade como Suppliers
. Em seguida, na marca inteligente do GridView, escolha criar um novo ObjectDataSource chamado SuppliersCachedDataSource
. Configure o ObjectDataSource para usar o StaticCache
método da GetSuppliers()
classe.
Figura 5: configurar o ObjectDataSource para usar a StaticCache
classe (Clique para exibir a imagem em tamanho real)
Figura 6: usar o GetSuppliers()
método para recuperar os dados do fornecedor armazenado em cache (clique para exibir a imagem em tamanho real)
Depois de concluir o assistente, o Visual Studio adicionará automaticamente BoundFields para cada um dos campos de dados em SuppliersDataTable
. A marcação declarativa de GridView e ObjectDataSource deve ser semelhante à seguinte:
<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" />
A Figura 7 mostra a página quando exibida por meio de um navegador. A saída é a mesma se tivéssemos extraído os dados da classe da BLL, mas o uso da StaticCache
classe retorna os dados do fornecedor conforme armazenados em cache na inicialização do SuppliersBLL
aplicativo. Você pode definir pontos de interrupção no StaticCache
método da GetSuppliers()
classe para verificar esse comportamento.
Figura 7: Os dados do fornecedor armazenados em cache são exibidos em um GridView (clique para exibir a imagem em tamanho real)
Resumo
A maioria de cada modelo de dados contém uma boa quantidade de dados estáticos, geralmente implementados na forma de tabelas de pesquisa. Como essas informações são estáticas, não há razão para acessar continuamente o banco de dados sempre que essas informações precisarem ser exibidas. Além disso, devido à sua natureza estática, ao armazenar em cache os dados, não há necessidade de expiração. Neste tutorial, vimos como pegar esses dados e armazená-los em cache de dados, no estado do aplicativo e por meio de uma variável de membro estático. Essas informações são armazenadas em cache na inicialização do aplicativo e permanecem no cache durante todo o tempo de vida do aplicativo.
Neste tutorial e nos dois últimos, examinamos os dados de cache durante o tempo de vida do aplicativo, bem como usando expirações baseadas em tempo. No entanto, ao armazenar em cache dados de banco de dados, uma expiração baseada em tempo pode ser menor do que o ideal. Em vez de liberar periodicamente o cache, seria ideal remover apenas o item armazenado em cache quando os dados do banco de dados subjacentes forem modificados. Esse ideal é possível por meio do uso de dependências de cache SQL, que examinaremos em nosso próximo tutorial.
Programação feliz!
Sobre o autor
Scott Mitchell, autor de sete livros do ASP/ASP.NET e fundador da 4GuysFromRolla.com, trabalha com tecnologias da Microsoft Web desde 1998. Scott trabalha como consultor independente, treinador e escritor. Seu último livro é Sams Teach Yourself ASP.NET 2.0 em 24 Horas. Ele pode ser contatado em mitchell@4GuysFromRolla.com. ou através de seu blog, que pode ser encontrado em http://ScottOnWriting.NET.
Agradecimentos Especiais
Esta série de tutoriais foi revisada por muitos revisores úteis. Os principais revisores deste tutorial foram Teresa Murphy e Zack Jones. Interessado em revisar meus próximos artigos do MSDN? Nesse caso, solte-me uma linha em mitchell@4GuysFromRolla.com.