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

rba*_*all 51 asp.net-mvc caching

我想在ASP.NET MVC中缓存对象.我有一个BaseController我希望所有控制器继承自己的东西.在BaseController中有一个User属性,它将简单地从数据库中获取用户数据,以便我可以在控制器中使用它,或将其传递给视图.

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

我喜欢这样的东西:

if(_user is null)
  GrabFromDatabase
  StuffIntoCache
return CachedObject as User
Run Code Online (Sandbox Code Playgroud)

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

小智 71

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

我喜欢以下"尝试从缓存/创建和存储"模式(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;
  }
}
Run Code Online (Sandbox Code Playgroud)

你会这样使用它:

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

您可以将此模式调整为Session,ViewState(ugh)或任何其他缓存机制.您还可以扩展ControllerContext.HttpContext(我认为它是System.Web.Extensions中的包装器之一),或者创建一个新类来实现它,并为模拟缓存提供了一些空间.

  • 不要使用cache [key] = result而是使用Cache.Insert(...),因为你可以在Insert中放置依赖项,过期策略等. (13认同)

nja*_*boy 59

我把威尔的答案,并修改了它,使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;

    }

}
Run Code Online (Sandbox Code Playgroud)

我还考虑进一步实现一个可测试的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;
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 我认为您的代码中存在错误.即使缓存存在,也会调用该函数.将`generator!= null`改为`(cache [key] == null && generator!= null)`来修复bug. (2认同)

Joh*_*han 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

如果要缓存更长时间,请使用将ControllerContext调用替换为Cache [].如果您确实选择使用Cache对象进行更长时间的缓存,则需要使用唯一的缓存密钥,因为它将在请求/用户之间共享.

  • 我回家后试试这个。谢谢! (2认同)
  • 已经有一段时间了,您回家了吗?:) (2认同)

Dig*_*Dan 6

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

  • 缓存踩踏
  • 双重检查锁

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

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

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

// /sf/answers/2971040621/
// 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.UtcNow.AddMinutes(expire.TotalMinutes), Cache.NoSlidingExpiration);
                }
            }
        }
        return (T)result;
    }
}
Run Code Online (Sandbox Code Playgroud)