内容摘要:
1:阐述问题
2:分析问题,解决问题
3:演示解决方案
1:阐述问题
有时候,我们会遇上这样一个问题:有很多条件 condition1 、condition2 、condition3、condition4 、condition5......这些条件各不相同,可能同时配置其中几个,这几个条件有一个交集,交集内部就是我们需要的。
给一个实例吧。用户在系统中配置了一个时间条件集合,用户可以按照年、月、周或者日来配置,按照其中一种来配置,下面有很多条件可以选择,其中开始日期和时间是必须配置的,最后会形成一个xml信息存储在数据库里面,我们会用当前时间判断每个用户的配置条件,如何符合,我们把他的邮箱拿出放到一个字符串尾部,不符合则不管,最后这个字符串就是所有符合用户配置条件的邮箱集合,我们可以把我们的信息推送给这些用户。其中xml按照月配置的如下:
12 2012-07-21T22:00:00.000+08:00 34 23FirstWeek 56 8true 79 22true 10true 11true 12true 13true 14true 15true 16true 17true 18true 19true 20true 21
StartDateTime是开始时间,它是一个基本条件,每个配置信息里面都必须存在。MonthlyDOWRecurrence是代表是按照月来配置的(也可能是WeeklyRecurrence周、DailyRecurrence日等)这个xml节点下面存在很多条件,例如WhichWeek是代表哪一周,DaysOfWeek表示每周的哪一天,又或者MonthsOfYear是每年的哪几个月。
问题就是这样,我从数据库MatchData得到所有用户的配置的数据集 Id、Email、MatchData(sql :SELECT [Id],[Email],[MatchData]FROM [MatchData].[dbo].[EmailInTime]),MatchData就是我们的配置xml信息,从集合中找出所有符合条件的用户Email。
2:分析问题,解决问题
如果你遇上了啦,该如何处理呢?下面是我的处理方法,如果你有更好的办法,请给我留意,不吝赐教,谢谢!
有人可以会总结一下有多少个条件,例如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 else11 {12 result+=false;13 }14 }15 16 if(condition2 in matchData)17 {18 if(nowDt is match matchData by condition2)19 {20 result+=true21 }22 else23 {24 result+=false;25 }26 }27 //.28 //.条件3到929 //.30 if(condition10 in matchData)31 {32 if(nowDt is match matchData by condition10)33 {34 result+=true35 }36 else37 {38 result+=false;39 }40 }41 return result;42 }
这是一个伪代码,简单模拟了一下,大家都会发现很多问题,
1:判断是否存在这个条件在xml里面,耗性能;
2:有很多条件是不用去判断的,最好是xml配置文件里面有什么条件,我们就去判断什么条件;
3:扩展性差,当我们需要条件更多的条件的时候,需要修改已有的代码。
如何解决这三个问题呢?我们可以去遍历xml配置信息,我们遇到什么条件的时候,我们反射到封装好的的条件方法里面。遍历xml,我们就可以解决第1个问题;遇上什么条件我们判断什么条件,这个可以解决第2个问题;利用每个条件都封装成方法,遇到条件的时候反射到对应的方法,就解决了第3个问题。
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 ///2 /// the api to get the email address of match all conditions 3 /// 4 ///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 /// 24 /// judge whether the current time match all conditions in the xml string 'matchData'25 /// 26 /// 27 /// 28 ///29 private bool IsMatchWithMatchData(string matchData, DateTime nowDt)30 {31 bool result = true;32 xmldoc.LoadXml(matchData);33 try34 {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 else45 break;46 }47 return result;48 }49 catch50 {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 ///11 /// determine whether the current now match with the condition of the xml string 12 /// 13 internal class MatchWithMatchData 14 { 15 DateTime startDt; 16 DateTime nowDt; 17 ///18 /// initialize data 19 /// 20 /// start datetime 21 /// now datetime 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 ///94 /// whether current datetime accord with the condition of every month's days 95 /// 96 /// 97 ///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 else113 {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 else148 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 else160 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 }
比较有意思的是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");测试用例下面有数据,总体跟踪一遍,就能理解这个代码
最后祝大家好运,希望在能够帮助大家,也希望大家能多提意见,觉得有收获的帮助顶一下。 代码下载: 文章出处: