Como armazenar em cache dados em um aplicativo MVC

252

Eu li muitas informações sobre o cache de páginas e o cache parcial de páginas em um aplicativo MVC. No entanto, gostaria de saber como você armazenaria os dados em cache.

No meu cenário, usarei o LINQ to Entities (estrutura da entidade). Na primeira chamada para GetNames (ou qualquer que seja o método), quero pegar os dados do banco de dados. Desejo salvar os resultados no cache e na segunda chamada para usar a versão em cache, se existir.

Alguém pode mostrar um exemplo de como isso funcionaria, onde isso deveria ser implementado (modelo?) E se funcionaria.

Eu já vi isso em aplicativos ASP.NET tradicionais, geralmente para dados muito estáticos.

Coolcoder
fonte
1
Ao revisar as respostas abaixo, certifique-se de considerar se deseja que seu controlador tenha conhecimento / responsabilidade pelos problemas de acesso a dados e armazenamento em cache. Geralmente você deseja separar isso. Veja o padrão de repositório para uma boa maneira de fazê-lo: deviq.com/repository-pattern
ssmith

Respostas:

75

Consulte a dll System.Web no seu modelo e use System.Web.Caching.Cache

    public string[] GetNames()
    {
      string[] names = Cache["names"] as string[];
      if(names == null) //not in cache
      {
        names = DB.GetNames();
        Cache["names"] = names;
      }
      return names;
    }

Um pouco simplificado, mas acho que funcionaria. Isso não é específico do MVC e eu sempre usei esse método para armazenar dados em cache.

terjetil
fonte
89
Não recomendo esta solução: no retorno, você pode obter um objeto nulo novamente, porque está relendo o cache e já pode ter sido eliminado do cache. Eu prefiro: public string [] GetNames () {string [] noms = Cache ["names"]; if (noms == null) {noms = DB.GetNames (); Cache ["names"] = noms; } retorno (noms); }
Oli
Concordo com Oli .. obtendo os resultados da chamada real para o DB é melhor do que fazê-los a partir do cache
CodeClimber
1
Isso funciona com o DB.GetNames().AsQueryablemétodo de atrasar a consulta?
precisa saber é o seguinte
Não, a menos que você alterar o valor de retorno de string [] para IEnumerable <string>
terjetyl
12
Se você não definir a expiração .. quando o cache expira por padrão?
Chaka
403

Aqui está um serviço / classe auxiliar de cache agradável e simples que eu uso:

using System.Runtime.Caching;  

public class InMemoryCache: ICacheService
{
    public T GetOrSet<T>(string cacheKey, Func<T> getItemCallback) where T : class
    {
        T item = MemoryCache.Default.Get(cacheKey) as T;
        if (item == null)
        {
            item = getItemCallback();
            MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(10));
        }
        return item;
    }
}

interface ICacheService
{
    T GetOrSet<T>(string cacheKey, Func<T> getItemCallback) where T : class;
}

Uso:

cacheProvider.GetOrSet("cache key", (delegate method if cache is empty));

O provedor de cache verificará se há algo com o nome de "ID do cache" no cache e, se não houver, chamará um método delegado para buscar dados e armazená-los no cache.

Exemplo:

var products=cacheService.GetOrSet("catalog.products", ()=>productRepository.GetAll())
Hrvoje Hudo
fonte
3
Eu adaptei isso para que o mecanismo de cache seja usado por sessão do usuário usando HttpContext.Current.Session. Também coloquei uma propriedade Cache na minha classe BaseController para facilitar o acesso e atualizar o construtor para permitir DI para testes de unidade. Espero que isto ajude.
precisa saber é o seguinte
1
Você também pode tornar essa classe e método estáticos para reutilização entre outros controladores.
Alex
5
Esta classe não deve depender do HttpContext. Eu simplifiquei apenas para fins de exemplo aqui. O objeto de cache deve ser inserido através do construtor - ele pode ser substituído por outros mecanismos de armazenamento em cache. Tudo isso é alcançado com IoC / DI, juntamente com o ciclo de vida estático (singleton).
Hrvoje Hudo
3
@Brendan - e, pior ainda, possui seqüências de caracteres mágicas para as chaves de cache, em vez de deduzi-las a partir do nome e dos parâmetros do método.
ssmith
5
Esta é uma solução impressionante de baixo nível. Como outros aludiram, você gostaria de agrupar isso em uma classe segura e específica de domínio. Acessar isso diretamente em seus controladores seria um pesadelo de manutenção por causa das seqüências de caracteres mágicas.
Josh Noe
43

