关于asp.net mvc:如何在MVC应用程序中缓存数据

How to cache data in a MVC application

我在MVC应用程序中阅读了大量有关页面缓存和部分页面缓存的信息。 但是,我想知道如何缓存数据。

在我的场景中,我将使用LINQ to Entities(实体框架)。 在第一次调用GetNames(或任何方法)时,我想从数据库中获取数据。 我想将结果保存在缓存中,并在第二次调用时使用缓存版本(如果存在)。

任何人都可以展示一个如何工作的例子,应该在哪里实现(模型?)以及它是否可行。

我已经在传统的ASP.NET应用程序中看到了这一点,通常用于非常静态的数据。


这是我使用的一个很好的简单缓存助手类/服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System.Runtime.Caching;  

public class InMemoryCache: ICacheService
{
    public T GetOrSet< T >(string cacheKey, Func< T > getItemCallback) where T : class
    {
        T item = MemoryCache.Default.Get(cacheKey) as T;
        if (item == null)
        {
            item = getItemCallback();
            MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(10));
        }
        return item;
    }
}

interface ICacheService
{
    T GetOrSet< T >(string cacheKey, Func< T > getItemCallback) where T : class;
}

用法:

1
cacheProvider.GetOrSet("cache key", (delegate method if cache is empty));

缓存提供程序将检查缓存中是否存在名称为"cache id"的内容,如果没有,则会调用委托方法来获取数据并将其存储在缓存中。

例:

1
var products=cacheService.GetOrSet("catalog.products", ()=>productRepository.GetAll())


在模型中引用System.Web dll并使用System.Web.Caching.Cache

1
2
3
4
5
6
7
8
9
10
    public string[] GetNames()
    {
      string[] names = Cache["names"] as string[];
      if(names == null) //not in cache
      {
        names = DB.GetNames();
        Cache["names"] = names;
      }
      return names;
    }

有点简化,但我想这会奏效。这不是MVC特定的,我一直使用这种方法来缓存数据。


我指的是TT的帖子,并建议采用以下方法:

在模型中引用System.Web dll并使用System.Web.Caching.Cache

1
2
3
4
5
6
7
8
9
10
11
public string[] GetNames()
{
    var noms = Cache["names"];
    if(noms == null)
    {    
        noms = DB.GetNames();
        Cache["names"] = noms;
    }

    return ((string[])noms);
}

您不应该返回从缓存中重新读取的值,因为您永远不会知道在该特定时刻它是否仍在缓存中。即使你之前在声明中插入它,它可能已经消失或者从未被添加到缓存中 - 你只是不知道。

因此,您添加从数据库读取的数据并直接返回,而不是从缓存中重新读取。


适用于.NET 4.5+框架

添加引用:System.Runtime.Caching

添加使用声明:
using System.Runtime.Caching;

1
2
3
4
5
6
7
8
9
10
11
public string[] GetNames()
{
    var noms = System.Runtime.Caching.MemoryCache.Default["names"];
    if(noms == null)
    {    
        noms = DB.GetNames();
        System.Runtime.Caching.MemoryCache.Default["names"] = noms;
    }

    return ((string[])noms);
}

In the .NET Framework 3.5 and earlier versions, ASP.NET provided an in-memory cache implementation in the System.Web.Caching namespace. In previous versions of the .NET Framework, caching was available only in the System.Web namespace and therefore required a dependency on ASP.NET classes. In the .NET Framework 4, the System.Runtime.Caching namespace contains APIs that are designed for both Web and non-Web applications.

更多信息:

  • https://msdn.microsoft.com/en-us/library/dd997357(v=vs.110).aspx

  • https://docs.microsoft.com/en-us/dotnet/framework/performance/caching-in-net-framework-applications


史蒂夫史密斯做了两篇很棒的博客文章,演示了如何在ASP.NET MVC中使用他的CachedRepository模式。它有效地使用存储库模式,允许您在不必更改现有代码的情况下获得缓存。

Introducing the CachedRepository Pattern

Building a CachedRepository via Strategy Pattern

在这两篇文章中,他向您展示了如何设置此模式,并解释了它为何有用。通过使用此模式,您可以获得缓存,而无需现有代码查看任何缓存逻辑。实际上,您使用缓存的存储库就像它是任何其他存储库一样。


