写在前面的话
写完这篇博客后,总觉得少了些什么,后来想了下,感觉自己只是把结果给亮了出来,自己为什么想到这么做,这个类库出生的缘由未详述,因此,在本段作下说明,如有不足之处,希望能和大家一起交流沟通。。,大家共同提高啊!
我的想法很简单,我在拥有:
- 获取数据的方法
并面对了下面的场景:
- 数据仅从数据库读取,不在程序中被修改
- 获取数据的方法在程序的很多地方被频繁的调用
- 我能把控数据的准确性对程序的影响
我希望:可以有一个容器,让我在程序的入口处进行数据的初始化(丢给它获取数据的方法、key、过期时间)后,我能够在程序中其它任何地方通过key来获取到数据,并且,数据能够根据我设定的过期时间进行有效地更新!
以此来达到:
- 缓存直接和方法进行绑定,而并非直接将具体值丢入缓存
- 避免在不同的页面声明相同的静态变量来获取相同的数据,减少代码量
- 统一在程序的入口管理所有的全局变量,提高代码的可维护性
- 避免频繁读取数据库,提高应用程序的性能
背景
本来想聊聊本文产生的背景的,后来发现本码农词穷了。因此,直入主题,本文的工作是利用委托实现了一个全局的数据缓存仓库。
这个类库接收4个参数:1 您要存储的数据的数据类型 2 获取需要存储数据的方法 3 过期的时间 4 读取该数据的唯一key
这个类库就能够:根据key获取数据,数据通过执行您传入的获取数据的方法来获得,当上一次获取的时间过期后,会重新执行获取数据的方法以更新数据!
推荐的使用方式:1 在应用程序的开端,进行所有需要缓存数据的初始化操作,统一管理所有的key;这样可以避免不必要的混乱
2 在应用程序中需要使用的地方,直接通过key进行数据的获取,这样避免了在每个页面中写重复的代码,提高应用的效率
实现
第一步,我们为需要存储的数据定义一个标准的数据结构:
/// <summary> /// 存储的数据结构 /// </summary> /// <typeparam name="T">需要存储的数据的数据类型(string int ..)</typeparam> public sealed class StoredDataInfo<T> { /// <summary> /// 存储的数据 /// </summary> public T Data { get; set; } /// <summary> /// 获取需要存储的数据的方法 /// </summary> public Func<T> GetDataMethod { get; set; } /// <summary> /// 数据过期的时间 /// </summary> public int TimeOfDuration { get; set; } /// <summary> /// 数据上一次被更新的时间 /// </summary> public DateTime LastModifyTime { get; set; } }
以上代码一目了然,不用多说,大家都懂得。
第二步,我们需要一个列表来存储需要的数据,因为我们会存储很多的数据
//存储所有数据 /// <summary> /// 存储所有数据 /// </summary> private static readonly Dictionary<string, StoredDataInfo<T>> EntireStoredData = new Dictionary<string, StoredDataInfo<T>>();
第三步,一个初始化数据的方法
//初始化数据项 /// <summary> /// 初始化数据项 /// </summary> /// <param name="key"></param> /// <param name="storedData"></param> private static string InitStoredDataItem(string key, StoredDataInfo<T> storedData) { lock (lockObj) { if (EntireStoredData.ContainsKey(key)) { return "key:" + key + " 已存在"; } EntireStoredData.Add(key, storedData); } return ""; }
第四步,根据key获取数据项的方法
// 获取指定key的数据项 /// <summary> /// 获取指定key的数据项 /// </summary> /// <param name="key"></param> /// <param name="isForcedRefresh">是否强制更新</param> /// <returns></returns> public static T GetData(string key, bool isForcedRefresh = false) { //不存在key if (!HasKey(key)) { #region string currKeys = ""; string currTType = ""; if (EntireStoredData.Any()) { currKeys = string.Join(",", EntireStoredData.Keys.ToArray()); var v = EntireStoredData.First().Value.Data; currTType = v.GetType().ToString(); } throw new Exception(string.Format("无指定key:{0},当前池包含key集合{1},当前池类型:{2}", key, currKeys, currTType)); #endregion } //根据key获取value StoredDataInfo<T> sdi = EntireStoredData[key]; //判断是否过期 int timeOfDuration = sdi.TimeOfDuration; DateTime lastModifyTime = sdi.LastModifyTime; if (!isForcedRefresh && DateTime.Now.AddMinutes(-timeOfDuration) <= lastModifyTime) return sdi.Data; //重新更新数据 sdi.Data = sdi.GetDataMethod(); sdi.LastModifyTime = DateTime.Now; return sdi.Data; }
使用
static void Main(string[] args) { #region 数据缓存仓库测试 //key const string key = "GetCurrDateKey"; //初始化仓库 DataWarehouse<string>.InitDataItem(key, GetCurrDate, 1); //根据key获取值 Console.WriteLine(DataWarehouse<string>.GetData(key)); //休眠 等待过期 Thread.Sleep(1000 * 61); //再次根据key获取值 Console.WriteLine(DataWarehouse<string>.GetData(key)); Console.ReadLine(); #endregion } /// <summary> /// 获取时间 /// </summary> /// <returns></returns> private static string GetCurrDate() { return DateTime.Now.ToString(); }
以上,做了一个很小的测试,存储一个当前时间的string类型的值,设定过期时间为1分钟,结果很显而易见。