Refiro-me à postagem de TT e sugiro a seguinte abordagem:

Consulte a dll System.Web no seu modelo e use System.Web.Caching.Cache

public string[] GetNames()
{ 
    var noms = Cache["names"];
    if(noms == null) 
    {    
        noms = DB.GetNames();
        Cache["names"] = noms; 
    }

    return ((string[])noms);
}

Você não deve retornar um valor relido a partir do cache, pois nunca saberá se naquele momento específico ele ainda está no cache. Mesmo se você o inseriu na declaração anterior, ele pode já ter desaparecido ou nunca ter sido adicionado ao cache - você simplesmente não sabe.

Portanto, você adiciona os dados lidos no banco de dados e os retorna diretamente, sem reler a partir do cache.

Oli
fonte
Mas a linha não Cache["names"] = noms;coloca no cache?
Omar
2
@Baddie Sim, sim. Mas este exemplo é diferente do primeiro a que Oli está se referindo, porque ele não acessa o cache novamente - o problema é o seguinte: return (string []) Cache ["names"]; .. PODE resultar em um valor nulo sendo retornado, porque PODE ter expirado. Não é provável, mas pode acontecer. Este exemplo é melhor, porque armazenamos o valor real retornado do banco de dados na memória, armazenamos esse valor em cache e, em seguida, retornamos esse valor, e não o valor re-lido do cache.
jamiebarrow
Ou ... o valor re-lido do cache, se ele ainda existir (! = Null). Portanto, todo o ponto de armazenamento em cache. Isso é apenas para dizer que ele verifica novamente valores nulos e lê o banco de dados quando necessário. Muito esperto, obrigado Oli!
precisa
Você pode compartilhar algum link onde eu possa ler sobre o cache de aplicativos com base em Key Value. Não consigo encontrar links.
Inquebrável
@Oli, como consumir este registros de cache de CSHTML ou página HTML
Deepan Raj
37

Para o .NET 4.5+ framework

adicionar referência: System.Runtime.Caching

adicione usando a instrução: using System.Runtime.Caching;

public string[] GetNames()
{ 
    var noms = System.Runtime.Caching.MemoryCache.Default["names"];
    if(noms == null) 
    {    
        noms = DB.GetNames();
        System.Runtime.Caching.MemoryCache.Default["names"] = noms; 
    }

    return ((string[])noms);
}

No .NET Framework 3.5 e versões anteriores, o ASP.NET forneceu uma implementação de cache na memória no espaço para nome System.Web.Caching. Nas versões anteriores do .NET Framework, o armazenamento em cache estava disponível apenas no espaço para nome System.Web e, portanto, exigia uma dependência das classes do ASP.NET. No .NET Framework 4, o espaço para nome System.Runtime.Caching contém APIs projetadas para aplicativos Web e não Web.

Mais informações:

juFo
fonte
De onde Db.GerNames()vem?
Júnior
DB.GetNames é apenas um método do DAL que busca alguns nomes do banco de dados. Isso é o que você normalmente recuperaria.
26517 juFo
Isso deve estar no topo, uma vez que temos a solução relevante atual
BYISHIMO Audace
2
Obrigado, necessário para adicionar o pacote nuget System.Runtime.Caching também (v4.5).
Steve Greene
26

