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+框架
添加引用:
添加使用声明:
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模式。它有效地使用存储库模式,允许您在不必更改现有代码的情况下获得缓存。
在这两篇文章中,他向您展示了如何设置此模式,并解释了它为何有用。通过使用此模式,您可以获得缓存,而无需现有代码查看任何缓存逻辑。实际上,您使用缓存的存储库就像它是任何其他存储库一样。
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秒。
更多关于这里