Este artigo foi traduzido por máquina.

Pontos de dados

Cache de segundo nível na estrutura de entidades e AppFabric

Julie Lerman

Baixar o código de exemplo

Julie LermanO ObjectContext do Entity Framework (FE) e DbContext mantêm informações de estado sobre entidades que estão gerenciando. Mas depois que o contexto sai do escopo, as informações do estado desapareceu. Esse tipo de cache é conhecido como cache de primeiro nível e só está disponível o tempo de vida de uma transação. Se você estiver escrevendo aplicativos distribuídos usando o EF onde o contexto não conversando — e, portanto, suas informações de estado não estiverem continuamente disponíveis — o cache de primeiro nível provavelmente não é suficiente para suportar suas demandas. Normalmente, esse é o caso com os serviços e aplicativos da Web — ou até mesmo quando você estiver usando algum tipo de implementação do repositório padrão onde um contexto de execução demorada não está disponível.

Por que o EF pode se beneficiar o cache de segundo nível

Por que você deve preocupar tendo acesso a uma representação do estado original através dos processos? Uma das grandes vantagens do EF é sua capacidade de gerar automaticamente os comandos de persistência de banco de dados (inserções, atualizações e exclusões) com base nas informações de estado encontradas no contexto. Mas se as informações do estado não estiverem disponíveis, o EF não tem nada a fazer quando chegou a hora a chamada SaveChanges. Desenvolvedores, incluindo sozinho, tentando contornar essa limitação, pois o EF primeiro foi apresentado em 2006.

Os caches de segundo nível são fundamentais para resolver esse tipo de problema. Esses caches existirem fora da transação — geralmente fora do aplicativo — e, portanto, estão disponíveis para qualquer instância de contexto. E o cache de segundo nível é um padrão de codificação comumente usado para armazenar em cache os dados para vários usos.

Em vez de escrever sua própria maneira de cache de dados, existem mecanismos de cache disponíveis, como memcached (memcached.org) e até mesmo cache suporte no Microsoft AppFabric (disponível no Windows Server, bem como em Windows Azure). Esses serviços fornecem a infra-estrutura para armazenamento em cache para que você não precise sweat os detalhes. E eles expõem APIs que tornam mais fácil para os programadores a ler, armazenar e expirar a dados no cache.

Se você tiver um sistema altamente transacional que pode se beneficiar com um cache para evitar pressionar repetidamente no banco de dados para dados consultados com freqüência, você também encontrará sozinho procurando uma solução de cache. Esta é uma ótima maneira de aumentar o desempenho ao usar os dados que é modificado com pouca freqüência — por exemplo, fazer referência a dados ou uma lista dos jogadores em uma equipe esportiva.

Figura 1 mostra o cache de primeiro nível mantido dentro de um contexto EF, bem como vários contextos acessando um cache de segundo nível comum.

First-Level Caching Happens Inside a Transactional Context and Second-Level Caching Is External
Figura 1 o cache de primeiro nível acontece dentro de um contexto transacional e cache de segundo nível externo

Usando o provedor de armazenamento em cache EF para adicionar o cache de segundo nível

Projetando a lógica para leitura, armazenando e expiração de dados do cache leva um pouco de trabalho. Você gostaria de fazer isso, ao trabalhar com o EF quando você estiver consultando dados ou armazenamento de dados. Ao executar uma consulta contra o contexto, você desejará consulte primeiro se dados existente no cache para que você não precise gastar recursos em uma chamada de banco de dados. Ao atualizar dados usando o método SaveChanges de um contexto, você desejará expirar e possivelmente atualizar os dados no cache. E trabalhar com o cache é mais complexa do que simplesmente ler e gravar dados. Há várias outras considerações para levar em conta. Há um artigo detalhado da associação para computação maquinaria (ACM) que contemple as complexidades de cache de ORM, "expondo o Cache ORM: familiaridade com ORM questões de cache pode ajudar a evitar problemas de desempenho e bugs" (bit.ly/k5bzd1). Eu não tentará repetir os prós, contras e pontos de acesso descritos no artigo. Em vez disso, me concentrarei na implementação.

O EF não possui suporte interno para trabalhar com os caches de segundo nível. Essa funcionalidade faria mais sentido como parte da lógica do ObjectContext e DbContext quando eles estão prestes a interagir com o banco de dados. Mas implementar o armazenamento em cache ao mesmo tempo, levando em consideração os diversos problemas abordados no artigo ACM não trivial, especialmente com a falta de pontos de extensibilidade óbvio o EF. Um dos recursos que é realçado com freqüência como uma grande diferença entre o EF e NHibernate é o fato de que o NHibernate possui suporte interno para implementar o cache de segundo nível.