Steve Smith fez duas ótimas postagens no blog que demonstram como usar seu padrão CachedRepository no ASP.NET MVC. Ele usa o padrão de repositório efetivamente e permite que você obtenha o armazenamento em cache sem precisar alterar o código existente.

http://ardalis.com/Introducing-the-CachedRepository-Pattern

http://ardalis.com/building-a-cachedrepository-via-strategy-pattern

Nessas duas postagens, ele mostra como configurar esse padrão e também explica por que é útil. Ao usar esse padrão, você obtém o cache sem que seu código existente veja nenhuma lógica de cache. Essencialmente, você usa o repositório em cache como se fosse qualquer outro repositório.

Brendan Enrick
fonte
1
Ótimas postagens! Obrigado por compartilhar !!
Mark Good
Links desativados em 31/08/2013.
CBono
Você pode compartilhar algum link onde eu possa ler sobre o cache de aplicativos com base em Key Value. Não consigo encontrar links.
Inquebrável
4

O AppFabric Caching é distribuído e uma técnica de cache na memória que armazena dados em pares de valor-chave usando memória física em vários servidores. O AppFabric fornece aprimoramentos de desempenho e escalabilidade para aplicativos .NET Framework. Conceitos e Arquitetura

Arun Duth
fonte
Isso é específico do Azure, não do ASP.NET MVC em geral.
Henry C
3

Estendendo a resposta de @Hrvoje Hudo ...

Código:

using System;
using System.Runtime.Caching;

public class InMemoryCache : ICacheService
{
    public TValue Get<TValue>(string cacheKey, int durationInMinutes, Func<TValue> getItemCallback) where TValue : class
    {
        TValue item = MemoryCache.Default.Get(cacheKey) as TValue;
        if (item == null)
        {
            item = getItemCallback();
            MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(durationInMinutes));
        }
        return item;
    }

    public TValue Get<TValue, TId>(string cacheKeyFormat, TId id, int durationInMinutes, Func<TId, TValue> getItemCallback) where TValue : class
    {
        string cacheKey = string.Format(cacheKeyFormat, id);
        TValue item = MemoryCache.Default.Get(cacheKey) as TValue;
        if (item == null)
        {
            item = getItemCallback(id);
            MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(durationInMinutes));
        }
        return item;
    }
}

interface ICacheService
{
    TValue Get<TValue>(string cacheKey, Func<TValue> getItemCallback) where TValue : class;
    TValue Get<TValue, TId>(string cacheKeyFormat, TId id, Func<TId, TValue> getItemCallback) where TValue : class;
}

Exemplos

Armazenamento em cache de item único (quando cada item é armazenado em cache com base em seu ID porque o armazenamento em cache de todo o catálogo para o tipo de item seria muito intenso).

Product product = cache.Get("product_{0}", productId, 10, productData.getProductById);

Armazenando em cache tudo

IEnumerable<Categories> categories = cache.Get("categories", 20, categoryData.getCategories);

Por que dizer

O segundo auxiliar é especialmente interessante porque a maioria das chaves de dados não é composta. Métodos adicionais podem ser adicionados se você usar chaves compostas frequentemente. Dessa forma, você evita fazer todos os tipos de concatenação ou sequência de caracteres. Formata para que a chave passe para o auxiliar de cache. Isso também facilita a passagem do método de acesso a dados, porque você não precisa passar o ID para o método wrapper ... tudo se torna muito conciso e consistente para a maioria dos casos de uso.

smdrager
fonte
1
Suas definições de interface estão sem o parâmetro "durationInMinutes". ;-)
Tech03
3

Aqui está uma melhoria na resposta de Hrvoje Hudo. Esta implementação possui algumas melhorias importantes:

  • As chaves de cache são criadas automaticamente com base na função para atualizar dados e o objeto passado que especifica dependências
  • Intervalo de tempo para qualquer duração do cache
  • Usa uma trava para segurança da linha

Observe que isso depende da Newtonsoft.Json para serializar o objeto DependOn, mas que pode ser facilmente trocado por qualquer outro método de serialização.

