Подтвердить что ты не робот

Как я могу кэшировать объекты в ASP.NET MVC?

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

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

Мне хотелось бы что-то вроде:

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

Я взял "Ответ" и изменил его, чтобы сделать класс 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;

    }

}

Я также рассмотрел бы этот шаг еще раз для реализации тестируемого решения сеанса, которое расширяет абстрактный класс 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;
    }

}

Ответ 3

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

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 для кэширования дольше, вам нужно будет использовать уникальный кеш-ключ, поскольку он будет использоваться для всех пользователей/пользователей.

Ответ 4

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

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

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

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

Ответ 8

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

  • cache stampede
  • блокировка двойной проверки

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

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

// using System.Web.Caching;
public static class CacheExtensions
{
    private static 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;
    }
}