Mas nem tudo está perdido! Insira os provedores EF e o brainy Jarek Kowalski (j.mp/jkefblog), antigo membro da equipe EF.

O modelo de provedor EF é fundamental para como o EF é capaz de oferecer suporte a qualquer banco de dados relacional — desde que há um provedor escrito para o banco de dados que inclui suporte EF. Há uma enorme quantidade de provedores de terceiros, permitindo que você use o EF com um array crescente dos bancos de dados (SQL Server, Oracle, Sybase, MySQL e Firebird são apenas alguns exemplos).

No EF, a fala de ObjectContext para a API EntityClient de nível inferior, que se comunica com o provedor de banco de dados para encontrar comandos específicos do banco de dados e, em seguida, interage com o banco de dados. Quando o banco de dados está retornando dados (como resultado de consultas ou comandos que atualizam os valores gerados pelo armazenamento), o caminho é invertido, conforme mostrado na a Figura 2.

Flow from the EF Context Through an ADO.NET Provider to Get to the Database
Figura 2 o fluxo do contexto EF por meio de um objeto ADO.NET Provider para chegar ao banco de dados

O ponto onde reside o provedor é pliable, permitindo que você injetar provedores adicionais entre o EntityClient e o banco de dados. Esses são denominados wrappers do provedor. Você pode saber mais sobre como escrever código ADO.NET para o EF ou outros tipos de provedores no blog da equipe EF lançar, "escrevendo um ADO EF habilitado.NET Provider"(bit.ly/etavcJ).

Há alguns anos atrás, Kowalski usado seu profundo conhecimento dos provedores EF para gravar um provedor que captura mensagens entre o cliente de entidade e o ADO.NET provider de escolha (independentemente de ser SqlClient, MySQL Connector ou outro) e injeta a lógica para interagir com um mecanismo de cache de segundo nível. O invólucro é extensível. Ele fornece a lógica subjacente para qualquer tipo de solução de cache, mas, em seguida, você precisa implementar uma classe que pontes entre esse wrapper e a solução de cache. O exemplo de provedor funciona com um cache na memória e a solução tem um adaptador de amostra para usar "Velocity", o nome de código para o armazenamento em cache distribuídas da Microsoft. Velocity eventualmente se tornou o mecanismo de cache de AppFabric de servidor do Microsoft Windows.

Criação de um adaptador de EFCachingProvider para o Windows Server AppFabric

O EFCachingProvider foi atualizado recentemente para o EF 4. O rastreamento e cache invólucros de provedor para a página de Entity Framework (bit.ly/zlpIb) inclui excelentes amostras e documentação, portanto, não é necessário repetir todas isso aqui. No entanto, o adaptador Velocity foi removido e não havia nenhum substituto para usar o cache em AppFabric.

AppFabric vive em dois lugares: Windows Server e Windows Azure. Eu já recriada a classe do provedor que trabalharam com Velocity, para que ele agora funcionará com o armazenamento em cache no Windows Server AppFabric e vamos compartilhar como fazer isso sozinho.

Primeiro, certifique-se de que você instalou os wrappers do provedor EF de bit.ly/zlpIb. Trabalhei na solução de exemplo, que contém os projetos para os wrappers do provedor (EFCachingProvider, EFTracingProvider e EFProviderWrapperToolkit). Também existem alguns projetos de cliente que testem a funcionalidade de cache final. O provedor de InMemoryCache é o padrão de estratégia de cache e é incorporado a EFCachingProvider. Também é realçado no projeto no a Figura 3 é ICache.cs. O InMemoryCache herda de isso, e portanto devem qualquer outro adaptador que você deseja criar para usar outros mecanismos de cache — como, por exemplo, o adaptador de AppFabricCache que criei.

ICache and InMemoryCache Are Core Classes in the EFCachingProvider
InMemoryCache e ICache a Figura 3 são Classes principais do EFCachingProvider

Para desenvolver para AppFabric, você precisará os assemblies de cliente de cache de AppFabric e uma instalação mínima do AppFabric na máquina de desenvolvimento. Consulte o tópico Biblioteca MSDN, "Explicação passo a passo: Implantando o Windows Server AppFabric em um único nó desenvolvimento ambiente," em bit.ly/lwsolW, para obter ajuda sobre esta tarefa. Ser avisado de que ele é um pouco envolvido. Fiz isso sozinho em duas máquinas de desenvolvimento.

Agora você pode criar um adaptador para o Windows Server AppFabric. Isso está muito próxima do adaptador Velocity3 original, mas eu gasto um pouco de tempo para aprender como trabalhar com a API do cliente AppFabric para obter essas estrelas fiquem alinhadas. Se você estiver criando um adaptador para um mecanismo de cache diferente, você precisará ajustar adequadamente à API do cache.