AppFabric缓存是一种分布式内存缓存技术,它使用跨多个服务器的物理内存将数据存储在键值对中。 AppFabric为.NET Framework应用程序提供了性能和可伸缩性改进。概念与架构


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
public sealed class CacheManager
{
    private static volatile CacheManager instance;
    private static object syncRoot = new Object();
    private ObjectCache cache = null;
    private CacheItemPolicy defaultCacheItemPolicy = null;

    private CacheEntryRemovedCallback callback = null;
    private bool allowCache = true;

    private CacheManager()
    {
        cache = MemoryCache.Default;
        callback = new CacheEntryRemovedCallback(this.CachedItemRemovedCallback);

        defaultCacheItemPolicy = new CacheItemPolicy();
        defaultCacheItemPolicy.AbsoluteExpiration = DateTime.Now.AddHours(1.0);
        defaultCacheItemPolicy.RemovedCallback = callback;
        allowCache = StringUtils.Str2Bool(ConfigurationManager.AppSettings["AllowCache"]); ;
    }
    public static CacheManager Instance
    {
        get
        {
            if (instance == null)
            {
                lock (syncRoot)
                {
                    if (instance == null)
                    {
                        instance = new CacheManager();
                    }
                }
            }

            return instance;
        }
    }

    public IEnumerable GetCache(String Key)
    {
        if (Key == null || !allowCache)
        {
            return null;
        }

        try
        {
            String Key_ = Key;
            if (cache.Contains(Key_))
            {
                return (IEnumerable)cache.Get(Key_);
            }
            else
            {
                return null;
            }
        }
        catch (Exception)
        {
            return null;
        }
    }

    public void ClearCache(string key)
    {
        AddCache(key, null);
    }

    public bool AddCache(String Key, IEnumerable data, CacheItemPolicy cacheItemPolicy = null)
    {
        if (!allowCache) return true;
        try
        {
            if (Key == null)
            {
                return false;
            }

            if (cacheItemPolicy == null)
            {
                cacheItemPolicy = defaultCacheItemPolicy;
            }

            String Key_ = Key;

            lock (Key_)
            {
                return cache.Add(Key_, data, cacheItemPolicy);
            }
        }
        catch (Exception)
        {
            return false;
        }
    }

    private void CachedItemRemovedCallback(CacheEntryRemovedArguments arguments)
    {
        String strLog = String.Concat("Reason:", arguments.RemovedReason.ToString()," | Key-Name:", arguments.CacheItem.Key," | Value-Object:", arguments.CacheItem.Value.ToString());
        LogManager.Instance.Info(strLog);
    }
}


这是对Hrvoje Hudo的回答的改进。此实现有几个关键改进:

  • 根据更新数据的函数和传入的指定依赖关系的对象自动创建缓存键
  • 传递任何缓存持续时间的时间跨度
  • 使用锁定线程安全

请注意,这依赖于Newtonsoft.Json来序列化dependsOn对象,但是可以轻松地将其替换为任何其他序列化方法。

ICache.cs

1
2
3
4
public interface ICache
{
    T GetOrSet< T >(Func< T > getItemCallback, object dependsOn, TimeSpan duration) where T : class;
}

InMemoryCache.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using System;
using System.Reflection;
using System.Runtime.Caching;
using Newtonsoft.Json;

public class InMemoryCache : ICache
{
    private static readonly object CacheLockObject = new object();

    public T GetOrSet< T >(Func< T > getItemCallback, object dependsOn, TimeSpan duration) where T : class
    {
        string cacheKey = GetCacheKey(getItemCallback, dependsOn);
        T item = MemoryCache.Default.Get(cacheKey) as T;
        if (item == null)
        {
            lock (CacheLockObject)
            {
                item = getItemCallback();
                MemoryCache.Default.Add(cacheKey, item, DateTime.Now.Add(duration));
            }
        }
        return item;
    }

    private string GetCacheKey< T >(Func< T > itemCallback, object dependsOn) where T: class
    {
        var serializedDependants = JsonConvert.SerializeObject(dependsOn);
        var methodType = itemCallback.GetType();
        return methodType.FullName + serializedDependants;
    }
}

用法:

1
2
3
4
5
var order = _cache.GetOrSet(
    () => _session.Set<Order>().SingleOrDefault(o => o.Id == orderId)
    , new { id = orderId }
    , new TimeSpan(0, 10, 0)
);


扩展@Hrvoje Hudo的答案......

码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;
using System.Runtime.Caching;

