最近做了一个.Net Core环境下,基于NPOI的Excel导入导出以及Word操作的服务封装,
涉及到大量反射操作,在性能优化过程中使用到了表达式树,记录一下。
Excel导入是相对比较麻烦的一块,实现的效果是:调用方只需要定义一个类,只需要标记特性,
服务读取Excel=>校验(正则、必填、整数范围、日期、数据库是否存在、数据重复) =>将校验结果返回 => 提供方法将Excel数据
转换为指定类集合。
在最后一步转换,最开始用反射实现,性能较差;后来通过了反射+委托,表达式树方式进行优化,
最终性能接近了硬编码。见图,转换近5000条有效数据,耗时仅100毫秒不到,是反射的近20倍。
读取Excel数据之后,我将数据读取到了自定义的两个类(方便后面的校验)
public class ExcelDataRow { ////// 行号 /// public int RowIndex { get; set; } ////// 单元格数据 /// public ListDataCols { get; set; } = new List (); /// /// 是否有效 /// public bool IsValid { get; set; } ////// 错误信息 /// public string ErrorMsg { get; set; } } public class ExcelDataCol : ExcelCol { ////// 对应属性名称 /// public string PropertyName { get; set; } ////// 行号 /// public int RowIndex { get; set; } ////// 字符串值 /// public string ColValue { get; set; } }
校验完之后,需要将ExcelDataRow转换为指定类型
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace Ade.OfficeService.Excel { ////// 生成表达式目录树 缓存 /// public class ExpressionMapper { private static Hashtable Table = Hashtable.Synchronized(new Hashtable(1024)); ////// 将ExcelDataRow快速转换为指定类型 /// ////// /// public static T FastConvert (ExcelDataRow dataRow) { //利用表达式树,动态生成委托并缓存,得到接近于硬编码的性能 //最终生成的代码近似于(假设T为Person类) //Func // new Person(){ // Name = Convert(ChangeType(dataRow.DataCols.SingleOrDefault(c=>c.PropertyName == prop.Name).ColValue,prop.PropertyType),prop.ProertyType), // Age = Convert(ChangeType(dataRow.DataCols.SingleOrDefault(c=>c.PropertyName == prop.Name).ColValue,prop.PropertyType),prop.ProertyType) // } // } string propertyNames = string.Empty; dataRow.DataCols.ForEach(c => propertyNames += c.PropertyName + "_"); var key = typeof(T).FullName + "_" + propertyNames.Trim('_'); if (!Table.ContainsKey(key)) { List memberBindingList = new List (); MethodInfo singleOrDefaultMethod = typeof(Enumerable) .GetMethods() .Single(m => m.Name == "SingleOrDefault" && m.GetParameters().Count() == 2) .MakeGenericMethod(new[] { typeof(ExcelDataCol) }); foreach (var prop in typeof(T).GetProperties()) { Expression > lambdaExpr = c => c.PropertyName == prop.Name; MethodInfo changeTypeMethod = typeof(ExpressionMapper).GetMethods().Where(m => m.Name == "ChangeType").First(); Expression expr = Expression.Convert( Expression.Call(changeTypeMethod , Expression.Property( Expression.Call( singleOrDefaultMethod , Expression.Constant(dataRow.DataCols) , lambdaExpr) , typeof(ExcelDataCol), "ColValue"), Expression.Constant(prop.PropertyType)) , prop.PropertyType); memberBindingList.Add(Expression.Bind(prop, expr)); } MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(T)), memberBindingList.ToArray()); Expression > lambda = Expression.Lambda >(memberInitExpression, new ParameterExpression[] { Expression.Parameter(typeof(ExcelDataRow), "p") }); Func func = lambda.Compile();//拼装是一次性的 Table[key] = func; } var ss = (Func )Table[key]; return ((Func )Table[key]).Invoke(dataRow); } public static object ChangeType(string stringValue, Type type) { object obj = null; Type nullableType = Nullable.GetUnderlyingType(type); if (nullableType != null) { if (stringValue == null) { obj = null; } } else if (typeof(System.Enum).IsAssignableFrom(type)) { obj = Enum.Parse(type, stringValue); } else { obj = Convert.ChangeType(stringValue, type); } return obj; } } }