Як кешувати дані в додатку MVC


252

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

У моєму сценарії я буду використовувати LINQ для сутностей (структура сутності). Під час першого дзвінка до GetNames (або будь-якого способу) я хочу захопити дані з бази даних. Я хочу зберегти результати в кеші та під час другого виклику використовувати кешовану версію, якщо вона існує.

Чи може хтось показати приклад того, як це би працювало, де це слід реалізувати (модель?) Та чи це буде працювати.

Я бачив це в традиційних додатках ASP.NET, як правило, для дуже статичних даних.


1
Переглядаючи відповіді нижче, не забудьте врахувати, чи хочете, щоб ваш контролер мав знання / відповідальність за доступ до даних та проблеми кешування. Як правило, ви хочете розділити це. Подивіться шаблон сховища, щоб зробити це гарним способом: deviq.com/repository-pattern
ssmith

Відповіді:


75

Посилайтеся на 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, і я завжди використовував цей метод для кешування даних.


89
Я не рекомендую це рішення: у відповідь ви можете отримати нульовий об’єкт знову, тому що він перечитується в кеші, і він, можливо, вже був скинутий з кеша. Я б краще зробив: public string [] GetNames () {string [] noms = Кеш ["імена"]; if (noms == null) {noms = DB.GetNames (); Кеш ["імена"] = номи; } повернення (номи); }
Олі

Я згоден з Oli. Отримати результати фактичного виклику в БД краще, ніж отримувати їх з кешу
Отримати CodeClimber

1
Чи працює це з DB.GetNames().AsQueryableметодом затримки запиту?
Чейз Флорелл

Якщо ви не зміните значення повернення з рядка [] на IEnumerable <string>
terjetyl

12
Якщо ви не встановите термін дії, коли кеш закінчується за замовчуванням?
Чак

403

Ось приємний і простий клас помічників кеш-сервісу / послуга, яку я використовую:

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
Я адаптував це, щоб механізм кешування використовувався для кожного сеансу користувача, використовуючи замість цього HttpContext.Current.Session. Я також помістив властивість Cache на мій клас BaseController, щоб його легкий доступ і оновив конструктор, що дозволяє робити DI для тестування одиниць. Сподіваюсь, це допомагає.
WestDiscGolf

1
Ви також можете зробити цей клас та метод статичними для повторного використання серед інших контролерів.
Алекс

5
Цей клас не повинен залежати від HttpContext. Я спростив це лише для прикладу. Об'єкт кешу повинен бути вставлений через конструктор - він може бути замінений на інші механізми кешування. Все це досягається за допомогою IoC / DI разом із статичним (однотонним) життєвим циклом.
Хрвое Худо,

3
@Brendan - і що ще гірше, у нього є магічні рядки для клавіш кешу, а не виводять їх із назви методу та параметрів.
ssmith

5
Це дивовижне рішення низького рівня. Як і інші, на які згадували, ви хочете перенести це у безпечний для класу тип, що залежить від домену. Доступ до цього безпосередньо у ваших контролерах був би кошмаром технічного обслуговування через магічні струни.
Джош Ное

43

Я маю на увазі пост 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);
}

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

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


Але чи не Cache["names"] = noms;вводиться рядок у кеш?
Омар

2
@ Бадді Так. Але цей приклад відрізняється від першого, на який посилається Олі, оскільки він не має доступу до кеша знову - проблема полягає в тому, що він просто робить: return (string []) Кеш ["імена"]; .. COULD може призвести до повернення нульового значення, оскільки воно МОЖЕ закінчитися. Це мало ймовірно, але це може статися. Цей приклад є кращим, оскільки ми зберігаємо фактичне значення, повернене з db в пам'ять, кешуємо це значення, а потім повертаємо це значення, а не значення, перечитане з кеша.
jamiebarrow

Або ... значення, перечитане з кешу, якщо воно все ще існує (! = Null). Отже, вся точка кешування. Це просто сказати, що він двічі перевіряє нульові значення та читає базу даних, де це необхідно. Дуже розумний, дякую Олі!
Шон Кендл

