Как я могу кешировать объекты в ASP.NET MVC? - PullRequest
50 голосов
/ 15 января 2009

Я бы хотел кешировать объекты в ASP.NET MVC. У меня есть BaseController, от которого я хочу наследовать все контроллеры. В BaseController есть свойство User, которое просто извлекает данные пользователя из базы данных, чтобы я мог использовать их в контроллере или передавать их представлениям.

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

Я бы хотел что-то вроде:

if(_user is null)
  GrabFromDatabase
  StuffIntoCache
return CachedObject as User

Как реализовать простое кэширование в ASP.NET MVC?

Ответы [ 8 ]

68 голосов
/ 15 января 2009

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

Мне нравится следующий шаблон "try get from cache / create and store" (псевдокод, подобный c #):

public static class CacheExtensions
{
  public static T GetOrStore<T>(this Cache cache, string key, Func<T> generator)
  {
    var result = cache[key];
    if(result == null)
    {
      result = generator();
      cache[key] = result;
    }
    return (T)result;
  }
}

вы бы использовали это так:

var user = HttpRuntime
              .Cache
              .GetOrStore<User>(
                 $"User{_userId}", 
                 () => Repository.GetUser(_userId));

Вы можете адаптировать этот шаблон для Session, ViewState (ugh) или любого другого механизма кэширования. Вы также можете расширить ControllerContext.HttpContext (который, я думаю, является одной из оболочек в System.Web.Extensions) или создать новый класс, чтобы сделать это с некоторым пространством для насмешки в кэше.

59 голосов
/ 09 июня 2009

Я взял ответ Уилла и изменил его, чтобы сделать класс CacheExtensions статичным и предложить небольшое изменение, чтобы справиться с возможностью того, что Func<T> будет null:

public static class CacheExtensions
{

    private static object sync = new object();
    public const int DefaultCacheExpiration = 20;

    /// <summary>
    /// Allows Caching of typed data
    /// </summary>
    /// <example><![CDATA[
    /// var user = HttpRuntime
    ///   .Cache
    ///   .GetOrStore<User>(
    ///      string.Format("User{0}", _userId), 
    ///      () => Repository.GetUser(_userId));
    ///
    /// ]]></example>
    /// <typeparam name="T"></typeparam>
    /// <param name="cache">calling object</param>
    /// <param name="key">Cache key</param>
    /// <param name="generator">Func that returns the object to store in cache</param>
    /// <returns></returns>
    /// <remarks>Uses a default cache expiration period as defined in <see cref="CacheExtensions.DefaultCacheExpiration"/></remarks>
    public static T GetOrStore<T>( this Cache cache, string key, Func<T> generator ) {
        return cache.GetOrStore( key, (cache[key] == null && generator != null) ? generator() : default( T ), DefaultCacheExpiration );
    }


    /// <summary>
    /// Allows Caching of typed data
    /// </summary>
    /// <example><![CDATA[
    /// var user = HttpRuntime
    ///   .Cache
    ///   .GetOrStore<User>(
    ///      string.Format("User{0}", _userId), 
    ///      () => Repository.GetUser(_userId));
    ///
    /// ]]></example>
    /// <typeparam name="T"></typeparam>
    /// <param name="cache">calling object</param>
    /// <param name="key">Cache key</param>
    /// <param name="generator">Func that returns the object to store in cache</param>
    /// <param name="expireInMinutes">Time to expire cache in minutes</param>
    /// <returns></returns>
    public static T GetOrStore<T>( this Cache cache, string key, Func<T> generator, double expireInMinutes ) {
        return cache.GetOrStore( key,  (cache[key] == null && generator != null) ? generator() : default( T ), expireInMinutes );
    }


    /// <summary>
    /// Allows Caching of typed data
    /// </summary>
    /// <example><![CDATA[
    /// var user = HttpRuntime
    ///   .Cache
    ///   .GetOrStore<User>(
    ///      string.Format("User{0}", _userId),_userId));
    ///
    /// ]]></example>
    /// <typeparam name="T"></typeparam>
    /// <param name="cache">calling object</param>
    /// <param name="key">Cache key</param>
    /// <param name="obj">Object to store in cache</param>
    /// <returns></returns>
    /// <remarks>Uses a default cache expiration period as defined in <see cref="CacheExtensions.DefaultCacheExpiration"/></remarks>
    public static T GetOrStore<T>( this Cache cache, string key, T obj ) {
        return cache.GetOrStore( key, obj, DefaultCacheExpiration );
    }

    /// <summary>
    /// Allows Caching of typed data
    /// </summary>
    /// <example><![CDATA[
    /// var user = HttpRuntime
    ///   .Cache
    ///   .GetOrStore<User>(
    ///      string.Format("User{0}", _userId), 
    ///      () => Repository.GetUser(_userId));
    ///
    /// ]]></example>
    /// <typeparam name="T"></typeparam>
    /// <param name="cache">calling object</param>
    /// <param name="key">Cache key</param>
    /// <param name="obj">Object to store in cache</param>
    /// <param name="expireInMinutes">Time to expire cache in minutes</param>
    /// <returns></returns>
    public static T GetOrStore<T>( this Cache cache, string key, T obj, double expireInMinutes ) {
        var result = cache[key];

        if ( result == null ) {

            lock ( sync ) {
                result = cache[key];
                if ( result == null ) {
                    result = obj != null ? obj : default( T );
                    cache.Insert( key, result, null, DateTime.Now.AddMinutes( expireInMinutes ), Cache.NoSlidingExpiration );
                }
            }
        }

        return (T)result;

    }

}

Я бы также рассмотрел этот шаг для реализации тестируемого решения Session, расширяющего абстрактный класс System.Web.HttpSessionStateBase.

public static class SessionExtension
{
    /// <summary>
    /// 
    /// </summary>
    /// <example><![CDATA[
    /// var user = HttpContext
    ///   .Session
    ///   .GetOrStore<User>(
    ///      string.Format("User{0}", _userId), 
    ///      () => Repository.GetUser(_userId));
    ///
    /// ]]></example>
    /// <typeparam name="T"></typeparam>
    /// <param name="cache"></param>
    /// <param name="key"></param>
    /// <param name="generator"></param>
    /// <returns></returns>
    public static T GetOrStore<T>( this HttpSessionStateBase session, string name, Func<T> generator ) {

        var result = session[name];
        if ( result != null )
            return (T)result;

        result = generator != null ? generator() : default( T );
        session.Add( name, result );
        return (T)result;
    }

}
6 голосов
/ 15 января 2009

Если вы хотите, чтобы он кэшировался для длины запроса, поместите это в базовый класс вашего контроллера:

public User User {
    get {
        User _user = ControllerContext.HttpContext.Items["user"] as User;

        if (_user == null) {
            _user = _repository.Get<User>(id);
            ControllerContext.HttpContext.Items["user"] = _user;
        }

        return _user;
    }
}

Если вы хотите кэшировать дольше, используйте вызов ControllerContext вместо одного к Cache []. Если вы решите использовать объект Cache для более длительного кеширования, вам нужно будет использовать уникальный ключ кеша, так как он будет распространяться среди запросов / пользователей.

3 голосов
/ 24 февраля 2017

Несколько других ответов здесь не касаются следующего:

  • Паническое бегство в кеше
  • двойная проверка блокировки

Это может привести к тому, что генератор (который может занять много времени) будет работать более одного раза в разных потоках.

Вот моя версия, что не должен страдать от этой проблемы:

// using System;
// using System.Web.Caching;

// https://stackoverflow.com/a/42443437
// Usage: HttpRuntime.Cache.GetOrStore("myKey", () => GetSomethingToCache());

public static class CacheExtensions
{
    private static readonly object sync = new object();
    private static TimeSpan defaultExpire = TimeSpan.FromMinutes(20);

    public static T GetOrStore<T>(this Cache cache, string key, Func<T> generator) =>
        cache.GetOrStore(key, generator, defaultExpire);

    public static T GetOrStore<T>(this Cache cache, string key, Func<T> generator, TimeSpan expire)
    {
        var result = cache[key];
        if (result == null)
        {
            lock (sync)
            {
                result = cache[key];
                if (result == null)
                {
                    result = generator();
                    cache.Insert(key, result, null, DateTime.Now.AddMinutes(expire.TotalMinutes), Cache.NoSlidingExpiration);
                }
            }
        }
        return (T)result;
    }
}
3 голосов
/ 12 августа 2010

@ njappboy: Хорошая реализация. Я бы только отложил вызов Generator( ) до последнего ответственного момента. таким образом, вы также можете кэшировать вызовы методов.

/// <summary>
/// Allows Caching of typed data
/// </summary>
/// <example><![CDATA[
/// var user = HttpRuntime
///   .Cache
///   .GetOrStore<User>(
///      string.Format("User{0}", _userId), 
///      () => Repository.GetUser(_userId));
///
/// ]]></example>
/// <typeparam name="T"></typeparam>
/// <param name="Cache">calling object</param>
/// <param name="Key">Cache key</param>
/// <param name="Generator">Func that returns the object to store in cache</param>
/// <returns></returns>
/// <remarks>Uses a default cache expiration period as defined in <see cref="CacheExtensions.DefaultCacheExpiration"/></remarks>
public static T GetOrStore<T>( this Cache Cache, string Key, Func<T> Generator )
{
    return Cache.GetOrStore( Key, Generator, DefaultCacheExpiration );
}

/// <summary>
/// Allows Caching of typed data
/// </summary>
/// <example><![CDATA[
/// var user = HttpRuntime
///   .Cache
///   .GetOrStore<User>(
///      string.Format("User{0}", _userId), 
///      () => Repository.GetUser(_userId));
///
/// ]]></example>
/// <typeparam name="T"></typeparam>
/// <param name="Cache">calling object</param>
/// <param name="Key">Cache key</param>
/// <param name="Generator">Func that returns the object to store in cache</param>
/// <param name="ExpireInMinutes">Time to expire cache in minutes</param>
/// <returns></returns>
public static T GetOrStore<T>( this Cache Cache, string Key, Func<T> Generator, double ExpireInMinutes )
{
    var Result = Cache [ Key ];

    if( Result == null )
    {
        lock( Sync )
        {
            if( Result == null )
            {
                Result = Generator( );
                Cache.Insert( Key, Result, null, DateTime.Now.AddMinutes( ExpireInMinutes ), Cache.NoSlidingExpiration );
            }
        }
    }

    return ( T ) Result;
}
3 голосов
/ 15 января 2009

Мне нравится скрывать тот факт, что данные кэшируются в хранилище. Вы можете получить доступ к кешу через свойство HttpContext.Current.Cache и сохранить информацию о пользователе, используя в качестве ключа «Пользователь» + id.ToString ().

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

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

2 голосов
/ 15 января 2009

Если вам не нужны специальные функции аннулирования кэширования ASP.NET, статические поля довольно хороши, легки и просты в использовании. Однако, как только вам понадобятся расширенные функции, вы можете переключиться на объект ASP.NET Cache для хранения.

Подход, который я использую, заключается в создании свойства и поля private. Если поле равно null, свойство заполнит его и вернет. Я также предоставляю метод InvalidateCache, который вручную устанавливает поле на null. Преимущество этого подхода в том, что механизм кэширования инкапсулирован в свойстве, и вы можете переключиться на другой подход, если хотите.

1 голос
/ 28 октября 2011

Реализация с минимальной блокировкой кеша. Значение, хранящееся в кэше, помещается в контейнер. Если значение отсутствует в кэше, контейнер значений блокируется. Кеш блокируется только при создании контейнера.

public static class CacheExtensions
{
    private static object sync = new object();

    private class Container<T>
    {
        public T Value;
    }

    public static TValue GetOrStore<TValue>(this Cache cache, string key, Func<TValue> create, TimeSpan slidingExpiration)
    {
        return cache.GetOrStore(key, create, Cache.NoAbsoluteExpiration, slidingExpiration);
    }

    public static TValue GetOrStore<TValue>(this Cache cache, string key, Func<TValue> create, DateTime absoluteExpiration)
    {
        return cache.GetOrStore(key, create, absoluteExpiration, Cache.NoSlidingExpiration);
    }

    public static TValue GetOrStore<TValue>(this Cache cache, string key, Func<TValue> create, DateTime absoluteExpiration, TimeSpan slidingExpiration)
    {
        return cache.GetOrCreate(key, x => create());
    }

    public static TValue GetOrStore<TValue>(this Cache cache, string key, Func<string, TValue> create, DateTime absoluteExpiration, TimeSpan slidingExpiration)
    {
        var instance = cache.GetOrStoreContainer<TValue>(key, absoluteExpiration, slidingExpiration);
        if (instance.Value == null)
            lock (instance)
                if (instance.Value == null)
                    instance.Value = create(key);

        return instance.Value;
    }

    private static Container<TValue> GetOrStoreContainer<TValue>(this Cache cache, string key, DateTime absoluteExpiration, TimeSpan slidingExpiration)
    {
        var instance = cache[key];
        if (instance == null)
            lock (cache)
            {
                instance = cache[key];
                if (instance == null)
                {
                    instance = new Container<TValue>();

                    cache.Add(key, instance, null, absoluteExpiration, slidingExpiration, CacheItemPriority.Default, null);
                }
            }

        return (Container<TValue>)instance;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...