Как кэшировать данные в приложении MVC

Я прочитал много информации о кэшировании страниц и частичном кэшировании страниц в приложении MVC. Тем не менее, я хотел бы знать, как вы будете кэшировать данные.

в моем сценарии я буду использовать LINQ для сущностей (Entity framework). При первом вызове GetNames (или независимо от метода) я хочу захватить данные из базы данных. Я хочу сохранить результаты в кэше и при втором вызове использовать кэшированную версию, если она существует.

может кто-нибудь показать пример как это будет работать, где это должно быть реализовано (модель?) и если это сработает.

Я видел, как это делается в традиционном ASP.NET приложения, как правило, для очень статических данных.

14 ответов


ссылка на систему.Web dll в вашей модели и системе использования.Сеть.Кэширование.Кэш

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

немного упрощенно, но я думаю, что это сработает. Это не MVC, и я всегда использовал этот метод для кэширования данных.


вот хороший и простой вспомогательный класс/сервис кэша, который я использую:

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

использование:

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

поставщик кэша проверит, есть ли в кэше что-либо с именем "Cache id", а если нет, он вызовет метод делегата для извлечения данных и хранения их в кэше.

пример:

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

Я имею в виду сообщение TT и предлагаю следующий подход:

ссылка на систему.Web dll в вашей модели и системе использования.Сеть.Кэширование.Кэш

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

    return ((string[])noms);
}

вы не должны возвращать значение, перечитанное из кэша, так как вы никогда не узнаете, находится ли оно в данный момент в кэше. Даже если вы вставили его в оператор раньше, он может уже исчезнуть или никогда не был добавлен в кэш - вы просто не знаете.

Так вы добавьте данные, считанные из базы данных, и верните их напрямую, не перечитывая из кэша.


для .NET 4.5 + framework

добавить ссылку: System.Runtime.Caching

добавить оператор using: 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);
}

в .NET Framework 3.5 и более ранних версиях, ASP.NET предусмотрена реализация кэша в памяти в системе.Сеть.Кэширование пространства имен. В предыдущих версиях .NET Framework кэширование было доступно только в системе.Веб-пространство имен и поэтому требует зависимостей классов ASP.NET . В .NET Framework 4 Система.Во время выполнения.Пространство имен кэширования содержит API, предназначенные как для веб -, так и для не-веб-приложений.

Подробнее:


Стив Смит сделал два больших сообщения в блоге, которые демонстрируют, как использовать его шаблон CachedRepository в ASP.NET MVC. Он эффективно использует шаблон репозитория и позволяет получить кэширование без необходимости изменения существующего кода.

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

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

в этих двух постах он показывает вам, как настроить эта закономерность и объясняет, почему она полезна. Используя этот шаблон, вы получаете кэширование без существующего кода, видящего какую-либо логику кэширования. По сути, вы используете кэшированный репозиторий, как если бы это был любой другой репозиторий.


Кэширование AppFabric распределяется и кэширование в памяти, которое хранит данные в парах ключ-значение, используя физическую память на нескольких серверах. AppFabric обеспечивает повышение производительности и масштабируемости приложений .NET Framework. концепции и архитектуры


расширение ответа @Hrvoje Hudo...

код:

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

примеры

кэширование одного элемента (когда каждый элемент кэшируется на основе его идентификатора, поскольку кэширование всего каталога для типа элемента будет слишком интенсивным).

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

кэширование всего чего-то

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

Почему TId

второй помощник особенно хорош, потому что большинство ключей данных не являются составными. Дополнительные методы могут быть добавлены, если вы часто используете составные ключи. Таким образом, вы избегаете делать все виды конкатенации строк или строки.Форматы, чтобы получить ключ для передачи помощнику кэша. Это также упрощает передачу метода доступа к данным, потому что вам не нужно передавать идентификатор в метод обертки... все это становится очень жестким и последовательным Для большинства случаев использования.


вот улучшение ответа Hrvoje Hudo. Эта реализация имеет несколько ключевых усовершенствований:

  • ключи кэша создаются автоматически на основе функции обновления данных и переданного объекта, который указывает зависимости
  • Pass в промежуток времени для любой продолжительности кэша
  • использует замок для безопасности потока

обратите внимание, что это зависит от Newtonsoft.JSON для сериализации объекта зависит, но это может быть легко заменяется на любой другой метод сериализации.

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

использование:

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

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

Я использую два класса. Сначала один объект ядра кэша:

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
}

второй - это список объектов кэша:

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

Я скажу, что реализация Singleton по этой сохраняющейся проблеме данных может быть решением этого вопроса, если вы найдете предыдущие решения очень сложными

 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

HttpContext.Current.Cache.Insert("subjectlist", subjectlist);

я использовал его таким образом, и он работает для меня. https://msdn.microsoft.com/en-us/library/system.web.caching.cache.add (v=против 110).aspx информация о параметрах системы.сеть.кэширование.кэш.добавлять.

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;

}

вы также можете попробовать использовать кэширование, встроенное в ASP MVC:

добавьте следующий атрибут к методу контроллера, который вы хотите кэшировать:

[OutputCache(Duration=10)]

в этом случае ActionResult этого будет кэшироваться в течение 10 секунд.

подробнее об этом здесь