首页 文章

如何在 ASP.NET MVC 中缓存对象?

提问于
浏览
50

我想在 ASP.NET MVC 中缓存对象。我有BaseController,我希望所有控制器继承。在 BaseController 中有一个User属性,它只是从数据库中获取用户数据,这样我就可以在控制器中使用它,或者将它传递给视图。

我想缓存这些信息。我在每个页面上都使用此信息,因此无需每个页面请求转到数据库。

我喜欢这样的东西:

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

如何在 ASP.NET MVC 中实现简单缓存?

8 回答

  • 69

    您仍然可以使用缓存(在所有响应中共享)和会话(每个用户唯一)进行存储。

    我喜欢以下“尝试从 cache/create 和存储”模式(c#-like 伪代码):

    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

    我接受了 Will 的回答并对其进行了修改以使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 抽象类的可测试的 Session 解决方案。

    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

    如果您希望缓存请求的长度,请将其放在控制器基类中:

    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 [14]。如果您确实选择使用 Cache 对象进行更长时间的缓存,则需要使用唯一的缓存键,因为它将在 requests/users 之间共享。

  • 3

    我喜欢隐藏数据缓存在存储库中的事实。您可以通过 HttpContext.Current.Cache 属性访问缓存,并使用“User”id.ToString()作为密钥存储用户信息。

    这意味着对存储库中的用户数据的所有访问都将使用缓存数据(如果可用),并且不需要在模型,控制器或视图中更改代码。

    我已经使用此方法来纠正系统上的严重性能问题,该系统正在查询每个 User 属性的数据库,并将页面加载时间从几分钟减少到单位数秒。

  • 3

    @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

    这里的其他几个答案不涉及以下内容:

    • 缓存踩踏事件

    • 双重检查锁定

    这可能导致生成器(可能需要很长时间)在不同的线程中运行多次。

    这是我的版本不应该遇到这个问题:

    // 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;
        }
    }
    
  • 2

    如果您不需要 ASP.NET 缓存的特定失效功能,静态字段非常好,轻量级且易于使用。但是,只要您需要高级功能,就可以切换到 ASP.NET 的Cache对象进行存储。

    我使用的方法是创建属性和private字段。如果该字段为null,则该属性将填充并返回该字段。我还提供了一个InvalidateCache方法,手动将字段设置为null。这种方法的优点是缓存机制封装在属性中,如果需要,可以切换到不同的方法。

  • 1

    使用最小的缓存锁定实现。存储在缓存中的值包装在容器中。如果该值不在缓存中,则值容器将被锁定。仅在创建容器期间锁定缓存。

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

相关问题