最近做了一個.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;
}
}
}
