学习反射之将List<T>自定义导出
1.前言:最近有个Task需要操作很多大表(每个表的列名都上百),需要将这些表数据从不同数据源中找到,然后将表导入到数据库,可能还得需要将表部分数据导出成各种文件(csv,txt,excel等)。
2.思考:一般像这种需求.先定义一个TemplateInfo,然后去DB为这个TemplateInfo创建一个表。最后封装TemplateManager定义一些操作DB的方法。
现在问题产生了:需要经常导出表中部分数据。有时候导出titles可能有细微变化。由于TemplateInfo有很多属性,每次光复制粘贴TemplateInfo属性赋值那部分代码就够花时间的了,跟不用说让有点强迫症的人每次面对几乎差不多代码了。
首先我们是不知道List<T>中T的具体类型,所以只能通过反射获取T的属性和值,在通过传递参数实现自定义,按照不同的参数条件,顺序,得到一组属性和对应值。然后直接生成文件。
3.ReflectionUtil类,定义一组反射相关的方法
1 public class ReflectionUtil 2 { 3 /// <summary> 4 /// Get template public property name collection 5 /// </summary> 6 /// <param name="dateType">template type</param> 7 /// <returns>property name collection</returns> 8 public static List<string> GetPropertyNames<T>(T dateType) 9 { 10 if (dateType == null) 11 { 12 throw new Exception("parameter cannot be null."); 13 } 14 15 var propertyName = new List<string>(); 16 Type type = dateType.GetType(); 17 PropertyInfo[] properties = type.GetProperties(); 18 foreach (var property in properties) 19 { 20 propertyName.Add(property.Name.ToString()); 21 } 22 23 return propertyName; 24 } 25 26 /// <summary> 27 /// Get property adn set func 28 /// </summary> 29 /// <param name="dataType">template type</param> 30 /// <param name="propertyName">property name</param> 31 /// <returns></returns> 32 public static Func<object, object> GetPropertySetFunc(Type dataType, string propertyName) 33 { 34 var propInfo = dataType.GetProperty(propertyName); 35 var getMethodInfo = propInfo.GetGetMethod(); 36 37 DynamicMethod dynamicGet = CreateGetDynamicMethod(dataType); 38 ILGenerator getGenerator = dynamicGet.GetILGenerator(); 39 40 getGenerator.Emit(OpCodes.Ldarg_0); 41 getGenerator.Emit(OpCodes.Call, getMethodInfo); 42 BoxIfNeeded(getMethodInfo.ReturnType, getGenerator); 43 getGenerator.Emit(OpCodes.Ret); 44 45 return dynamicGet.CreateDelegate(typeof(Func<object, object>)) as Func<object, object>; 46 } 47 48 /// <summary> 49 /// Create get dynamic method 50 /// </summary> 51 /// <param name="type">template type</param> 52 /// <returns></returns> 53 private static DynamicMethod CreateGetDynamicMethod(Type type) 54 { 55 return new DynamicMethod("DynamicGet", typeof(object), new[] { typeof(object) }, type, true); 56 } 57 58 /// <summary> 59 /// Box 60 /// </summary> 61 /// <param name="type">template type</param> 62 /// <param name="generator">il generator</param> 63 private static void BoxIfNeeded(Type type, ILGenerator generator) 64 { 65 if (type.IsValueType) 66 { 67 generator.Emit(OpCodes.Box, type); 68 } 69 } 70 }
4.CollectionExportUtil类,封装一组重载方法,根据不同参数满足各种文件需求导出。可以减少很多代码量和时间。
1 /// <summary> 2 /// ExportType can be add by new requirement in the future 3 /// </summary> 4 public enum ExportType 5 { 6 TxtFile, 7 CsvFile, 8 NoSeparator, 9 Space, 10 Other 11 } 12 13 public class CollectionExportUtil 14 { 15 /// <summary> 16 /// export file from property index start 17 /// </summary> 18 /// <typeparam name="T">template</typeparam> 19 /// <param name="dataCollection">template collection</param> 20 /// <param name="propertyIndexStart">property index start</param> 21 /// <param name="exportType">content type</param> 22 /// <returns>content string</returns> 23 public static string GenerateExportText<T>(List<T> dataCollection, int propertyIndexStart, ExportType exportType) 24 { 25 int propertyIndexEnd = ReflectionUtil.GetPropertyNames(dataCollection[0]).Count() - 1; 26 return GenerateExportText(dataCollection, propertyIndexStart, propertyIndexEnd, exportType); 27 } 28 29 /// <summary> 30 /// export file from property index start to index end 31 /// </summary> 32 /// <typeparam name="T">template</typeparam> 33 /// <param name="dataCollection">template collection</param> 34 /// <param name="propertyIndexStart">property index start</param> 35 /// <param name="propertyIndexEnd">property index end </param> 36 /// <param name="exportType">file type</param> 37 /// <returns>content string</returns> 38 public static string GenerateExportText<T>(List<T> dataCollection, int propertyIndexStart, int propertyIndexEnd, ExportType exportType) 39 { 40 if (propertyIndexStart == null || propertyIndexEnd == null) 41 { 42 throw new Exception("property index parameter cannot be null."); 43 } 44 45 if (propertyIndexStart.CompareTo(0) < 0) 46 { 47 throw new Exception("property index start must more than zero."); 48 } 49 50 if (propertyIndexStart.CompareTo(propertyIndexEnd) > 0) 51 { 52 throw new Exception("property index start must less than index end."); 53 } 54 55 Dictionary<string, string> columnInfos = new Dictionary<string, string>(); 56 var columns = ReflectionUtil.GetPropertyNames(dataCollection[0]); 57 58 if (propertyIndexEnd.CompareTo(columns.Count) > 0) 59 { 60 throw new Exception("index end cannot bigger than column count."); 61 } 62 63 for (int i = propertyIndexStart; i <= propertyIndexEnd; i++) 64 { 65 columnInfos.Add(columns[i], columns[i]); 66 } 67 68 return GenerateExportText(dataCollection, columnInfos, exportType); 69 } 70 71 /// <summary> 72 /// Export by template property index list with property name 73 /// </summary> 74 /// <typeparam name="T">template</typeparam> 75 /// <param name="dataCollection">template collection</param> 76 /// <param name="indexs">property index with sequence </param> 77 /// <param name="separator">separator</param> 78 /// <returns>content string</returns> 79 public static string GenerateExportText<T>(List<T> dataCollection, List<int> indexs, ExportType exportType) 80 { 81 if (indexs == null) 82 { 83 throw new Exception("parameter cannot be null."); 84 } 85 86 Dictionary<string, string> columnInfos = new Dictionary<string, string>(); 87 var columns = ReflectionUtil.GetPropertyNames(dataCollection[0]); 88 for (int i = 0; i < indexs.Count; i++) 89 { 90 columnInfos.Add(columns[indexs[i]], columns[indexs[i]]); 91 } 92 93 return GenerateExportText(dataCollection, columnInfos, exportType); 94 } 95 96 /// <summary> 97 /// Export by template property index list with sequence 98 /// </summary> 99 /// <typeparam name="T">template</typeparam> 100 /// <param name="dataCollection">template collection</param> 101 /// <param name="indexs">property index with sequence </param> 102 /// <param name="titles">title of the property with sequence</param> 103 /// <param name="separator">separator</param> 104 /// <returns>content string</returns> 105 public static string GenerateExportText<T>(List<T> dataCollection, List<int> indexs, List<string> titles, ExportType exportType) 106 { 107 if (indexs == null || titles == null) 108 { 109 throw new Exception("parameter cannot be null."); 110 } 111 112 if (indexs.Count != titles.Count) 113 { 114 throw new Exception("index count not equal the titles count."); 115 } 116 117 Dictionary<string, string> columnInfos = new Dictionary<string, string>(); 118 var columns = ReflectionUtil.GetPropertyNames(typeof(T)); 119 for (int i = 0; i < indexs.Count; i++) 120 { 121 columnInfos.Add(titles[i], columns[indexs[i]]); 122 } 123 124 return GenerateExportText(dataCollection, columnInfos, exportType); 125 } 126 127 /// <summary> 128 /// Export by template with all property name as the titles 129 /// </summary> 130 /// <typeparam name="T">template</typeparam> 131 /// <param name="dataCollection">template collection</param> 132 /// <param name="separator">separator</param> 133 /// <returns>content string</returns> 134 public static string GenerateExportText<T>(List<T> dataCollection, ExportType exportType) 135 { 136 Dictionary<string, string> columnInfos = new Dictionary<string, string>(); 137 var columns = ReflectionUtil.GetPropertyNames(dataCollection[0]); 138 foreach (var item in columns) 139 { 140 columnInfos.Add(item, item); 141 } 142 143 return GenerateExportText(dataCollection, columnInfos, exportType); 144 } 145 146 /// <summary> 147 /// Export by template with (title,property) 148 /// </summary> 149 /// <typeparam name="T">template</typeparam> 150 /// <param name="dataCollection">template collection</param> 151 /// <param name="columnInfos">(title,property) collection</param> 152 /// <param name="separator">separator</param> 153 /// <returns>content string</returns> 154 public static string GenerateExportText<T>(List<T> dataCollection, Dictionary<string, string> columnInfos, ExportType exportType) 155 { 156 StringBuilder fullText = new StringBuilder(); 157 var headers = columnInfos.Select(p => p.Key); 158 159 string separator = string.Empty; 160 switch (exportType) 161 { 162 case ExportType.TxtFile: 163 separator = " "; 164 break; 165 case ExportType.CsvFile: 166 separator = ","; 167 break; 168 case ExportType.NoSeparator: 169 separator = ""; 170 break; 171 case ExportType.Space: 172 separator = " "; 173 break; 174 } 175 176 //add title 177 fullText.AppendLine(string.Join(separator, headers)); 178 179 if (dataCollection == null) 180 { 181 return fullText.ToString(); 182 } 183 184 Dictionary<string, Func<object, object>> delegateMap = new Dictionary<string, Func<object, object>>(); 185 Type dataType = typeof(T); 186 187 foreach (var pair in columnInfos) 188 { 189 string propertyName = pair.Value; 190 delegateMap.Add(propertyName, ReflectionUtil.GetPropertySetFunc(dataType, propertyName)); 191 } 192 193 foreach (var data in dataCollection) 194 { 195 List<string> values = new List<string>(); 196 197 foreach (var pair in columnInfos) 198 { 199 string propertyName = pair.Value; 200 var getValueFunc = delegateMap[propertyName]; 201 values.Add(getValueFunc.Invoke(data).ToString()); 202 } 203 204 //add value 205 fullText.AppendLine(string.Join(separator, values)); 206 } 207 208 return fullText.ToString(); 209 } 210 211 /// <summary> 212 /// genetate file 213 /// </summary> 214 /// <param name="content"></param> 215 /// <param name="path"></param> 216 public static void GenerateExportFile(string content, string path) 217 { 218 File.WriteAllText(path, content, Encoding.Default); 219 } 220 221 /// <summary> 222 /// generate file with encoding type 223 /// </summary> 224 /// <param name="content"></param> 225 /// <param name="path"></param> 226 /// <param name="encoding"></param> 227 public static void GenerateExportFile(string content, string path, Encoding encoding) 228 { 229 if ((path + "").Trim().Length == 0) 230 { 231 throw new Exception("path of generated cannot be empty."); 232 } 233 234 if (!Directory.Exists(Path.GetDirectoryName(path))) 235 { 236 Directory.CreateDirectory(Path.GetDirectoryName(path)); 237 } 238 239 File.WriteAllText(path, content, encoding); 240 } 241 }
5.使用举例:Demon
5.1 对于表:AutoDivInsertBulkLoaderInfo
1 public class AutoDivInsertBulkLoaderInfo 2 { 3 public string RicEventType { get; set; }//key of the table} 4 public string URL { get; set; } 5 public string AnnualOrSemiAnnualReport { get; set; } 6 public string RIC { get; set; } 7 public string DVP_TYPE { get; set; } 8 public string CLA_EVENT_STATUS { get; set; } 9 public string FPE_PERIOD_END { get; set; } 10 public string FPE_PERIOD_LENGTH { get; set; } 11 public string DIVIDEND_AMOUNT { get; set; } 12 public string ANNOUNCEMENT_DATE { get; set; } 13 public string PAY_DATE { get; set; } 14 public string RECORD_DATE { get; set; } 15 public string EX_DATE { get; set; } 16 public string PAYDATE_DAY_SET { get; set; } 17 public string PAID_AS_PERCENT { get; set; } 18 public string CLA_CUR_VAL { get; set; } 19 public string QDI_PERCENT { get; set; } 20 public string DESCRIPTION { get; set; } 21 public string CLA_MEETING_TYPE_VAL { get; set; } 22 public string MEETING_DATE { get; set; } 23 public string CLA_TAX_STATUS_VAL { get; set; } 24 public string TAX_RATE_PERCENT { get; set; } 25 public string FOREIGN_INVESTOR_TAX_RATE { get; set; } 26 public string SOURCE_TYPE { get; set; } 27 public string RELEASE_DATE { get; set; } 28 public string LOCAL_DATE { get; set; } 29 public string TIMEZONE_NAME { get; set; } 30 public string SOURCE_PROVIDER { get; set; } 31 public string BRIDGE_SYMBOL { get; set; } 32 public string SEQ_NUM { get; set; } 33 public string CLA_RECORD_STATUS { get; set; } 34 public string FPE_PERIOD_LENGTH_INDICATOR { get; set; } 35 public string CLA_DIV_MARKER_VAL { get; set; } 36 public string CLA_DIV_FREQ_VAL { get; set; } 37 public string PID_QUARTER { get; set; } 38 public string PID_YEAR { get; set; } 39 public string PID { get; set; } 40 public string CLA_TEXT_TYPE { get; set; } 41 public string CLA_DIV_FEATURES_VAL { get; set; } 42 public string TAX_CREDIT_PERCENT { get; set; } 43 public string CLA_TAX_TRTMNT_MKR_VAL { get; set; } 44 public string RESCINDED { get; set; } 45 public string CAC_MA_COMMENTS { get; set; } 46 public string FRANKED_PERCENT { get; set; } 47 public string REINVESTMENT_PLAN_AVAILABLE { get; set; } 48 public string REINVESTMENT_DEADLINE { get; set; } 49 public string REINVESTMENT_PRICE { get; set; } 50 public string CLA_SOURCE_OF_FUND { get; set; } 51 public string CLA_DIV_RANKING { get; set; } 52 public string DIVIDEND_RANKING_DATE { get; set; } 53 public string BOOKCLOSURE_START_DATE { get; set; } 54 public string BOOKCLOSURE_END_DATE { get; set; } 55 public string MODIFIED { get; set; } 56 public string SOURCE_ID { get; set; } 57 public string SOURCE_LINK { get; set; } 58 public string SOURCE_DESCRIPTION { get; set; } 59 60 public AutoDivInsertBulkLoaderInfo() 61 { 62 //this.RicEventType = string.Empty;//key 63 this.URL = string.Empty; 64 this.AnnualOrSemiAnnualReport = string.Empty; 65 //this.RIC = string.Empty; 66 //this.DVP_TYPE = string.Empty; 67 this.CLA_EVENT_STATUS = string.Empty; 68 this.FPE_PERIOD_END = string.Empty; 69 this.FPE_PERIOD_LENGTH = string.Empty; 70 this.DIVIDEND_AMOUNT = string.Empty; 71 this.ANNOUNCEMENT_DATE = string.Empty; 72 this.PAY_DATE = string.Empty; 73 this.RECORD_DATE = string.Empty; 74 this.EX_DATE = string.Empty; 75 this.PAYDATE_DAY_SET = string.Empty; 76 this.PAID_AS_PERCENT = string.Empty; 77 this.CLA_CUR_VAL = string.Empty; 78 this.QDI_PERCENT = string.Empty; 79 this.DESCRIPTION = string.Empty; 80 this.CLA_MEETING_TYPE_VAL = string.Empty; 81 this.MEETING_DATE = string.Empty; 82 this.CLA_TAX_STATUS_VAL = string.Empty; 83 this.TAX_RATE_PERCENT = string.Empty; 84 this.FOREIGN_INVESTOR_TAX_RATE = string.Empty; 85 this.SOURCE_TYPE = string.Empty; 86 this.RELEASE_DATE = string.Empty; 87 this.LOCAL_DATE = string.Empty; 88 this.TIMEZONE_NAME = string.Empty; 89 this.SOURCE_PROVIDER = string.Empty; 90 this.BRIDGE_SYMBOL = string.Empty; 91 this.SEQ_NUM = string.Empty; 92 this.CLA_RECORD_STATUS = string.Empty; 93 this.FPE_PERIOD_LENGTH_INDICATOR = string.Empty; 94 this.CLA_DIV_MARKER_VAL = string.Empty; 95 this.CLA_DIV_FREQ_VAL = string.Empty; 96 this.PID_QUARTER = string.Empty; 97 this.PID_YEAR = string.Empty; 98 this.PID = string.Empty; 99 this.CLA_TEXT_TYPE = string.Empty; 100 this.CLA_DIV_FEATURES_VAL = string.Empty; 101 this.TAX_CREDIT_PERCENT = string.Empty; 102 this.CLA_TAX_TRTMNT_MKR_VAL = string.Empty; 103 this.RESCINDED = string.Empty; 104 this.CAC_MA_COMMENTS = string.Empty; 105 this.FRANKED_PERCENT = string.Empty; 106 this.REINVESTMENT_PLAN_AVAILABLE = string.Empty; 107 this.REINVESTMENT_DEADLINE = string.Empty; 108 this.REINVESTMENT_PRICE = string.Empty; 109 this.CLA_SOURCE_OF_FUND = string.Empty; 110 this.CLA_DIV_RANKING = string.Empty; 111 this.DIVIDEND_RANKING_DATE = string.Empty; 112 this.BOOKCLOSURE_START_DATE = string.Empty; 113 this.BOOKCLOSURE_END_DATE = string.Empty; 114 this.MODIFIED = string.Empty; 115 this.SOURCE_ID = string.Empty; 116 this.SOURCE_LINK = string.Empty; 117 this.SOURCE_DESCRIPTION = string.Empty; 118 } 119 }
5.2 需求导出从URL开始(第2个属性)按升顺方式将AutoDivInsertBulkLoaderInfo所有属性的值导出,且属性AnnualOrSemiAnnualReport的Title改为
”Annual/Semi-annual Report“,其他Title 等于默认属性名。
1 private void GenerateAutoDivInsertBulkLoaderBulkFile(List<AutoDivInsertBulkLoaderInfo> autoinsertBulkFile) 2 { 3 if (autoinsertBulkFile == null || autoinsertBulkFile.Count == 0) 4 { 5 Logger.Log("autoinsertBulkFile collection is empty, neednot to generate autoinsertBulkFile.csv file.", Logger.LogType.Warning); 6 return; 7 } 8 9 Dictionary<string, string> titles = new Dictionary<string, string>(); 10 var property = Ric.Util.ReflectionUtil.GetPropertyNames(autoinsertBulkFile[0]); 11 for (int i = 0; i < property.Count; i++) 12 { 13 if (i == 0) 14 continue; 15 16 if (i == 2) 17 titles.Add("Annual/Semi-annual Report", property[i]); 18 else 19 titles.Add(property[i], property[i]); 20 } 21 22 string path = Path.Combine(resultPath, DateTime.Now.ToString("dd-MM-yyyy")); 23 string fileName = Path.Combine(path, string.Format("AUTO_DIV_Insert_Bulk_Loader_{0}.csv", DateTime.Now.ToString("dd-MM-yyyy"))); 24 CollectionExportUtil.GenerateExportFile(CollectionExportUtil.GenerateExportText(autoinsertBulkFile, titles, ExportType.CsvFile), fileName); 25 }