Outra parte fundamental do quebra-cabeça é estender a classe ObjectContext. Espero que experimentar isso com um DbContext de 4.1 EF em breve, mas isso será necessário a modificar a lógica subjacente do EFCachingProvider.

Você pode usar o mesmo código para estender o contexto independentemente de qual implementação de ICache você estiver trabalhando com. A classe estendida herda de sua classe de contexto (que, por sua vez, é herdeira de ObjectContext) e, em seguida, expõe alguns métodos de extensão do EFCachingProvider. Esses métodos de extensão permitem que o contexto interagir diretamente (e automaticamente) com o provedor de armazenamento em cache. Figura4 mostra um exemplo na solução que estende o NorthwindEFEntities, um contexto de um modelo criado no banco de dados Northwind.

Figura 4 estender uma classe existente que herda de ObjectContext, NorthwindEFEntities

using EFCachingProvider;
using EFCachingProvider.Caching;
using EFProviderWrapperToolkit;
 
namespace NorthwindModelDbFirst
{
  public partial class ExtendedNorthwindEntities : NorthwindEFEntities
  {
 
  public ExtendedNorthwindEntities()
    : this("name=NorthwindEFEntities")
  {
  }
 
  public ExtendedNorthwindEntities(string connectionString)
    : base(EntityConnectionWrapperUtils.
CreateEntityConnectionWithWrappers(
    connectionString,"EFCachingProvider"))
  {
  }
  
  private EFCachingConnection CachingConnection
  {
    get { return this.UnwrapConnection<EFCachingConnection>(); }
  }
 
  public ICache Cache
  {
    get { return CachingConnection.Cache; }
    set { CachingConnection.Cache = value; }
  }
 
  public CachingPolicy CachingPolicy
  {
    get { return CachingConnection.CachingPolicy; }
    set { CachingConnection.CachingPolicy = value; }
  }
 
  #endregion
  }
}

Adicionei um projeto de biblioteca de classes à solução chamada EFAppFabricCacheAdapter. O projeto deve referências a EFCachingProvider, bem como dois dos assemblies AppFabric: Microsoft.ApplicationServer.Caching.Core e Microsoft.ApplicationServer.Caching.Client. Figura 5 mostra a minha classe de adaptador, AppFabricCache, que emula o VelocityCache original.

Figura 5 adaptador que interage com o Windows Server AppFabric

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using EFCachingProvider.Caching;
using Microsoft.ApplicationServer.Caching;
 
namespace EFAppFabricCacheAdapter
{
  public class AppFabricCache : ICache
  {
    private DataCache _cache;
 
    public AppFabricCache(DataCache cache)
    {
      _cache = cache;
    }
 
    public bool GetItem(string key, out object value)
    {
      key = GetCacheKey(key);
      value = _cache.Get(key);
 
      return value != null;
    }
 
    public void PutItem(string key, object value,
      IEnumerable<string> dependentEntitySets,
      TimeSpan slidingExpiration, DateTime absoluteExpiration)
    {
      key = GetCacheKey(key);
      _cache.Put(key, value, absoluteExpiration - DateTime.Now,
        dependentEntitySets.Select(c => new DataCacheTag(c)).ToList());
 
      foreach (var dep in dependentEntitySets)
      {
        CreateRegionIfNeeded(dep);
        _cache.Put(key, " ", dep);
      }
 
    }
 
    public void InvalidateSets(IEnumerable<string> entitySets)
    {
      // Go through the list of objects in each of the sets.
foreach (var dep in entitySets)
      {
        foreach (var val in _cache.GetObjectsInRegion(dep))
        {
          _cache.Remove(val.Key);
        }
      }
    }
 
    public void InvalidateItem(string key)
    {
      key = GetCacheKey(key);
 
      DataCacheItem item = _cache.GetCacheItem(key);
      _cache.Remove(key);
 
      foreach (var tag in item.Tags)
      {
        _cache.Remove(key, tag.ToString());
      }
    }
 
    // Creates a hash of the query to store as the key 
    private static string GetCacheKey(string query)
    {
      byte[] bytes = Encoding.UTF8.GetBytes(query);
      string hashString = Convert
        .ToBase64String(MD5.Create().ComputeHash(bytes));
      return hashString;
    }
 
    private void CreateRegionIfNeeded(string regionName)
    {
      try
      {
        _cache.CreateRegion(regionName);
      }
      catch (DataCacheException de)
      {
        if (de.ErrorCode != DataCacheErrorCode.RegionAlreadyExists)
        {
          throw;
        }
      }
    }
  }
}