public class InMemoryCache : ICacheService
{
    public TValue Get<TValue>(string cacheKey, int durationInMinutes, Func<TValue> getItemCallback) where TValue : class
    {
        TValue item = MemoryCache.Default.Get(cacheKey) as TValue;
        if (item == null)
        {
            item = getItemCallback();
            MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(durationInMinutes));
        }
        return item;
    }

    public TValue Get<TValue, TId>(string cacheKeyFormat, TId id, int durationInMinutes, Func<TId, TValue> getItemCallback) where TValue : class
    {
        string cacheKey = string.Format(cacheKeyFormat, id);
        TValue item = MemoryCache.Default.Get(cacheKey) as TValue;
        if (item == null)
        {
            item = getItemCallback(id);
            MemoryCache.Default.Add(cacheKey, item, DateTime.Now.AddMinutes(durationInMinutes));
        }
        return item;
    }
}

interface ICacheService
{
    TValue Get<TValue>(string cacheKey, Func<TValue> getItemCallback) where TValue : class;
    TValue Get<TValue, TId>(string cacheKeyFormat, TId id, Func<TId, TValue> getItemCallback) where TValue : class;
}

例子

单项缓存(当每个项目根据其ID进行缓存时,因为缓存项目类型的整个目录将过于密集)。

1
Product product = cache.Get("product_{0}", productId, 10, productData.getProductById);

缓存所有的东西

1
IEnumerable<Categories> categories = cache.Get("categories", 20, categoryData.getCategories);

为何选择TId

第二个帮手特别好,因为大多数数据键不是复合的。如果经常使用复合键,可以添加其他方法。这样就可以避免执行各种字符串连接或string.Formats来获取传递给缓存助手的密钥。它还使得传递数据访问方法变得更容易,因为您不必将ID传递给包装器方法......对于大多数用例来说,整个事情变得非常简洁和一致。


我以这种方式使用它,它对我有用。
https://msdn.microsoft.com/en-us/library/system.web.caching.cache.add(v=vs.110).aspx
system.web.caching.cache.add的参数信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public string GetInfo()
{
     string name = string.Empty;
     if(System.Web.HttpContext.Current.Cache["KeyName"] == null)
     {
         name = GetNameMethod();
         System.Web.HttpContext.Current.Cache.Add("KeyName", name, null, DateTime.Noew.AddMinutes(5), Cache.NoSlidingExpiration, CacheitemPriority.AboveNormal, null);
     }
     else
     {
         name = System.Web.HttpContext.Current.Cache["KeyName"] as string;
     }

      return name;

}

我用了两节课。第一个缓存核心对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Cacher<TValue>
    where TValue : class
{
    #region Properties
    private Func<TValue> _init;
    public string Key { get; private set; }
    public TValue Value
    {
        get
        {
            var item = HttpRuntime.Cache.Get(Key) as TValue;
            if (item == null)
            {
                item = _init();
                HttpContext.Current.Cache.Insert(Key, item);
            }
            return item;
        }
    }
    #endregion

    #region Constructor
    public Cacher(string key, Func<TValue> init)
    {
        Key = key;
        _init = init;
    }
    #endregion

    #region Methods
    public void Refresh()
    {
        HttpRuntime.Cache.Remove(Key);
    }
    #endregion
}

第二个是缓存对象列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static class Caches
{
    static Caches()
    {
        Languages = new Cacher<IEnumerable<Language>>("Languages", () =>
                                                          {
                                                              using (var context = new WordsContext())
                                                              {
                                                                  return context.Languages.ToList();
                                                              }
                                                          });
    }
    public static Cacher<IEnumerable<Language>> Languages { get; private set; }
}


1
HttpContext.Current.Cache.Insert("subjectlist", subjectlist);

我会说在这个持续存在的数据问题上实现Singleton可以解决这个问题,以防您发现以前的解决方案非常复杂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 public class GPDataDictionary
{
    private Dictionary<string, object> configDictionary = new Dictionary<string, object>();

    /// <summary>
    /// Configuration values dictionary
    /// </summary>
    public Dictionary<string, object> ConfigDictionary
    {
        get { return configDictionary; }
    }

    private static GPDataDictionary instance;
    public static GPDataDictionary Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new GPDataDictionary();
            }
            return instance;
        }
    }

    // private constructor
    private GPDataDictionary() { }

}  // singleton


您还可以尝试使用ASP MVC中内置的缓存:

将以下属性添加到您要缓存的控制器方法:

1
[OutputCache(Duration=10)]

在这种情况下,ActionResult将缓存10秒。

更多关于这里