asp.net-mvc 如何在 ASP.NET MVC 中缓存对象?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/445050/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-07 23:17:04  来源:igfitidea点击:

How can I cache objects in ASP.NET MVC?

asp.net-mvccaching

提问by rball

I'd like to cache objects in ASP.NET MVC. I have a BaseControllerthat I want all Controllers to inherit from. In the BaseController there is a Userproperty that will simply grab the User data from the database so that I can use it within the controller, or pass it to the views.

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

I'd like to cache this information. I'm using this information on every single page so there is no need to go to the database each page request.

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

I'd like something like:

我想要类似的东西:

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

How do I implement simple caching in ASP.NET MVC?

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

回答by

You can still use the cache (shared among all responses) and session (unique per user) for storage.

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

I like the following "try get from cache/create and store" pattern (c#-like pseudocode):

我喜欢下面的“尝试从缓存中获取/创建和存储”模式(类似 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;
  }
}

you'd use this like so:

你会像这样使用它:

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

You can adapt this pattern to the Session, ViewState (ugh) or any other cache mechanism. You can also extend the ControllerContext.HttpContext (which I think is one of the wrappers in System.Web.Extensions), or create a new class to do it with some room for mocking the cache.

您可以将此模式应用于 Session、ViewState(呃)或任何其他缓存机制。您还可以扩展 ControllerContext.HttpContext(我认为它是 System.Web.Extensions 中的包装器之一),或者创建一个新类来完成它,并留出一些空间来模拟缓存。

回答by njappboy

I took Will's answer and modified it to make the CacheExtensionsclass static and to suggest a slight alteration in order to deal with the possibility of Func<T>being null:

我接受了 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;

    }

}

I would also consider taking this a step further to implement a testable Session solution that extends the System.Web.HttpSessionStateBase abstract class.

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

}

回答by John Sheehan

If you want it cached for the length of the request, put this in your controller base class:

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

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

If you want to cache for longer, use the replace the ControllerContext call with one to Cache[]. If you do choose to use the Cache object to cache longer, you'll need to use a unique cache key as it will be shared across requests/users.

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

回答by DigitalDan

A couple of the other answers here don't deal with the following:

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

  • cache stampede
  • double check lock
  • 缓存踩踏
  • 双重检查锁

This could lead to the generator (which could take a long time) running more than once in different threads.

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

Here's my version that shouldn'tsuffer from this problem:

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

// 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.UtcNow.AddMinutes(expire.TotalMinutes), Cache.NoSlidingExpiration);
                }
            }
        }
        return (T)result;
    }
}

回答by SDReyes

@njappboy: Nice implementation. I would only defer the Generator( )invocation until the last responsible moment. thus you can cache method invocations too.

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

回答by Matthew

I like to hide the fact that the data is cached in the repository. You can access the cache through the HttpContext.Current.Cache property and store the User information using "User"+id.ToString() as the key.

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

This means that all access to the User data from the repository will use cached data if available and requires no code changes in the model, controller, or view.

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

I have used this method to correct serious performance problems on a system that was querying the database for each User property and reduced page load times from minutes to single digit seconds.

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

回答by Mehrdad Afshari

If you don't need specific invalidation features of ASP.NET caching, static fields are pretty good, lightweight and easy to use. However, as soon as you needed the advanced features, you can switch to ASP.NET's Cacheobject for storage.

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

The approach I use is to create a property and a privatefield. If the field is null, the property will fill it and return it. I also provide an InvalidateCachemethod that manually sets the field to null. The advantage of this approach it that the caching mechanism is encapsulated in the property and you can switch to a different approach if you want.

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

回答by Ilya Orlov

Implementation with a minimal cache locking. The value stored in the cache is wrapped in a container. If the value is not in the cache, then the value container is locked. The cache is locked only during the creation of the container.

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

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