A classe usa a Microsoft.ApplicationServer.Caching.DataCache para atender a implementação necessária do ICache. O mais notável é o uso de regiões de AppFabric na PutItem e InvalidateSets. Quando um novo item é armazenado no cache, o adaptador também adiciona-lo para uma região ou grupo, que é definido por todas as entidades em um conjunto específico de entidade. Em outras palavras, se você tiver um modelo com o cliente, pedido e LineItem, em seguida, suas instâncias de cliente serão ser armazenado em cache em uma região de clientes, as instâncias de pedido em uma região chamada Orders e assim por diante. Quando um determinado item é invalidado, em vez de procurando desse item específico e invalidar a ele, todos os itens na região são invalidados.

É este uso de regiões que me fez reservar minha tentativa de implementar o suporte de Windows Azure AppFabric. No momento em que estou escrevendo esta coluna, Windows Azure AppFabric ainda é um CTP e não oferece suporte a regiões. Porque o código subjacente do provedor de armazenamento em cache é dependente das regiões e esses métodos, não foi possível criar facilmente uma implementação de provedor que funcionaria apenas para Windows Azure AppFabric. Você pode, obviamente, chamar o método InvalidateItem sozinho, mas o que eliminaria o benefício do comportamento automatizado do provedor.

Usando o adaptador de Cache de AppFabric

Há um último projeto para adicionar, e que é o projeto que emprega o adaptador. A demonstração do EFCachingProvider que é parte da solução original usa um aplicativo de console com vários métodos para testar o armazenamento em cache: SimpleCachingDemo, CacheInvalidationDemo e NonDeterministicQueryCachingDemo. Em meu aplicativo de console adicionado para fora do AppFabricCache de teste, você pode usar os mesmos três métodos com a mesma implementação. O interessante sobre este teste é o código para instanciar e configurando a AppFabricCache que será usada pelo contexto estendido nesses três métodos.

Um DataCache de AppFabric precisa ser criado primeiro identificando um ponto de extremidade do servidor de AppFabric, em seguida, usando seu DataCacheFactory para criar o DataCache. Aqui está o código para fazer isso:

private static ICache CreateAppFabricCache()
{
  var server = new List<DataCacheServerEndpoint>();
  server.Add(new DataCacheServerEndpoint("localhost", 22233));
  var conf = new DataCacheFactoryConfiguration();
  conf.Servers = server;
  DataCacheFactory fac = new DataCacheFactory(conf);
  return new AppFabricCache(fac.GetDefaultCache());
}

Observe que estou codificar que os detalhes de ponto de extremidade para a simplicidade neste exemplo, mas você provavelmente não desejará fazer isso em um aplicativo de produção. Depois que você criou o DataCache, você usá-lo para instanciar um AppFabricCache.

Com esse cache em mãos, posso passá-lo para o EFCachingProvider e aplicar configurações, como um DefaultCachingPolicy:

ICache dataCache = CreateAppFabricCache();
EFCachingProviderConfiguration.DefaultCache = dataCache;
EFCachingProviderConfiguration.DefaultCachingPolicy = CachingPolicy.CacheAll;

Finalmente, ao instanciar o meu contexto estendido, ele automaticamente procurará por um provedor de armazenamento em cache, localizando a instância de AppFabricCache que basta definir como padrão. Isso fará o cache esteja ativo, usando quaisquer definições de configuração é aplicada. Tudo o que você precisa fazer é ir sobre seus negócios com o contexto — consultando, trabalhando com objetos e a chamada SaveChanges. Graças aos métodos de extensão que ligar seu contexto para o EFProviderCache e a instância de DataCache que você anexou, todo o armazenamento em cache acontecerá automaticamente em segundo plano. Observe que o CachingPolicy de CacheAll armazena em serve para demonstrações, mas você deve considerar usar uma diretiva mais ajustada para que você não está em cache dados desnecessariamente.

O EFProviderCache foi projetado tendo em mente a extensibilidade. Desde que o mecanismo que você deseja usar o cache de destino oferece suporte as implementações padrão para armazenar, recuperar, expirar e agrupar dados, você deve ser capaz de acompanhar o padrão do adaptador para fornecer um cache compartilhado para os aplicativos que usam o EF para acesso a dados.

Julie Lerman é uma Microsoft MVP, mentora e consultora do .NET, que reside nas colinas de Vermont. Você pode encontrar sua apresentação sobre acesso a dados e outros tópicos do Microsoft .NET em grupos de usuários e conferências ao redor do mundo. Seu blog está em thedatafarm.com/blog e ela é autora do livro altamente reconhecido, “Programming Entity Framework” (O’Reilly Media, 2010). Siga-a no Twitter, em twitter.com/julielerman.

Graças aos seguintes especialistas técnicos para revisão deste artigo: Jarek Kowalski e Srikanth Mandadi