經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
前言:
經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。
隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。
ORM 實現的三個通用階段:
第一階段:
在以往新手入門寫 ORM 實現的時候,往往會藉助代碼生成器,來針對整個資料庫,生成一個一個的基礎增刪改查。
用代碼生成器提前生成針對性的方法,運行效率高,但開發效率有可維護性低。
第二階段:
隨著對程式進一步的理解,可能會進化的使用反射來替代代碼生成器,可以簡化掉大量的生成式代碼。
但該方向正好相反,運行效率低,開發效率和可維護性高,通過對反射屬性加以緩存,可以改善運行效率問題。
第三階段:
今天給出的項目示例是:
通過 Emit 實現 ORM 中常用的,通過 ADO.NET 的 DataReader 流讀取資料庫數據,並將其讀取到實體類 這一例子。
通過該方法,可以即有高的運行效率,同時又保持開發效率和可維護性。
下麵看基礎示例:
示例代碼:
以下示例代碼,取自 CYQ.Data:
using System; using System.Collections.Generic; using System.Text; using System.Reflection.Emit; using System.Reflection; using CYQ.Data.Table; using CYQ.Data.Tool; using CYQ.Data.SQL; using System.Data.Common; namespace CYQ.Data.Emit { /// <summary> /// DbDataReader 轉實體 /// </summary> internal static partial class DbDataReaderToEntity { static Dictionary<Type, Func<DbDataReader, object>> typeFuncs = new Dictionary<Type, Func<DbDataReader, object>>(); private static readonly object lockObj = new object(); internal static Func<DbDataReader, object> Delegate(Type t) { if (typeFuncs.ContainsKey(t)) { return typeFuncs[t]; } lock (lockObj) { if (typeFuncs.ContainsKey(t)) { return typeFuncs[t]; } DynamicMethod method = CreateDynamicMethod(t); var func = method.CreateDelegate(typeof(Func<DbDataReader, object>)) as Func<DbDataReader, object>; typeFuncs.Add(t, func); return func; } } /// <summary> /// 構建一個ORM實體轉換器(第1次構建有一定開銷時間) /// </summary> /// <param name="entityType">轉換的目標類型</param> private static DynamicMethod CreateDynamicMethod(Type entityType) { #region 創建動態方法 var readerType = typeof(DbDataReader); Type convertToolType = typeof(ConvertTool); MethodInfo getValue = readerType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(string) }, null); MethodInfo changeType = convertToolType.GetMethod("ChangeType", BindingFlags.Static | BindingFlags.Public, null, new Type[] { typeof(object), typeof(Type) }, null); MethodInfo getTypeFromHandle = typeof(Type).GetMethod("GetTypeFromHandle"); DynamicMethod method = new DynamicMethod("DbDataReaderToEntity", typeof(object), new Type[] { readerType }, entityType); var constructor = entityType.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { }, null); ILGenerator gen = method.GetILGenerator();//開始編寫IL方法。 if (constructor == null) { gen.Emit(OpCodes.Ret); return method; } var instance = gen.DeclareLocal(entityType);//0 : Entity t0; gen.DeclareLocal(typeof(object));//1 string s1; gen.DeclareLocal(typeof(Type));//2 Type t2; gen.Emit(OpCodes.Newobj, constructor); gen.Emit(OpCodes.Stloc_0, instance);//t0= new T(); List<PropertyInfo> properties = ReflectTool.GetPropertyList(entityType); if (properties != null && properties.Count > 0) { foreach (var property in properties) { SetValueByRow(gen, getValue, changeType, getTypeFromHandle, property, null); } } List<FieldInfo> fields = ReflectTool.GetFieldList(entityType); if (fields != null && fields.Count > 0) { foreach (var field in fields) { SetValueByRow(gen, getValue, changeType, getTypeFromHandle, null, field); } } gen.Emit(OpCodes.Ldloc_0, instance);//t0 載入,準備返回 gen.Emit(OpCodes.Ret); #endregion return method; } private static void SetValueByRow(ILGenerator gen, MethodInfo getValue, MethodInfo changeType, MethodInfo getTypeFromHandle, PropertyInfo pi, FieldInfo fi) { Type valueType = pi != null ? pi.PropertyType : fi.FieldType; string fieldName = pi != null ? pi.Name : fi.Name; Label labelContinue = gen.DefineLabel();//定義迴圈標簽;goto; gen.Emit(OpCodes.Ldarg_0);//載入 reader 對象 gen.Emit(OpCodes.Ldstr, fieldName);//設置欄位名。 gen.Emit(OpCodes.Callvirt, getValue);//reader.GetValue(...) gen.Emit(OpCodes.Stloc_1);//將索引 1 處的局部變數載入到計算堆棧上。 gen.Emit(OpCodes.Ldloc_1);//將索引 1 處的局部變數載入到計算堆棧上。 gen.Emit(OpCodes.Brfalse_S, labelContinue);//if(!a){continue;} //-------------新增:o=ConvertTool.ChangeType(o, t); if (valueType.Name != "Object") { gen.Emit(OpCodes.Ldtoken, valueType);//這個卡我卡的有點久。將元數據標記轉換為其運行時表示形式,並將其推送到計算堆棧上。 //下麵這句Call,解決在 .net 中無法獲取Type值,拋的異常:嘗試讀取或寫入受保護的記憶體。這通常指示其他記憶體已損壞。 gen.Emit(OpCodes.Call, getTypeFromHandle); gen.Emit(OpCodes.Stloc_2); gen.Emit(OpCodes.Ldloc_1);//o gen.Emit(OpCodes.Ldloc_2); gen.Emit(OpCodes.Call, changeType);//Call ChangeType(o,type);=> invoke(o,type) 調用由傳遞的方法說明符指示的方法。 gen.Emit(OpCodes.Stloc_1); // o=GetItemValue(ordinal); } //------------------------------------------- SetValue(gen, pi, fi); gen.MarkLabel(labelContinue);//繼續下一個迴圈 } private static void SetValue(ILGenerator gen, PropertyInfo pi, FieldInfo fi) { if (pi != null && pi.CanWrite) { gen.Emit(OpCodes.Ldloc_0);//實體對象obj gen.Emit(OpCodes.Ldloc_1);//屬性的值 objvalue EmitCastObj(gen, pi.PropertyType);//類型轉換 gen.EmitCall(OpCodes.Callvirt, pi.GetSetMethod(), null); // Call the property setter } if (fi != null) { gen.Emit(OpCodes.Ldloc_0);//實體對象obj gen.Emit(OpCodes.Ldloc_1);//屬性的值 objvalue EmitCastObj(gen, fi.FieldType);//類型轉換 gen.Emit(OpCodes.Stfld, fi);//對實體賦值 System.Object.FieldSetter(String typeName, String fieldName, Object val) } } private static void EmitCastObj(ILGenerator il, Type targetType) { if (targetType.IsValueType) { il.Emit(OpCodes.Unbox_Any, targetType); } else { il.Emit(OpCodes.Castclass, targetType); } } } }
示例代碼使用示例:
private static List<T> ReaderToListEntity<T>(DbDataReader reader) { List<T> list = new List<T>(); var func = DbDataReaderToEntity.Delegate(typeof(T)); while (reader.Read()) { object obj = func(reader); if (obj != null) { list.Add((T)obj); } } return list; }
示例代碼使用示例重點講解:
1、Emit 實現中,接收 DbDataReader 做為參數,它是各種 DataReader 的基類:
可以適應不同的資料庫類型,如果新手使用只是針對某一資料庫類型,也可以修改為:SqlDataReader 或 MySqlDataReader 等。
2、Emit 實現中,僅實現讀取當前行數據的功能,而讀取多行,是在外層封裝(即使用示例的封裝方法)實現:
這樣的好處是可以簡化 Emit 的部分實現,同時又保留高效的性能。
3、Emit 實現中,涉及到三個外部方法:
A:List<PropertyInfo> properties = ReflectTool.GetPropertyList(entityType);
該方法是 CYQ.Data 的內部的實現,以緩存反射的屬性,可以用以下代碼替代:
PropertyInfo[] pInfo = t.GetProperties();
B:List<FieldInfo> fields = ReflectTool.GetFieldList(entityType);
該方法是 CYQ.Data 的內部的實現,以緩存反射的屬性,可以用以下代碼替代:
FieldInfo[] pInfo = t.GetFields();
C:ConvertTool.ChangeType 方法:
方法原型如下,實現全品類類型的安全轉換:
public static object ChangeType(object value, Type t)
如果生成的實體類和資料庫類型保持一致,則可以不需要進行類型轉換,加類型轉換,是為了可以相容資料庫欄位類型和實體類屬性類型的不同。
該方法的高效實現,可以參考:ConverTool
總結:
Emit 雖然活躍在 ORM 和 動態代理的領域,但掌握它, 併在合適的場景使用它,則可以獲得更高效的解決方案。
當然,前提是你需要對程式 “性能” 有清晰的追求。
版權聲明:本文原創發表於 博客園,作者為 路過秋天 本文歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則視為侵權。 |
個人微信公眾號 |
Donation(掃碼支持作者):支付寶: |
Donation(掃碼支持作者):微信: |