ICache.cs

public interface ICache
{
    T GetOrSet<T>(Func<T> getItemCallback, object dependsOn, TimeSpan duration) where T : class;
}

InMemoryCache.cs

using System;
using System.Reflection;
using System.Runtime.Caching;
using Newtonsoft.Json;

public class InMemoryCache : ICache
{
    private static readonly object CacheLockObject = new object();

    public T GetOrSet<T>(Func<T> getItemCallback, object dependsOn, TimeSpan duration) where T : class
    {
        string cacheKey = GetCacheKey(getItemCallback, dependsOn);
        T item = MemoryCache.Default.Get(cacheKey) as T;
        if (item == null)
        {
            lock (CacheLockObject)
            {
                item = getItemCallback();
                MemoryCache.Default.Add(cacheKey, item, DateTime.Now.Add(duration));
            }
        }
        return item;
    }

    private string GetCacheKey<T>(Func<T> itemCallback, object dependsOn) where T: class
    {
        var serializedDependants = JsonConvert.SerializeObject(dependsOn);
        var methodType = itemCallback.GetType();
        return methodType.FullName + serializedDependants;
    }
}

Uso:

var order = _cache.GetOrSet(
    () => _session.Set<Order>().SingleOrDefault(o => o.Id == orderId)
    , new { id = orderId }
    , new TimeSpan(0, 10, 0)
);
DShook
fonte
2
O if (item == null)deve estar dentro da fechadura. Agora, quando isso ifocorre antes do bloqueio, a condição de corrida pode ocorrer. Ou melhor ainda, você deve manter ifantes do bloqueio, mas verifique novamente se o cache ainda está vazio como a primeira linha dentro do bloqueio. Porque se dois encadeamentos vierem ao mesmo tempo, ambos atualizarão o cache. Seu bloqueio atual não é útil.
Al Kepp
3
public sealed class CacheManager
{
    private static volatile CacheManager instance;
    private static object syncRoot = new Object();
    private ObjectCache cache = null;
    private CacheItemPolicy defaultCacheItemPolicy = null;

    private CacheEntryRemovedCallback callback = null;
    private bool allowCache = true;

    private CacheManager()
    {
        cache = MemoryCache.Default;
        callback = new CacheEntryRemovedCallback(this.CachedItemRemovedCallback);

        defaultCacheItemPolicy = new CacheItemPolicy();
        defaultCacheItemPolicy.AbsoluteExpiration = DateTime.Now.AddHours(1.0);
        defaultCacheItemPolicy.RemovedCallback = callback;
        allowCache = StringUtils.Str2Bool(ConfigurationManager.AppSettings["AllowCache"]); ;
    }
    public static CacheManager Instance
    {
        get
        {
            if (instance == null)
            {
                lock (syncRoot)
                {
                    if (instance == null)
                    {
                        instance = new CacheManager();
                    }
                }
            }

            return instance;
        }
    }

    public IEnumerable GetCache(String Key)
    {
        if (Key == null || !allowCache)
        {
            return null;
        }

        try
        {
            String Key_ = Key;
            if (cache.Contains(Key_))
            {
                return (IEnumerable)cache.Get(Key_);
            }
            else
            {
                return null;
            }
        }
        catch (Exception)
        {
            return null;
        }
    }

    public void ClearCache(string key)
    {
        AddCache(key, null);
    }

    public bool AddCache(String Key, IEnumerable data, CacheItemPolicy cacheItemPolicy = null)
    {
        if (!allowCache) return true;
        try
        {
            if (Key == null)
            {
                return false;
            }

            if (cacheItemPolicy == null)
            {
                cacheItemPolicy = defaultCacheItemPolicy;
            }

            String Key_ = Key;

            lock (Key_)
            {
                return cache.Add(Key_, data, cacheItemPolicy);
            }
        }
        catch (Exception)
        {
            return false;
        }
    }

