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

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

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

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

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

Ответ 1

Ссылка на 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, и я всегда использовал этот метод для кэширования данных.

Ответ 2

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

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

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

Пример:

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

Ответ 3

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

Ссылка на 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);
}

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

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

Ответ 4

Для .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-интерфейсы, разработанные как для веб-приложений, так и для не-веб-приложений.

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

Ответ 5

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

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

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

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

Ответ 6

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

Ответ 7

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

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

Ответ 8

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

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

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

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

Ответ 9

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

Ответ 10

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

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

Ответ 11

Я использовал это таким образом, и это работает для меня. 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;

}

Ответ 12

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

Ответ 13

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

Ответ 14

Приведенное ниже решение Оли было одним из согласованных подходов. Я думал, что поскольку noms могут ссылаться на ненулевое значение для кэша при проверке на нулевое значение, не может ли noms ссылаться на другое значение, если срок действия кэша истек или стал каким-либо образом недействительным (возможно, нулевым) до того, как функция вернула результат? У меня такое чувство, что я что-то упускаю.

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

Спасибо!

Ответ 15

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

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

[OutputCache(Duration=10)]

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

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