web-dev-qa-db-fra.com

Comment mettre en cache des données dans une application MVC

J'ai lu beaucoup d'informations sur la mise en cache de pages et la mise en cache partielle de pages dans une application MVC. Cependant, j'aimerais savoir comment vous mettriez en cache des données.

Dans mon scénario, j'utiliserai LINQ to Entities (framework d'entité). Lors du premier appel à GetNames (ou quelle que soit la méthode utilisée), je souhaite récupérer les données de la base de données. Je veux enregistrer les résultats dans le cache et au deuxième appel pour utiliser la version en cache si elle existe.

Quelqu'un peut-il montrer un exemple de la façon dont cela fonctionnerait, où cela devrait être mis en œuvre (modèle?) Et si cela fonctionnerait.

J'ai vu cela dans les applications ASP.NET traditionnelles, généralement pour des données très statiques.

233
Coolcoder

Référencez la dll System.Web dans votre modèle et utilisez 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 peu simplifié mais je suppose que cela fonctionnerait. Ce n'est pas spécifique à MVC et j'ai toujours utilisé cette méthode pour la mise en cache des données.

72
terjetyl

Voici une classe et un service d'aide au cache simples et agréables que j'utilise: 

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

Usage:

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

Le fournisseur de cache vérifie si le nom "identifiant de cache" contient quelque chose dans le cache et, dans le cas contraire, appelle une méthode de délégation pour extraire des données et les stocker dans le cache. 

Exemple:

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

Je fais référence au message de TT et suggère l'approche suivante:

Référencez le dll System.Web dans votre modèle et utilisez System.Web.Caching.Cache

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

    return ((string[])noms);
}

Vous ne devez pas renvoyer une valeur relue dans la mémoire cache, car vous ne saurez jamais si, à ce moment précis, elle est toujours dans la mémoire cache. Même si vous l'aviez déjà inséré dans la déclaration, il se peut qu'il soit déjà parti ou qu'il n'ait jamais été ajouté à la mémoire cache - vous ne le savez pas.

Vous ajoutez donc les données lues dans la base de données et les retournez directement, sans relire à partir du cache. 

42
Oli

Pour le framework .NET 4.5+

ajouter une référence: System.Runtime.Caching 

ajouter en utilisant la déclaration: 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);
}

Dans les versions .NET Framework 3.5 et antérieures, ASP.NET fournissait une implémentation de cache en mémoire dans l'espace de noms System.Web.Caching. Dans les versions précédentes de .NET Framework, la mise en cache était disponible uniquement dans l'espace de noms System.Web et nécessitait donc une dépendance sur les classes ASP.NET. Dans le .NET Framework 4, l'espace de noms System.Runtime.Caching contient des API conçues pour les applications Web et non Web. 

Plus d'informations:

27
juFo

Steve Smith a publié deux articles de blog qui expliquent comment utiliser son modèle CachedRepository dans ASP.NET MVC. Il utilise efficacement le modèle de référentiel et vous permet d’obtenir la mise en cache sans avoir à modifier votre code existant.

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

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

Dans ces deux articles, il vous montre comment configurer ce modèle et explique également pourquoi il est utile. En utilisant ce modèle, vous obtenez la mise en cache sans que votre code existant ne voie la logique de mise en cache. Vous utilisez essentiellement le référentiel mis en cache comme s'il s'agissait de tout autre référentiel.

24
Brendan Enrick

AppFabric Caching est une technique de mise en cache en mémoire qui distribue des données dans des paires clé-valeur utilisant la mémoire physique sur plusieurs serveurs. AppFabric apporte des améliorations en termes de performances et d'évolutivité pour les applications .NET Framework. Concepts et architecture

4
Arun Duth
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
Chau

Voici une amélioration de la réponse de Hrvoje Hudo. Cette mise en œuvre présente quelques améliorations clés:

  • Les clés de cache sont créées automatiquement en fonction de la fonction de mise à jour des données et de l'objet transmis spécifiant les dépendances.
  • Passer dans le temps pour n'importe quelle durée de cache
  • Utilise un verrou pour la sécurité du fil

Notez que cela dépend de Newtonsoft.Json pour sérialiser l'objet dependOn, mais peut être facilement échangé pour toute autre méthode de sérialisation.

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

Usage:

var order = _cache.GetOrSet(
    () => _session.Set<Order>().SingleOrDefault(o => o.Id == orderId)
    , new { id = orderId }
    , new TimeSpan(0, 10, 0)
);
3
DShook

Extension de la réponse de @Hrvoje Hudo ...

Code:

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

Exemples

Mise en cache d'élément unique (lorsque chaque élément est mis en cache en fonction de son ID, car la mise en cache de tout le catalogue pour le type d'élément serait trop intensive).

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

Cacher tout de quelque chose

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

Pourquoi TID

Le second assistant est particulièrement agréable car la plupart des clés de données ne sont pas composites. Des méthodes supplémentaires peuvent être ajoutées si vous utilisez souvent des clés composites. De cette manière, vous évitez de faire toutes sortes de concaténations de chaînes ou de chaînes.Formats pour obtenir la clé à transmettre à l’assistant de cache. Cela facilite également le passage de la méthode d'accès aux données, car vous n'avez pas à passer l'ID dans la méthode d'encapsulation ... tout devient alors très concis et cohérent pour la majorité des cas d'utilisation.

3
smdrager

J'utilise deux classes. Le premier est l’objet de base du 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
}

Le second est la liste des objets en 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; }
}
1
Berezh

Je l'ai utilisé de cette manière et cela fonctionne pour moi . https://msdn.Microsoft.com/en-us/library/system.web.caching.cache.add(v=vs.110) .aspx paramètres info pour 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;

}
0
user3776645
HttpContext.Current.Cache.Insert("subjectlist", subjectlist);
0
Md. Akhtar Uzzaman

Je dirai que la mise en œuvre de Singleton sur ce problème persistant de données peut être une solution à ce problème si les solutions précédentes sont beaucoup plus complexes

 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
0
GeraGamo