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

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

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

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

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

14 ответов

Решение

Ссылайтесь на dll System.Web в вашей модели и используйте 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;
    }

Немного упрощенно, но я думаю, это сработает. Это не специфично для 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));

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

Пример:

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

Я имею в виду пост ТТ и предлагаю следующий подход:

Ссылайтесь на dll System.Web в вашей модели и используйте System.Web.Caching.Cache

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 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 предоставляла реализацию кэширования в памяти в пространстве имен System.Web.Caching. В предыдущих версиях.NET Framework кэширование было доступно только в пространстве имен System.Web и поэтому требовало зависимости от классов ASP.NET. В.NET Framework 4 пространство имен System.Runtime.Caching содержит API-интерфейсы, предназначенные как для веб-приложений, так и для не-веб-приложений.

Больше информации:

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

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

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

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

Я использовал это таким образом, и это работает для меня. https://msdn.microsoft.com/en-us/library/system.web.caching.cache.add(v=vs.110).aspx информация о параметрах для 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;

}

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

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

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

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

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

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

Расширяя ответ @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);

Почему

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

Я использую два класса. Первый объект ядра кеша:

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; }
}
HttpContext.Current.Cache.Insert("subjectlist", subjectlist);

Я скажу, что реализация 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

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

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

[OutputCache(Duration=10)]

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

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

Другие вопросы по тегам