Come memorizzare nella cache i dati in un'applicazione MVC


252

Ho letto molte informazioni sul caching delle pagine e sul caching parziale delle pagine in un'applicazione MVC. Tuttavia, vorrei sapere come si desidera memorizzare i dati nella cache.

Nel mio scenario userò LINQ to Entities (framework di entità). Alla prima chiamata a GetNames (o qualunque sia il metodo) voglio prendere i dati dal database. Voglio salvare i risultati nella cache e nella seconda chiamata per utilizzare la versione cache se esiste.

Qualcuno può mostrare un esempio di come funzionerebbe, dove dovrebbe essere implementato (modello?) E se funzionerebbe.

L'ho visto nelle app ASP.NET tradizionali, in genere per dati molto statici.


1
Nel rivedere le risposte di seguito, assicurati di considerare se desideri che il tuo controllore sia a conoscenza / responsabile dell'accesso ai dati e delle preoccupazioni relative alla memorizzazione nella cache. Generalmente vuoi separarlo. Vedi il modello di deposito
ssmith

Risposte:


75

Fare riferimento alla dll System.Web nel modello e utilizzare 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;
    }

Un po 'semplificato ma immagino che funzionerebbe. Questo non è specifico di MVC e ho sempre usato questo metodo per memorizzare i dati nella cache.


89
Non raccomando questa soluzione: in cambio, potresti ottenere di nuovo un oggetto null, perché sta rileggendo nella cache e potrebbe essere già stato eliminato dalla cache. Preferirei fare: public string [] GetNames () {string [] noms = Cache ["names"]; if (noms == null) {noms = DB.GetNames (); Cache ["names"] = noms; } return (noms); }
Oli,

Sono d'accordo con Oli .. ottenere i risultati dall'effettiva chiamata al DB è meglio che ottenerli dalla cache
CodeClimber

1
Funziona con il DB.GetNames().AsQueryablemetodo di ritardare la query?
Chase Florell,

A meno che non modifichi il valore restituito da stringa [] in IEnumerable <stringa>
terjetyl

12
Se non si imposta la scadenza ... quando la cache scade per impostazione predefinita?
Chaka,

403

Ecco una simpatica e semplice classe / servizio di supporto cache che utilizzo:

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));

Il provider di cache verificherà se esiste qualcosa con il nome di "ID cache" nella cache e, in caso contrario, chiamerà un metodo delegato per recuperare i dati e archiviarli nella cache.

Esempio:

var products=cacheService.GetOrSet("catalog.products", ()=>productRepository.GetAll())

3
L'ho adattato in modo che il meccanismo di memorizzazione nella cache venga utilizzato per sessione utente utilizzando invece HttpContext.Current.Session. Ho anche messo una proprietà Cache sulla mia classe BaseController in modo che il suo facile accesso e aggiornato il costruttore consentano DI per test unitari. Spero che questo ti aiuti.
WestDiscGolf

1
È inoltre possibile rendere questa classe e questo metodo statici per la riusabilità tra altri controller.
Alex

5
Questa classe non dovrebbe dipendere da HttpContext. L'ho semplificato solo per esempio qui. L'oggetto cache deve essere inserito tramite il costruttore - può essere sostituito quindi con altri meccanismi di memorizzazione nella cache. Tutto ciò è ottenuto con IoC / DI, insieme al ciclo di vita statico (singleton).
Hrvoje Hudo,

3
@Brendan - e peggio ancora, ha le stringhe magiche in atto per le chiavi della cache, piuttosto che inferirle dal nome e dai parametri del metodo.
fabbro

5
Questa è una fantastica soluzione di basso livello. Come altri hanno accennato, ti consigliamo di racchiuderlo in un tipo sicuro, classe specifica del dominio. Accedere a questo direttamente nei controller sarebbe un incubo di manutenzione a causa delle stringhe magiche.
Josh Noe,

43

Mi riferisco al post di TT e suggerisco il seguente approccio:

Fare riferimento alla dll System.Web nel modello e utilizzare System.Web.Caching.Cache

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

    return ((string[])noms);
}

Non si dovrebbe restituire un valore riletto dalla cache, poiché non si saprà mai se in quel momento specifico è ancora nella cache. Anche se l'hai inserito nell'istruzione in precedenza, potrebbe essere già sparito o non è mai stato aggiunto alla cache - semplicemente non lo sai.

Quindi aggiungi i dati letti dal database e li restituisci direttamente, non rileggendoli dalla cache.


Ma la riga non viene Cache["names"] = noms;inserita nella cache?
Omar,

2
@Baddie Sì. Ma questo esempio è diverso dal primo a cui Oli si riferisce, perché non accede nuovamente alla cache - il problema è che semplicemente facendo: return (string []) Cache ["names"]; .. POTREBBE restituire un valore nullo restituito, perché POTREBBE essere scaduto. Non è probabile, ma può succedere. Questo esempio è migliore, perché memorizziamo il valore effettivo restituito dal db in memoria, memorizziamo nella cache quel valore e quindi restituiamo quel valore, non il valore riletto dalla cache.
Jamiebarrow,

Oppure ... il valore rileggere dalla cache, se esiste ancora (! = Null). Quindi, l'intero punto di memorizzazione nella cache. Questo è solo per dire che ricontrolla i valori null e legge il database dove necessario. Molto intelligente, grazie Oli!
Sean Kendle,

Puoi condividere qualche link in cui posso leggere informazioni sulla memorizzazione nella cache delle applicazioni basata su valori chiave. Non riesco a trovare i collegamenti.
Unbreakable

@Oli, Come utilizzare questi record di cache dalla pagina CSHTML o HTML
Deepan Raj,

37

Per .NET 4.5+ framework

aggiungi riferimento: System.Runtime.Caching

