网站配置之最佳实践
开发中,不论是CS还是BS,为了应对不同情况的变化,程序中 我们或多或少都会通过配置的方式来应对不同的情况,下面分享一种数据库存储配置,缓存中读取配置项的方式。
数据库脚本:
CREATE TABLE Base_Config ( UUId UniqueIdentifier Primary Key, --主键 Title nvarchar (200) , --标题 SectionName nvarchar (200) , --节点项名称 KeyName nvarchar (200) Not null, --键名 Value nvarchar (200) , --值 ConfigType int DEFAULT (0), --配置类型,键值配置为 0,二级配置为 1 Remark nvarchar (4000), --对该配置的说明性文字 Added datetime , --添加时间 Modified datetime --更改时间 )
配置类代码(注:SharpDB 是我自己封装的一个数据库操作类库,后面我会分享出来):
using SharpDB; using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Data; /// <summary> /// 配置项操作类 /// </summary> public class ConfigHelper { /// <summary> /// 防并发同步锁 /// </summary> static object locker = new object(); static Dictionary<string, string> SystemConfig = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); static Dictionary<string, NameValueCollection> SectionSystemConfig = new Dictionary<string, NameValueCollection>(StringComparer.OrdinalIgnoreCase); static ConfigHelper() { InitSystemValues(); InitSectionValues(); } /// <summary> /// 初始化所有普通键值对数据 /// </summary> /// <returns></returns> private static void InitSystemValues() { string sel_Sql = "Select KeyName,Value From Base_Config where ConfigType = 0 "; DB db = new DB(AccessType.MsSql); DataTable data = db.QueryTable(sel_Sql); if (data != null && data.Rows.Count > 0) { foreach (DataRow dr in data.Rows) { SystemConfig.Add(dr["KeyName"].ToString(), dr["Value"].ToString()); } } } /// <summary> /// 初始化所有二级键值对数据 /// </summary> private static void InitSectionValues() { string sel_Sql = "Select SectionName,KeyName,Value From Base_Config where ConfigType = 1 "; DB db = new DB(AccessType.MsSql); DataTable data = db.QueryTable(sel_Sql); if (data != null && data.Rows.Count > 0) { foreach (DataRow dr in data.Rows) { NameValueCollection nvc = null; if (SectionSystemConfig.TryGetValue(dr["SectionName"].ToString(), out nvc)) { nvc.Add(dr["KeyName"].ToString(), dr["Value"].ToString()); SectionSystemConfig.Remove(dr["SectionName"].ToString()); SectionSystemConfig.Add(dr["SectionName"].ToString(), nvc); } else { nvc = new NameValueCollection(); nvc.Add(dr["KeyName"].ToString(), dr["Value"].ToString()); SectionSystemConfig.Add(dr["SectionName"].ToString(), nvc); } } } } /// <summary> /// 二级键值配置项 /// 获取数据节点 配置值 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="sectionName">节点名称</param> /// <param name="keyName">键名称</param> /// <param name="defaultValue">默认值,未找到配置则返回默认值</param> /// <returns></returns> public static T GetValue<T>(string sectionName, string keyName, T defaultValue) { if (sectionName == null) throw new ArgumentNullException("sectionName"); if (keyName == null) throw new ArgumentNullException("keyName"); var nvc = SectionSystemConfig[sectionName]; if (nvc == null || nvc.Count <= 0) { return defaultValue; } string retVal = nvc[keyName]; if (retVal == null) { return defaultValue; } return retVal.ChangeType<T>(); } /// <summary> /// 普通键值对项 /// 获取数据节点 配置值 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="keyName">键名称</param> /// <param name="defaultValue">默认值,未找到配置则返回默认值</param> /// <returns></returns> public static T GetValue<T>(string keyName, T defaultValue) { if (keyName == null) throw new ArgumentNullException("keyName"); string retVal = SystemConfig[keyName]; if (retVal == null) { return defaultValue; } return retVal.ChangeType<T>(); } /// <summary> /// 二级键值配置项 /// 获取配置键值数据 /// </summary> /// <param name="sectionName">节点名称</param> /// <returns></returns> public static NameValueCollection GetSectionValues(string sectionName) { return SectionSystemConfig[sectionName]; } /// <summary> /// 获取所有普通键名称 /// </summary> /// <returns></returns> public static List<string> GetKeyNames() { List<string> lstKeyName = new List<string>(); foreach (var item in SystemConfig) { lstKeyName.Add(item.Key); } return lstKeyName; } /// <summary> /// 获取所有节点名称 /// </summary> /// <returns></returns> public static List<string> GetSectionNames() { List<string> lstSectionNames = new List<string>(); foreach (var item in SectionSystemConfig) { lstSectionNames.Add(item.Key); } return lstSectionNames; } /// <summary> /// 设置更改键值对数据(存在则更新,不存在则插入) /// </summary> /// <param name="sectionName">节点名称</param> /// <param name="keyName">键名称</param> /// <param name="value">值</param> /// <returns></returns> public static int SetValue(string sectionName, string keyName, object value) { lock (locker) //保证数据一致性 { if (sectionName == null) throw new ArgumentNullException("sectionName"); if (keyName == null) throw new ArgumentNullException("keyName"); sectionName = sectionName.Trim(); keyName = keyName.Trim(); value = (value == null ? string.Empty : value.ToString().Trim()); bool isUpdate = false; int ConfigType = -1; if (string.IsNullOrWhiteSpace(sectionName)) { ConfigType = 0; isUpdate = (SystemConfig[keyName] == null ? false : true); } else { ConfigType = 1; isUpdate = SectionSystemConfig.ContainsKey(sectionName); } DB db = new DB(AccessType.MsSql); string exec_Sql = string.Empty; int res = -1; if (isUpdate) { exec_Sql = "update Base_Config set value='{2}',Modified=getdate() where sectionName='{0}' and keyName ='{1}' "; exec_Sql = string.Format(exec_Sql, sectionName, keyName, value); res = db.ExecuteSql(exec_Sql); //同步更新配置数据 if (ConfigType == 0) { SystemConfig[keyName] = value.ToString(); } else { var nvc = SectionSystemConfig[sectionName]; nvc[keyName] = value.ToString(); SectionSystemConfig.Remove(sectionName); SectionSystemConfig.Add(sectionName, nvc); } } else { exec_Sql = "insert into Base_Config (uuid,SectionName,keyName,value,ConfigType,added) values (newid(),'{0}','{1}','{2}',{3},getdate())"; exec_Sql = string.Format(exec_Sql, sectionName, keyName, value, ConfigType); res = db.ExecuteSql(exec_Sql); //同步更新配置数据 if (ConfigType == 0) { SystemConfig.Add(keyName, value.ToString()); } else { NameValueCollection nvc = new NameValueCollection(); nvc.Add(keyName, value.ToString()); SectionSystemConfig.Add(sectionName, nvc); } } return res; } } /// <summary> /// 设置更改键值对数据(存在则更新,不存在则插入) /// </summary> /// <param name="keyName">键名称</param> /// <param name="value">值</param> /// <returns></returns> public static int SetValue(string keyName, object value) { return SetValue(string.Empty, keyName, value); } /// <summary> /// 删除节点键项 /// </summary> /// <param name="sectionName"></param> /// <param name="keyName"></param> /// <returns></returns> public static int DeleteKey(string keyName, string sectionName = "") { lock (locker) { int res = -1; if (sectionName == null) throw new ArgumentNullException("sectionName"); if (keyName == null) throw new ArgumentNullException("keyName"); DB db = new DB(AccessType.MsSql); string del_Sql = "delete from Base_Config where sectionName ='{0}' and keyName='{1}' "; del_Sql = string.Format(del_Sql, sectionName, keyName); res = db.ExecuteSql(del_Sql); //同步更新配置数据 if (string.IsNullOrWhiteSpace(sectionName)) { SystemConfig.Remove(keyName); } else { var nvc = SectionSystemConfig[sectionName]; nvc.Remove(keyName); SectionSystemConfig[sectionName] = nvc; } return res; } } /// <summary> /// 删除节点项 /// </summary> /// <param name="sectionName"></param> /// <returns></returns> public static int DeleteSection(string sectionName) { lock (locker) { if (sectionName == null) throw new ArgumentNullException("sectionName"); int res = -1; DB db = new DB(AccessType.MsSql); string del_Sql = "delete from Base_Config where ConfigType=1 and sectionName ='{0}' "; del_Sql = string.Format(del_Sql, sectionName); res = db.ExecuteSql(del_Sql); //同步更新配置数据 SectionSystemConfig.Remove(sectionName); return res; } } }
说明:
1. 该 SharpDB.DB 我另一个开源分享的 数据库操作类(后期公布分享),真正要使用时,替换成自己的数据库操作类即可。
2. 说是最佳实线,是因为 去除了损耗性能的 每次 读取配置 都查询的情况,而使用静态字典去存储配置的读取。
3. 使用时,如果程序代码部署在多个网站服务器,如果对配置进行了 新增,修改,删除 操作时,需要重新启动网站或回收应用程序池,配置才会生效(读取到最新的数据)。
4. 说是最佳实践,不是因为这个可以满足所有情况的 应用程序配置,而是指 这样小巧好用 性能还算Ok,这是我是一个抛砖引玉的做法;最佳实践的话,如果项目中集成有Redis,那么久可以把 该配置操作 集成到Redis中最好。