1:阐述问题
2:分析问题,解决问题
3:演示解决方案
有时候,我们会遇上这样一个问题:有很多条件 condition1 、condition2 、condition3、condition4 、condition5......这些条件各不相同,可能同时配置其中几个,这几个条件有一个交集,交集内部就是我们需要的。
给一个实例吧。用户在系统中配置了一个时间条件集合,用户可以按照年、月、周或者日来配置,按照其中一种来配置,下面有很多条件可以选择,其中开始日期和时间是必须配置的,最后会形成一个xml信息存储在数据库里面,我们会用当前时间判断每个用户的配置条件,如何符合,我们把他的邮箱拿出放到一个字符串尾部,不符合则不管,最后这个字符串就是所有符合用户配置条件的邮箱集合,我们可以把我们的信息推送给这些用户。其中xml按照月配置的如下:
class="code_img_closed" src="/Upload/Images/2013092123/0015B68B3C38AA5B.gif" alt="" />logs_code_hide('7950ccdc-3f22-42e9-b81c-7253fd05ae9d',event)" src="/Upload/Images/2013092123/2B1B950FA3DF188F.gif" alt="" />1 <ScheduleDefinition xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 2 <StartDateTime xmlns="http://schemas.microsoft.com/sqlserver/reporting/2010/03/01/ReportServer">2012-07-21T22:00:00.000+08:00</StartDateTime> 3 <MonthlyDOWRecurrence xmlns="http://schemas.microsoft.com/sqlserver/reporting/2010/03/01/ReportServer"> 4 <WhichWeek>FirstWeek</WhichWeek> 5 <DaysOfWeek> 6 <Monday>true</Monday> 7 </DaysOfWeek> 8 <MonthsOfYear> 9 <January>true</January> 10 <February>true</February> 11 <March>true</March> 12 <April>true</April> 13 <May>true</May> 14 <June>true</June> 15 <July>true</July> 16 <August>true</August> 17 <September>true</September> 18 <October>true</October> 19 <November>true</November> 20 <December>true</December> 21 </MonthsOfYear> 22 </MonthlyDOWRecurrence> 23 </ScheduleDefinition>View Code
StartDateTime是开始时间,它是一个基本条件,每个配置信息里面都必须存在。MonthlyDOWRecurrence是代表是按照月来配置的(也可能是WeeklyRecurrence周、DailyRecurrence日等)这个xml节点下面存在很多条件,例如WhichWeek是代表哪一周,DaysOfWeek表示每周的哪一天,又或者MonthsOfYear是每年的哪几个月。
问题就是这样,我从数据库MatchData得到所有用户的配置的数据集 Id、Email、MatchData(sql :SELECT [Id],[Email],[MatchData]FROM [MatchData].[dbo].[EmailInTime]),MatchData就是我们的配置xml信息,从集合中找出所有符合条件的用户Email。
如果你遇上了啦,该如何处理呢?下面是我的处理方法,如果你有更好的办法,请给我留意,不吝赐教,谢谢!
有人可以会总结一下有多少个条件,例如10个condition,然后写成是个if语句从上到下去判断是否存在这个条件,如果存在,判断是否当前时间符合这个条件。
1 private bool IsMatchWithMatchData(string matchData, DateTime nowDt) 2 { 3 bool result=true; 4 if(condition1 in matchData) 5 { 6 if(nowDt is match matchData by condition1) 7 { 8 result+=true 9 } 10 else 11 { 12 result+=false; 13 } 14 } 15 16 if(condition2 in matchData) 17 { 18 if(nowDt is match matchData by condition2) 19 { 20 result+=true 21 } 22 else 23 { 24 result+=false; 25 } 26 } 27 //. 28 //.条件3到9 29 //. 30 if(condition10 in matchData) 31 { 32 if(nowDt is match matchData by condition10) 33 { 34 result+=true 35 } 36 else 37 { 38 result+=false; 39 } 40 } 41 return result; 42 }View Code
这是一个伪代码,简单模拟了一下,大家都会发现很多问题,
1:判断是否存在这个条件在xml里面,耗性能;
2:有很多条件是不用去判断的,最好是xml配置文件里面有什么条件,我们就去判断什么条件;
3:扩展性差,当我们需要条件更多的条件的时候,需要修改已有的代码。
如何解决这三个问题呢?我们可以去遍历xml配置信息,我们遇到什么条件的时候,我们反射到封装好的的条件方法里面。遍历xml,我们就可以解决第1个问题;遇上什么条件我们判断什么条件,这个可以解决第2个问题;利用每个条件都封装成方法,遇到条件的时候反射到对应的方法,就解决了第3个问题。
下面我把解决方案的数据库和代码展现给大家,希望能看到你的宝贵意见!
数据库结构如下:
数据库在代码的App_Data文件夹下,下载代码可以得到。
定义好数据库后,我们看一下解决方案:
工程下面包含三个类SqlHelper、AnalyseMatchData和MatchWithMatchData:SqlHelper类是网上下载的帮助工具类,用于操作数据库;AnalyseMatchData类只包含一个公共的方法GetMailAddressByMatchData,作为对外提供的API接口;MatchWithMatchData类是程序集内部类internal class ,因为我们一个会分层,把这下代码放在一个类库里面,这里面包含的是每个条件反射的方法。然后就是一个web.config配置文件和一个Index页面,Index页面用于测试效果,web.config配置文件里面当然就是连接字符串了啦,如果要用本人的代码就需要附加数据库和修改配置的连接字符串了,相信对大家都是小KS了。
1 private readonly string connectionStr; 2 private readonly string matchDataSqlStr; 3 MatchWithMatchData matchFunction;//use to reflect the xml node to the corresponding function 4 Type matchType;//the MatchWithMatchData class 's type 5 XmlDocument xmldoc; 6 DateTime nowDt; 7 8 public AnalyseMatchData() 9 { 10 connectionStr = System.Configuration.ConfigurationManager.ConnectionStrings["MatchData"].ConnectionString; 11 matchDataSqlStr = "SELECT [Id],[Email],[MatchData]FROM [MatchData].[dbo].[EmailInTime]"; 12 matchFunction = new MatchWithMatchData(); 13 matchType = matchFunction.GetType(); 14 xmldoc = new XmlDocument(); 15 }
首先是定义需要的使用的全局变量,然后再构造函数里面初始化变量,细心的朋友会看到当前时间nowDt变量没有初始化,因为应该放在后面的api接口里面,当前时间应该是用户调用接口的时间。
1 /// <summary> 2 /// the api to get the email address of match all conditions 3 /// </summary> 4 /// <returns></returns> 5 public string GetMailAddressByMatchData() 6 { 7 StringBuilder emailAddresses = new StringBuilder(); 8 nowDt = Convert.ToDateTime("2013-06-26T22:15:00.000+08:00");//DateTime.Now;// 9 using (SqlDataReader dr = SqlHelper.ExecuteReader(connectionStr, CommandType.Text, matchDataSqlStr)) 10 { 11 while (dr.Read()) 12 { 13 string matchData = dr.GetString(dr.GetOrdinal("MatchData")); 14 if (IsMatchWithMatchData(matchData, nowDt)) 15 { 16 string emails = dr.GetString(dr.GetOrdinal("Email")); 17 emailAddresses.Append(string.IsNullOrWhiteSpace(emails) ? string.Empty : ";" + emails); 18 } 19 } 20 } 21 return emailAddresses.Length > 0 ? emailAddresses.Remove(0, 1).ToString() : string.Empty; 22 } 23 /// <summary> 24 /// judge whether the current time match all conditions in the xml string 'matchData' 25 /// </summary> 26 /// <param name="matchData"></param> 27 /// <param name="nowDt"></param> 28 /// <returns></returns> 29 private bool IsMatchWithMatchData(string matchData, DateTime nowDt) 30 { 31 bool result = true; 32 xmldoc.LoadXml(matchData); 33 try 34 { 35 XmlNodeList xmllist = xmldoc.SelectSingleNode("ScheduleDefinition").ChildNodes; 36 DateTime startDt = Convert.ToDateTime(xmllist[0].InnerText).ToLocalTime(); 37 matchFunction.InitData(startDt, nowDt); 38 foreach (XmlNode xmlnode in xmllist) 39 { 40 MethodInfo method = matchType.GetMethod(xmlnode.Name + "Function", BindingFlags.Instance | BindingFlags.Public); 41 object oj = method.Invoke(matchFunction, new object[] { xmlnode.OuterXml }); 42 if (result) 43 result = result && Convert.ToBoolean(oj); 44 else 45 break; 46 } 47 return result; 48 } 49 catch 50 { 51 return false; 52 } 53 }
GetMailAddressByMatchData方法是对应的 api接口他需要遍历从数据库里面得到的数据集,最后把符合条件的email地址返回给调用方。其中比较关键的是IsMatchWithMatchData方法用于判断xml配置条件和当前的时间nowDt的比较,如何前期时间在配置的时间条件内返回为真true否则为假false。遍历xml节点,反射到对应的方法,方法的名称是很有讲究的,是节点名称后面加一个后缀xmlnode.Name + "Function"。
这样我们达到了业务的分离,每一个方法表示一个条件业务,需要扩展的时候,新增一种xml节点类型,然后新添加一个方法,其他的一概不用管,是不是很方便!
有人说反射消耗性能,何不使用switch想简单工厂一样,遇到什么节点调用什么方法。其实我不太提倡这种方式:
1:扩张性没有反射好,扩展的时候需要修改代码,不符合开闭原则;
2:xml现在是两层条件,如果用switch,需要嵌套地使用switch了,如果以后变成三层的呢?所有灵活度也不如反射。
3:其实现在反射的性能愈来愈好啦,几乎和一般的方式调用很接近了。我曾使用一万六千多条在3秒内完成,相信我们的员工没有这么多吧!也可以做一些其他的优化,例如利用缓冲,还有及时条件中只要一个条件为false,就直接返回为假,不用检测所有的XML里面的条件,相信你有更多的方式优化它。
反射的方法类MatchWithMatchData里面:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Reflection; 5 using System.Web; 6 using System.Xml; 7 8 namespace MatchData 9 { 10 /// <summary> 11 /// determine whether the current now match with the condition of the xml string 12 /// </summary> 13 internal class MatchWithMatchData 14 { 15 DateTime startDt; 16 DateTime nowDt; 17 /// <summary> 18 /// initialize data 19 /// </summary> 20 /// <param name="startDt">start datetime</param> 21 /// <param name="nowDt">now datetime</param> 22 public void InitData(DateTime startDt, DateTime nowDt) 23 { 24 this.startDt = startDt; 25 this.nowDt = nowDt; 26 } 27 28 public bool StartDateTimeFunction(string xml) 29 { 30 XmlDocument xmldoc = new XmlDocument(); 31 xmldoc.LoadXml(xml); 32 //whether the date and time is in the range of startDateTime 33 if (nowDt.Date >= startDt.Date && Math.Abs((startDt.TimeOfDay - nowDt.TimeOfDay).TotalSeconds) < 20) 34 return true; 35 else 36 return false; 37 } 38 39 public bool EndDateFunction(string xml) 40 { 41 XmlDocument xmldoc = new XmlDocument(); 42 xmldoc.LoadXml(xml); 43 DateTime dt = DateTime.Parse(xmldoc["EndDate"].InnerText).ToLocalTime(); 44 if (nowDt.Date <= dt.Date) 45 return true; 46 else 47 return false; 48 } 49 //----------------------------------------------------------------------------- 50 public bool MonthlyDOWRecurrenceFunction(string xml) 51 { 52 bool result = true; 53 XmlDocument xmldoc = new XmlDocument(); 54 xmldoc.LoadXml(xml); 55 foreach (XmlNode xmlnode in xmldoc.ChildNodes[0].ChildNodes) 56 { 57 MethodInfo method = this.GetType().GetMethod(xmlnode.Name + "Function", BindingFlags.Instance | BindingFlags.Public); 58 object oj = method.Invoke(this, new object[] { xmlnode.OuterXml }); 59 if (result) 60 result = result && Convert.ToBoolean(oj); 61 else 62 break; 63 } 64 return result; 65 } 66 67 public bool MonthlyRecurrenceFunction(string xml) 68 { 69 return MonthlyDOWRecurrenceFunction(xml); 70 } 71 72 public bool WeeklyRecurrenceFunction(string xml) 73 { 74 return MonthlyDOWRecurrenceFunction(xml); 75 } 76 77 public bool DailyRecurrenceFunction(string xml) 78 { 79 return MonthlyDOWRecurrenceFunction(xml); 80 } 81 //------------------------------------------------------------------------------- 82 // Sub methods to handle sub options 83 //------------------------------------------------------------------------------- 84 public bool WhichWeekFunction(string xml) 85 { 86 XmlDocument xmldoc = new XmlDocument(); 87 xmldoc.LoadXml(xml); 88 string WhichWeek = xmldoc["WhichWeek"].InnerText; 89 double r = (nowDt.Day - (double)nowDt.DayOfWeek);//the month's day of last week sunday 90 int nowWeekInt = Convert.ToInt32(Math.Ceiling((r < 0 ? 0 : r) / 7)) + 1;//the index of the current week of the current month 91 return WhichWeek == WhichWeekToName(nowWeekInt); 92 } 93 /// <summary> 94 /// whether current datetime accord with the condition of every month's days 95 /// </summary> 96 /// <param name="xml"></param> 97 /// <returns></returns> 98 public bool DaysFunction(string xml) 99 { 100 XmlDocument xmldoc = new XmlDocument(); 101 xmldoc.LoadXml(xml); 102 string day = xmldoc["Days"].InnerText; 103 string[] days = day.Split(','); 104 foreach (string dy in days) 105 { 106 if (dy.Length >= 3) 107 { 108 string[] dys = dy.Split('-'); 109 if (nowDt.Day >= Convert.ToInt32(dys[0]) && nowDt.Day <= Convert.ToInt32(dys[1])) 110 return true; 111 } 112 else 113 { 114 if (nowDt.Day == Convert.ToInt32(dy)) 115 return true; 116 } 117 } 118 return false; 119 } 120 121 public bool WeeksIntervalFunction(string xml) 122 { 123 XmlDocument xmldoc = new XmlDocument(); 124 xmldoc.LoadXml(xml); 125 int weeksInterval = Convert.ToInt32(xmldoc["WeeksInterval"].InnerText) + 1; 126 double ts = (Math.Abs((nowDt - startDt).TotalDays) + Convert.ToInt32(startDt.DayOfWeek) - 7) / 7 % (weeksInterval); 127 return ts < 1; 128 } 129 130 public bool DaysIntervalFunction(string xml) 131 { 132 XmlDocument xmldoc = new XmlDocument(); 133 xmldoc.LoadXml(xml); 134 int daysInterval = Convert.ToInt32(xmldoc["DaysInterval"].InnerText); 135 double ts = Math.Abs((nowDt - startDt).TotalDays) % daysInterval; 136 return ts < 1; 137 } 138 139 public bool MonthsOfYearFunction(string xml) 140 { 141 XmlDocument xmldoc = new XmlDocument(); 142 xmldoc.LoadXml(xml); 143 string monthStr = MonthToName(nowDt.Month); 144 XmlElement nodeElement = xmldoc["MonthsOfYear"][monthStr]; 145 if (nodeElement != null && Convert.ToBoolean(nodeElement.InnerText)) 146 return true; 147 else 148 return false; 149 } 150 151 public bool DaysOfWeekFunction(string xml) 152 { 153 XmlDocument xmldoc = new XmlDocument(); 154 xmldoc.LoadXml(xml); 155 string weekStr = WeekToName(Convert.ToInt32(nowDt.DayOfWeek)); 156 XmlElement nodeElement = xmldoc["DaysOfWeek"][weekStr]; 157 if (nodeElement != null && Convert.ToBoolean(nodeElement.InnerText)) 158 return true; 159 else 160 return false; 161 } 162 //------------------------------------------------------------------------------- 163 private string WhichWeekToName(int weekOfMonth) 164 { 165 string nowWeekStr = "LastWeek"; 166 switch (weekOfMonth) 167 { 168 case 1: 169 nowWeekStr = "FirstWeek"; 170 break; 171 case 2: 172 nowWeekStr = "SecondWeek"; 173 break; 174 case 3: 175 nowWeekStr = "ThreeWeek"; 176 break; 177 case 4: 178 nowWeekStr = "FourtWeek"; 179 break; 180 default: 181 break; 182 } 183 return nowWeekStr; 184 } 185 186 private string MonthToName(int month) 187 { 188 string monthStr = "December"; 189 switch (month) 190 { 191 case 1: 192 monthStr = "January"; 193 break; 194 case 2: 195 monthStr = "February"; 196 break; 197 case 3: 198 monthStr = "March"; 199 break; 200 case 4: 201 monthStr = "April"; 202 break; 203 case 5: 204 monthStr = "May"; 205 break; 206 case 6: 207 monthStr = "June"; 208 break; 209 case 7: 210 monthStr = "July"; 211 break; 212 case 8: 213 monthStr = "August"; 214 break; 215 case 9: 216 monthStr = "September"; 217 break; 218 case 10: 219 monthStr = "October"; 220 break; 221 case 11: 222 monthStr = "November"; 223 break; 224 default: 225 break; 226 227 } 228 return monthStr; 229 } 230 231 private string WeekToName(int week) 232 { 233 string weekStr = "Sunday"; 234 switch (week) 235 { 236 case 1: 237 weekStr = "Monday"; 238 break; 239 case 2: 240 weekStr = "Tuesday"; 241 break; 242 case 3: 243 weekStr = "Tuesday"; 244 break; 245 case 4: 246 weekStr = "Thursday"; 247 break; 248 case 5: 249 weekStr = "Friday"; 250 break; 251 case 6: 252 weekStr = "Saturday"; 253 break; 254 default: 255 break; 256 } 257 return weekStr; 258 } 259 } 260 }View Code
比较有意思的是WhichWeekFunction方法,判断当前日期是哪一周,MonthlyDOWRecurrenceFunction等方法下面,还有条件,又使用了反射的方式调用。整体是如此简单,如此容易理解。测试的时候,把DateTime.Now;//Convert.ToDateTime("2013-06-26T22:15:00.000+08:00");//改成Convert.ToDateTime("2013-06-26T22:15:00.000+08:00");//DateTime.Now;//,这是一个小技巧。这个Convert.ToDateTime("2013-06-26T22:15:00.000+08:00");测试用例下面有数据,总体跟踪一遍,就能理解这个代码
最后祝大家好运,希望在能够帮助大家,也希望大家能多提意见,觉得有收获的帮助顶一下。
代码下载:http://files.cnblogs.com/zhangxl/MatchData.zip
文章出处:http://www.cnblogs.com/zhangxl/p/reflection.html