aggiungi usando statement: 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);
}

In .NET Framework 3.5 e versioni precedenti, ASP.NET ha fornito un'implementazione della cache in memoria nello spazio dei nomi System.Web.Caching. Nelle versioni precedenti di .NET Framework, la memorizzazione nella cache era disponibile solo nello spazio dei nomi System.Web e pertanto richiedeva una dipendenza dalle classi ASP.NET. In .NET Framework 4, lo spazio dei nomi System.Runtime.Caching contiene API progettate per applicazioni Web e non Web.

Ulteriori informazioni:


Da dove viene Db.GerNames()?
Junior

DB.GetNames è solo un metodo dal DAL che recupera alcuni nomi dal database. Questo è ciò che normalmente recupereresti.
juFo

Questo dovrebbe essere al vertice in quanto ha l'attuale soluzione pertinente
BYISHIMO Audace

2
Grazie, è necessario aggiungere anche il pacchetto nuget System.Runtime.Caching (v4.5).
Steve Greene,

26

Steve Smith ha pubblicato due grandi post sul blog che dimostrano come utilizzare il suo modello CachedRepository in ASP.NET MVC. Utilizza in modo efficace il modello di repository e consente di ottenere la memorizzazione nella cache senza dover modificare il codice esistente.

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

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

In questi due post ti mostra come impostare questo schema e spiega anche perché è utile. Usando questo modello si ottiene la memorizzazione nella cache senza che il codice esistente visualizzi alcuna logica di memorizzazione nella cache. Essenzialmente si utilizza il repository memorizzato nella cache come se fosse un altro repository.


1
Grandi post! Grazie per la condivisione!!
Segna bene il

Link morti dal 2013-08-31.
CBono,


Puoi condividere qualche link in cui posso leggere informazioni sulla memorizzazione nella cache delle applicazioni basata su valori chiave. Non riesco a trovare i collegamenti.
Unbreakable

4

La memorizzazione nella cache di AppFabric è distribuita e una tecnica di memorizzazione nella cache in memoria che archivia i dati in coppie chiave-valore utilizzando la memoria fisica su più server. AppFabric offre miglioramenti delle prestazioni e della scalabilità per le applicazioni .NET Framework. Concetti e architettura


Questo è specifico per Azure, non ASP.NET MVC in generale.
Henry C

3

Estensione della risposta di @Hrvoje Hudo ...

Codice:

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;
}

Esempi

Memorizzazione nella cache di un singolo articolo (quando ogni articolo viene memorizzato nella cache in base al suo ID perché la memorizzazione nella cache dell'intero catalogo per il tipo di articolo sarebbe troppo intensiva).

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

Memorizzazione nella cache di tutto

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

Perché TId

Il secondo aiuto è particolarmente utile perché la maggior parte delle chiavi dei dati non sono composte. È possibile aggiungere ulteriori metodi se si utilizzano spesso chiavi composite. In questo modo eviti di eseguire qualsiasi tipo di concatenazione o stringa di stringhe. Formatta per ottenere la chiave da passare all'helper della cache. Inoltre, rende più semplice il passaggio del metodo di accesso ai dati perché non è necessario passare l'ID nel metodo wrapper ... il tutto diventa molto conciso e coerente per la maggior parte dei casi d'uso.


1
Nelle definizioni dell'interfaccia manca il parametro "durationInMinutes". ;-)
Tech0

3

Ecco un miglioramento alla risposta di Hrvoje Hudo. Questa implementazione ha un paio di miglioramenti chiave:

  • Le chiavi della cache vengono create automaticamente in base alla funzione per aggiornare i dati e l'oggetto passato che specifica le dipendenze
  • Passa il lasso di tempo per qualsiasi durata della cache
  • Utilizza un lucchetto per la sicurezza del thread

Si noti che questo ha una dipendenza da Newtonsoft.Json per serializzare l'oggetto dependOn, ma che può essere facilmente sostituito con qualsiasi altro metodo di serializzazione.

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)
);

2
Il if (item == null)dovrebbe essere all'interno della serratura. Ora, quando questo ifè prima del blocco, possono verificarsi condizioni di gara. O ancora meglio, dovresti tenerlo ifprima del blocco, ma ricontrolla se la cache è ancora vuota come prima riga all'interno del blocco. Perché se due thread arrivano contemporaneamente, entrambi aggiornano la cache. Il tuo blocco attuale non è utile.
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);
    }
}

3
Considera di aggiungere alcune spiegazioni
Mike Debela,

2

L'ho usato in questo modo e funziona per me. https://msdn.microsoft.com/en-us/library/system.web.caching.cache.add(v=vs.110).aspx informazioni sui parametri per 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;

}

extra voti per cose pienamente qualificanti con il suo spazio dei nomi completo !!
Ninjanoel,

1

Uso due classi. Innanzitutto l'oggetto principale della 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
}

Il secondo è un elenco di oggetti 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; }
}

0

Dirò che implementare Singleton su questo persistente problema di dati può essere una soluzione per questo caso nel caso in cui trovi soluzioni precedenti molto complicate

 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

Questo ha funzionato perfettamente per me ed è per questo che lo consiglio a tutti coloro che potrebbero essere aiutati da questo
GeraGamo,


-8

Puoi anche provare a utilizzare la memorizzazione nella cache integrata in ASP MVC:

Aggiungi il seguente attributo al metodo del controller che desideri memorizzare nella cache:

[OutputCache(Duration=10)]

In questo caso, ActionResult verrà memorizzato nella cache per 10 secondi.

Maggiori informazioni qui


4
OutputCache è per il rendering di Action, la domanda riguardava i dati della cache e non la pagina.
Coolcoder l'

è fuori tema ma OutputCache memorizza anche nella cache i dati del database
Muflix,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.