Чи можете ви поділитися посиланням, де я можу прочитати про кешування програм на основі Key Value. Я не в змозі знайти посилання.
Незламний

@ Oli, Як споживати ці записи кешу зі сторінки CSHTML або HTML
Deepan Raj

37

Для .NET 4.5+ фреймворку

додати посилання: 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, призначені як для веб-, так і для не веб-додатків.

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


Звідки Db.GerNames()береться?
Молодший

DB.GetNames - це лише метод з DAL, який отримує деякі імена з бази даних. Це те, що ви зазвичай отримаєте.
juFo

Це має бути вгорі, оскільки воно має відповідне поточне рішення
BYISHIMO Audace

2
Дякуємо, потрібно також додати пакунок nuget System.Runtime.Caching (v4.5).
Стів Грін

26

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

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

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

У цих двох публікаціях він показує, як налаштувати цю схему, а також пояснює, чому це корисно. Використовуючи цей шаблон, ви отримуєте кешування без наявного коду, не бачивши жодної логіки кешування. По суті, ви використовуєте кешоване сховище так, ніби воно було іншим сховищем.


1
Чудові пости! Дякую, що поділились!!
Відмітити добрий

Посилання загинули станом на 31.08.2013.
CBono


Чи можете ви поділитися посиланням, де я можу прочитати про кешування програм на основі Key Value. Я не в змозі знайти посилання.
Незламний

4

Кешування AppFabric поширюється та кешує в кеш-пам’яті техніку, яка зберігає дані в парах ключових значень, використовуючи фізичну пам’ять на декількох серверах. AppFabric забезпечує покращення продуктивності та масштабованості для додатків .NET Framework. Поняття та архітектура


Це стосується Azure, а не ASP.NET MVC взагалі.
Генрі C

3

Розширення відповіді @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, щоб отримати ключ для передачі до помічника кешу. Це також полегшує передачу методу доступу до даних, оскільки вам не доведеться передавати ідентифікатор у метод обгортки ... вся справа стає дуже короткою та стійкою для більшості випадків використання.


1
У ваших визначеннях інтерфейсу відсутній параметр "durationInMinutes". ;-)
Tech0

3

Ось вдосконалення відповіді Хрвое Худо. Ця реалізація має кілька ключових удосконалень:

  • Клавіші кешу створюються автоматично на основі функції оновлення даних та переданого об'єкта, що визначає залежності
  • Пройдіть проміжок часу на будь-яку тривалість кешу
  • Використовується замок для безпеки різьби

Зауважте, що це має залежність від 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)
);

2
if (item == null)Повинен бути всередині замку. Тепер, коли це ifдо блокування, може статися стан перегонів. Або ще краще, вам слід тримати ifперед блокуванням, але перевірте, чи кеш все ще порожній, як перший рядок всередині блокування. Тому що якщо дві нитки надходять одночасно, вони обидва оновлюють кеш. Ваш поточний замок не корисний.
Аль Кепп

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
Поміркуйте, додавши пояснення
Майк Дебела

2

Я використав це таким чином, і він працює на мене. 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;

}

додаткові кошти на повністю кваліфіковані речі з повним простором імен !!
Ninjanoel

1

Я використовую два класи. Спочатку один основний об'єкт кешу:

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

0

Я скажу, що реалізація Синглтона в цій постійній проблемі з даними може бути вирішенням цієї проблеми, якщо ви вважаєте, що попередні рішення були дуже складними

 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

Це спрацювало для мене чудово, тому я рекомендую це всім, кому це може допомогти
GeraGamo


-8

Ви також можете спробувати використати кешування, вбудоване в ASP MVC:

Додайте такий атрибут до методу контролера, який ви хочете кешувати:

[OutputCache(Duration=10)]

У цьому випадку ActionResult цього буде кешований протягом 10 секунд.

Більше про це тут


4
OutputCache призначений для візуалізації дії, питання стосувалося кешування даних, а не сторінки.
Coolcoder

це поза темою, але OutputCache також кешує дані бази даних
Muflix
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.