    private void CachedItemRemovedCallback(CacheEntryRemovedArguments arguments)
    {
        String strLog = String.Concat("Reason: ", arguments.RemovedReason.ToString(), " | Key-Name: ", arguments.CacheItem.Key, " | Value-Object: ", arguments.CacheItem.Value.ToString());
        LogManager.Instance.Info(strLog);
    }
}
Chau
fonte
3
Considere a adição de alguma explicação
Mike Debela
2

Eu usei dessa maneira e funciona para mim. https://msdn.microsoft.com/en-us/library/system.web.caching.cache.add(v=vs.110).aspx informações de parâmetros para system.web.caching.cache.add.

public string GetInfo()
{
     string name = string.Empty;
     if(System.Web.HttpContext.Current.Cache["KeyName"] == null)
     {
         name = GetNameMethod();
         System.Web.HttpContext.Current.Cache.Add("KeyName", name, null, DateTime.Noew.AddMinutes(5), Cache.NoSlidingExpiration, CacheitemPriority.AboveNormal, null);
     }
     else
     {
         name = System.Web.HttpContext.Current.Cache["KeyName"] as string;
     }

      return name;

}
user3776645
fonte
upvotes extras para material totalmente qualificado com seu espaço para nome completo !!
Ninjanoel 28/11/19
1

Eu uso duas classes. Primeiro, o objeto principal do cache:

public class Cacher<TValue>
    where TValue : class
{
    #region Properties
    private Func<TValue> _init;
    public string Key { get; private set; }
    public TValue Value
    {
        get
        {
            var item = HttpRuntime.Cache.Get(Key) as TValue;
            if (item == null)
            {
                item = _init();
                HttpContext.Current.Cache.Insert(Key, item);
            }
            return item;
        }
    }
    #endregion

    #region Constructor
    public Cacher(string key, Func<TValue> init)
    {
        Key = key;
        _init = init;
    }
    #endregion

    #region Methods
    public void Refresh()
    {
        HttpRuntime.Cache.Remove(Key);
    }
    #endregion
}

O segundo é a lista de objetos de cache:

public static class Caches
{
    static Caches()
    {
        Languages = new Cacher<IEnumerable<Language>>("Languages", () =>
                                                          {
                                                              using (var context = new WordsContext())
                                                              {
                                                                  return context.Languages.ToList();
                                                              }
                                                          });
    }
    public static Cacher<IEnumerable<Language>> Languages { get; private set; }
}
Berezh
fonte
0

Eu direi que implementar o Singleton nessa questão persistente de dados pode ser uma solução para esse caso, caso você encontre soluções anteriores muito complicadas

 public class GPDataDictionary
{
    private Dictionary<string, object> configDictionary = new Dictionary<string, object>();

    /// <summary>
    /// Configuration values dictionary
    /// </summary>
    public Dictionary<string, object> ConfigDictionary
    {
        get { return configDictionary; }
    }

    private static GPDataDictionary instance;
    public static GPDataDictionary Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new GPDataDictionary();
            }
            return instance;
        }
    }

    // private constructor
    private GPDataDictionary() { }

}  // singleton
GeraGamo
fonte
Isso funcionou para mim perfeitamente É por isso que eu recomendo isso para todo mundo quem pode ser ajudado por este
GeraGamo
0
HttpContext.Current.Cache.Insert("subjectlist", subjectlist);
Md. Akhtar Uzzaman
fonte
-8

Você também pode tentar usar o cache incorporado ao ASP MVC:

Adicione o seguinte atributo ao método do controlador que você deseja armazenar em cache:

[OutputCache(Duration=10)]

Nesse caso, o ActionResult disso será armazenado em cache por 10 segundos.

Mais sobre isso aqui

qui
fonte
4
OutputCache é para a renderização de Action, a questão era em relação ao cache de dados e não da página.
Coolcoder
é off topic, mas OutputCache também armazenar em cache os dados do banco de dados
Muflix