最近做了一個.Net Core環境下,基於NPOI的Excel導入導出以及Word操作的服務封裝,
涉及到大量反射操作,在性能優化過程中使用到了表達式樹,記錄一下。
Excel導入是相對比較麻煩的一塊,實現的效果是:調用方只需要定義一個類,只需要標記特性,
服務讀取Excel=>校驗(正則、必填、整數范圍、日期、數據庫是否存在、數據重復) =>將校驗結果返回 => 提供方法將Excel數據
轉換為指定類集合。
在最后一步轉換,最開始用反射實現,性能較差;后來通過了反射+委托,表達式樹方式進行優化,
最終性能接近了硬編碼。見圖,轉換近5000條有效數據,耗時僅100毫秒不到,是反射的近20倍。
讀取Excel數據之后,我將數據讀取到了自定義的兩個類(方便后面的校驗)
public class ExcelDataRow { /// <summary> /// 行號 /// </summary> public int RowIndex { get; set; } /// <summary> /// 單元格數據 /// </summary> public List<ExcelDataCol> DataCols { get; set; } = new List<ExcelDataCol>(); /// <summary> /// 是否有效 /// </summary> public bool IsValid { get; set; } /// <summary> /// 錯誤信息 /// </summary> public string ErrorMsg { get; set; } } public class ExcelDataCol : ExcelCol { /// <summary> /// 對應屬性名稱 /// </summary> public string PropertyName { get; set; } /// <summary> /// 行號 /// </summary> public int RowIndex { get; set; } /// <summary> /// 字符串值 /// </summary> 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 { /// <summary> /// 生成表達式目錄樹 緩存 /// </summary> public class ExpressionMapper { private static Hashtable Table = Hashtable.Synchronized(new Hashtable(1024)); /// <summary> /// 將ExcelDataRow快速轉換為指定類型 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="dataRow"></param> /// <returns></returns> public static T FastConvert<T>(ExcelDataRow dataRow) { //利用表達式樹,動態生成委托並緩存,得到接近於硬編碼的性能 //最終生成的代碼近似於(假設T為Person類) //Func<ExcelDataRow,Person> // 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<MemberBinding> memberBindingList = new List<MemberBinding>(); 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<Func<ExcelDataCol, bool>> 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<Func<ExcelDataRow, T>> lambda = Expression.Lambda<Func<ExcelDataRow, T>>(memberInitExpression, new ParameterExpression[] { Expression.Parameter(typeof(ExcelDataRow), "p") }); Func<ExcelDataRow, T> func = lambda.Compile();//拼裝是一次性的 Table[key] = func; } var ss = (Func<ExcelDataRow, T>)Table[key]; return ((Func<ExcelDataRow, T>)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; } } }