在各類ORM框架或者SQL映射框架(例如MyBatis,SOD框架之SQL-MAP功能)中,都有將查詢的結果映射爲內存對象的需求,包括映射到實體類、簡單類型(例如Java的POJO,.NET的POCO)的對象。在.NET中,這個過程能夠經過ADO.NET的DataReader對象來讀取數據,而後將讀取的數據映射到內存對象。本篇文章來討論下不一樣方式的數據讀取方式對性能的影響。html
在寫這篇文章以前,我在想如今都2020年全民奔小康了,除了微軟官方的EF框架以外,各類ORM框架層出不窮,連筆者的SOD框架都誕生15年了,還有必要研究這麼Low的問題嗎?後來想了想,本身寫博客主要是總結經驗,記錄問題分析過程的,雖然筆者在2013年就作過一個測試,寫了《用事實說話,成熟的ORM性能不是瓶頸,靈活性不是問題:EF5.0、PDF.NET5.0、Dapper原理分析與測試手記》,但這篇文章已通過去6年多時間了,.NET框架都發展到跨平臺的.NET Core了,如今Dapper更火了,基於Emit和表達式樹的ORM輪子層出不窮,性能和易用性都不錯,這些優秀的ORM框架得到了很高的關注,而SOD框架一直很低調,由於它一直沒用採用Emit和表達式樹技術,也沒有采用反射,而是最原始的DataReader的非類型化數據讀取方式,性能上可能比不上這些ORM框架,但會有多大的差別呢?SOD框架一直強調本身不只僅是一個ORM框架,ORM僅僅是它的一個功能組件,不過你們既然都這麼強調性能,因而決定從新測試一下DataReader的非類型化數據讀取與類型化數據讀取的性能差別,演示下正確使用二者的方式。git
下面的測試方法都是將數據庫一樣的數據經過DataReader讀取出來映射到不一樣的對象中,本篇文章測試用來映射的對象一個是SOD框架的實體類,一個是普通的DTO對象,DTO是POCO的一種。下面是這兩種對象的定義:程序員
SOD實體對象類User的定義:github
public class User : EntityBase { public User() { TableName="Tb_User1"; IdentityName = "UserID"; PrimaryKeys.Add("UserID"); } /// <summary> /// 設置字段名數組,若是不實現該方法,框架會自動反射獲取到字段名數組,所以從效率考慮,建議實現該方法 /// </summary> protected override void SetFieldNames() { PropertyNames = new string[] { "UserID", "Name", "Pwd", "RegistedDate" }; } /// <summary> /// 獲取實體類全局惟一標識;重寫該方法,能夠加快訪問效率 /// </summary> /// <returns></returns> public override string GetGolbalEntityID() { //使用工具-》建立GUID 生成 return "F1344072-AB1E-4BCF-A28C-769C7C4AA06B"; } public int ID { get { return getProperty<int>("UserID"); } set { setProperty("UserID", value); } } public string Name { get { return getProperty<string>("Name"); } set { setProperty("Name", value, 50); } } public string Pwd { get { return getProperty<string>("Pwd"); } set { setProperty("Pwd", value, 50); } } public DateTime RegistedDate { get { return getProperty<DateTime>("RegistedDate"); } set { setProperty("RegistedDate", value); } } }
DTO類 UserDto的定義,跟實體類User徹底同樣的屬性名稱和屬性類型:sql
public class UserDto { public UserDto() { } public int UserID { get; set; } public string Name { get; set; } public string Pwd { get; set; } public DateTime RegistedDate { get; set; } }
下面開始不一樣的查詢方式測試。數據庫
測試方案爲將DataReader讀取出來的數據手工逐一映射到一個POCO對象的屬性上,例以下面映射到UserDto對象上。根據查詢時候的SQL語句中指定的數據列的順序和類型來使用DataReader是效率最高的方式,也就是DataReader類型化數據讀取方法,使用字段索引而不是字段名稱來讀取數據的方式,以下面示例代碼中的reader.GetInt32(0) :數組
//手寫DataReader查詢 private static long HandQuery(AdoHelper db, System.Diagnostics.Stopwatch watch) { watch.Restart(); string sql = "select UserID, Name, Pwd, RegistedDate from Tb_User1"; IList<UserDto> list = db.ExecuteMapper(sql).MapToList<UserDto>(reader => new UserDto { UserID = reader.IsDBNull(0)? default(int): reader.GetInt32(0), Name = reader.IsDBNull(1) ? default(string) : reader.GetString(1), Pwd = reader.IsDBNull(2) ? default(string) : reader.GetString(2), RegistedDate = reader.IsDBNull(3) ? default(DateTime) : reader.GetDateTime(3) }); watch.Stop(); Console.WriteLine("HandQuery List (100000 item) 耗時:(ms)" + watch.ElapsedMilliseconds); return watch.ElapsedMilliseconds; }
代碼說明:緩存
方法的第一個參數db是SOD框架的AdoHelper對象,它是對各類數據庫進行訪問的一個提供程序類,封裝了ADO.NET各類對象的訪問,包括自動管理鏈接、執行查詢、管理事務和記錄日誌等功能。在當前測試程序中這裏它的實例對象是SQL Server訪問提供程序。AdoHelper對象的ExecuteMapper方法將數據查詢結果封裝成一個DataReaderMapper對象,而後可使用該對象的MapToList方法使用DataReader對象的類型化數據讀取方法,將讀取的值賦值給要映射的對象的屬性,例如這裏的UserDto對象。須要注意的是,在調用DataReader的類型化數據讀取方法的時候,必須先判斷當前位置的數據是否空數據(DBNull),不然會出錯。例如上面的示例代碼中,若是索引位置0的數據爲空數據,則給UserDto對象的UserID屬性賦值int類型的默認值0。MapToList方法會讀取結果集的全部數據,讀取完後自動關閉鏈接。架構
AdoHelper對象的封裝比較簡單,而且上面的查詢會查詢Tb_User1表的所有10萬條數據,因此在討論查詢性能的時候,能夠認爲絕大部分時間都是在處理DataReader讀取數據的問題,而且還採用了比字段名定位數據讀取位置更高效的字段索引讀取的方式,所以能夠認爲HandQuery方法的查詢等同於最高效的手寫查詢方式。app
上面的手寫測試代碼看起來簡單,可是必須清楚當前讀取字段的索引位置和當前字段的數據類型,當SQL比較複雜或者SQL語句不在當前方法內設置的,那麼要寫這種代碼就很困難了而且還容易出錯,因此手寫代碼使用類型化數據讀取和對象屬性映射就是一個費力不討好的「體力活」,除非對性能有極高要求不然通常人都不會這樣直接處理查詢映射。要解決這個問題咱們可使用反射、Emit或者表達式樹來動態生成這種跟手寫查詢同樣的代碼。
SOD框架並無使用上面的幾種方式來模擬手寫查詢代碼,而是使用DataReader的非類型化數據讀取方式,再結合委託和緩存的方式來高效訪問要映射的對象,例如當前要映射的POCO對象。這個過程能夠經過AdoHelper對象的QueryList方法來完成,請看下面的示例代碼:
private static long QueryPOCO(AdoHelper db, System.Diagnostics.Stopwatch watch) { watch.Restart(); string sql = "select UserID, Name, Pwd, RegistedDate from Tb_User1"; IList<UserDto> list = db.QueryList<UserDto>(sql); watch.Stop(); Console.WriteLine("QueryPOCO List (100000 item) 耗時:(ms)" + watch.ElapsedMilliseconds); return watch.ElapsedMilliseconds; }
代碼說明:
使用AdoHelper對象的QueryList方法要求要映射的對象的屬性名字和查詢結果集的字段名必須嚴格一致,若是名字不一致,能夠在SQL語句中使用字段別名。QueryList方法能夠接受多個參數,除了第一個參數是要執行的SQL語句以外,其它參數能夠是SQL語句中的「參數」。因此這個查詢方式很是簡單,只須要一行代碼就可完成查詢,相似Dapper的功能,因此這個功能算是SOD框架中的「微型ORM」。
下面是QueryList方法的定義和使用示例:
/// <summary> /// 根據SQL格式化串和可選的參數,直接查詢結果並映射到POCO 對象 /// <example> /// <code> /// <![CDATA[ /// //假設UserPoco 對象跟 Table_User 表是映射的相同結構 /// AdoHelper dbLocal = new SqlServer(); /// dbLocal.ConnectionString = "Data Source=.;Initial Catalog=LocalDB;Integrated Security=True"; /// var list=dbLoal.QueryList<UserPoco>("SELECT UID,Name FROM Table_User WHERE Sex={0} And Height>={1:5.2}",1, 1.60M); /// ]]> /// </code> /// </example> /// </summary> /// <typeparam name="T">POCO 對象類型</typeparam> /// <param name="sqlFormat">SQL格式化串</param> /// <param name="parameters">可選的參數</param> /// <returns>POCO 對象列表</returns> public List<T> QueryList<T>(string sqlFormat, params object[] parameters) where T : class, new() { IDataReader reader = FormatExecuteDataReader(sqlFormat, parameters); return QueryList<T>(reader); }
如上代碼所示,方法第一個參數是一個SQL格式化字符串,在這個格式化字符串中能夠有多個參數,就像string.Format方法的使用同樣。例如上面方法的註釋中查詢條件Sex字段的參數和Height字段的參數,其中Height字段的參數的格式是精度爲5,小數位數爲2的浮點數。
上面的方法調用了QueryList泛型方法來處理DataReader對象讀取的數據,下面看看它的實現:
/// <summary> /// 採用快速的方法,將數據閱讀器的結果映射到一個POCO類的列表上 /// </summary> /// <typeparam name="T">POCO類類型</typeparam> /// <param name="reader">抽象數據閱讀器</param> /// <returns>POCO類的列表</returns> public static List<T> QueryList<T>(IDataReader reader) where T : class, new() { List<T> list = new List<T>(); using (reader) { if (reader.Read()) { int fcount = reader.FieldCount; //使用類型化委託讀取正確的數據,解決MySQL等數據庫可能的問題,感謝網友 @賣女孩的小肥羊 發現此問題 Dictionary<Type, MyFunc<IDataReader, int, object>> readerDelegates = DataReaderDelegate(); MyFunc<IDataReader, int, object>[] getDataMethods = new MyFunc<IDataReader, int, object>[fcount]; INamedMemberAccessor[] accessors = new INamedMemberAccessor[fcount]; DelegatedReflectionMemberAccessor accessorMethod = new DelegatedReflectionMemberAccessor(); for (int i = 0; i < fcount; i++) { accessors[i] = accessorMethod.FindAccessor<T>(reader.GetName(i)); //修改爲從POCO實體類的屬性上來獲取DataReader類型化數據訪問的方法,而不是以前的DataReader 的字段的類型 if (!readerDelegates.TryGetValue(accessors[i].MemberType, out getDataMethods[i])) { getDataMethods[i] = (rd, ii) => rd.GetValue(ii); } } do { T t = new T(); for (int i = 0; i < fcount; i++) { if (!reader.IsDBNull(i)) { MyFunc<IDataReader, int, object> read = getDataMethods[i]; object value=read(reader,i); accessors[i].SetValue(t, value); } } list.Add(t); } while (reader.Read()); } } return list; }
在上面的代碼中的do循環以前,爲要映射的POCO對象的每一個屬性訪問器構建了一個MyFunc<IDataReader, int, object> 委託,該委託實際上來自於SOD框架預約義的一個處理DataReader類型化數據讀取的委託,爲了通用,上面這個委託方法返回值定義成了object類型,這樣在實際調用的時候會進行「裝箱」操做,也就是上面方法的代碼:
object value=read(reader,i); accessors[i].SetValue(t, value);
之因此要進行裝箱,是由於屬性訪問器方法SetValue須要一個object類型參數。
返回DataReader類型化數據讀取方法委託的DataReaderDelegate方法定義以下:
private static Dictionary<Type, MyFunc<IDataReader, int, object>> dictReaderDelegate = null; private static Dictionary<Type, MyFunc<IDataReader, int, object>> DataReaderDelegate() { if (dictReaderDelegate == null) { Dictionary<Type, MyFunc<IDataReader, int, object>> dictReader = new Dictionary<Type, MyFunc<IDataReader, int, object>>(); dictReader.Add(typeof(int), (reader, i) => reader.GetInt32(i)); dictReader.Add(typeof(bool), (reader, i) => reader.GetBoolean(i)); dictReader.Add(typeof(byte), (reader, i) => reader.GetByte(i)); dictReader.Add(typeof(char), (reader, i) => reader.GetChar(i)); dictReader.Add(typeof(DateTime), (reader, i) => reader.GetDateTime(i)); dictReader.Add(typeof(decimal), (reader, i) => reader.GetDecimal(i)); dictReader.Add(typeof(double), (reader, i) => reader.GetDouble(i)); dictReader.Add(typeof(float), (reader, i) => reader.GetFloat(i)); dictReader.Add(typeof(Guid), (reader, i) => reader.GetGuid(i)); dictReader.Add(typeof(System.Int16), (reader, i) => reader.GetInt16(i)); dictReader.Add(typeof(System.Int64), (reader, i) => reader.GetInt64(i)); dictReader.Add(typeof(string), (reader, i) => reader.GetString(i)); dictReader.Add(typeof(object), (reader, i) => reader.GetValue(i)); dictReaderDelegate = dictReader; } return dictReaderDelegate; }
SOD框架的實體類查詢方法直接使用了DataReader非類型化數據讀取方式,一次性將一行數據讀取到一個object[]對象數組中,SOD實體類將直接使用這個object[]對象數組,這使得數據映射過程能夠大大簡化代碼,而且取得不錯的效率。下面是測試實體類查詢方法的示例代碼:
private static long EntityQuery(AdoHelper db, System.Diagnostics.Stopwatch watch) { watch.Restart(); User user = new User(); OQL q = OQL.From(user).Select(user.ID, user.Name, user.Pwd, user.RegistedDate).END; //q.Limit(5000); var list = EntityQuery<User>.QueryList(q, db); watch.Stop(); Console.WriteLine("SOD QueryList List (100000 item) 耗時:(ms)" + watch.ElapsedMilliseconds); return watch.ElapsedMilliseconds; }
下面是QueryList方法有關數據讀取和映射的具體實現部分:
/// <summary> /// 根據數據閱讀器對象,查詢實體對象集合(注意查詢完畢將自動釋放該閱讀器對象) /// </summary> /// <param name="reader">數據閱讀器對象</param> /// <param name="tableName">指定實體類要映射的表名字,默認不指定</param> /// <returns>實體類集合</returns> public static List<T> QueryList(System.Data.IDataReader reader,string tableName) { List<T> list = new List<T>(); if (reader == null) return list; using (reader) { if (reader.Read()) { int fcount = reader.FieldCount; string[] names = new string[fcount]; for (int i = 0; i < fcount; i++) names[i] = reader.GetName(i); T t0 = new T(); if (!string.IsNullOrEmpty(tableName)) t0.MapNewTableName(tableName); t0.PropertyNames = names; do { object[] values = new object[fcount]; reader.GetValues(values); T t = (T)t0.Clone(false ); //t.PropertyNames = names; t.PropertyValues = values; list.Add(t); } while (reader.Read()); } } return list; }
上面的方法直接使用了DataReader對象的非類型化數據讀取方法GetValues,將數據讀取到values數組對象中。在當前QueryList方法中沒用對DataReader對象讀取的數據進行裝箱,可是這種方式相比測試方式1的手寫映射方式性能仍是要低,猜想方法內部進行了複雜的處理,不然沒法解釋測試方式2測試代碼中類型化數據讀取後數據進行裝箱後供數據訪問器使用,測試2的測試性能仍然高於當前測試方式3,但不會有太大的性能差距。
若是DataReader對象類型化讀取速度必定比非類型化數據讀取方法GetValues快,那麼能夠嘗試將類型化數據讀取的值裝箱到數組元素中,這樣有可能提升SOD框架現有的QueryList方法的性能。下面模擬對QueryList方法進行修改,使得DataReader對象類型化讀取到數組元素中。請看下面的示例代碼:
private static long EntityQuery2(AdoHelper db, System.Diagnostics.Stopwatch watch) { watch.Restart(); string sql = "select UserID, Name, Pwd, RegistedDate from Tb_User1"; string tableName = ""; User entity = new User(); IDataReader reader = db.ExecuteDataReader(sql); List<User> list = new List<User>(); using (reader) { if (reader.Read()) { int fcount = reader.FieldCount; string[] names = new string[fcount]; for (int i = 0; i < fcount; i++) names[i] = reader.GetName(i); User t0 = new User(); if (!string.IsNullOrEmpty(tableName)) t0.MapNewTableName(tableName); //正式,下面放開 // t0.PropertyNames = names; // Action< int, object[]> readInt = ( i, o) => { if (reader.IsDBNull(i)) o[i] = DBNull.Value; else o[i] = reader.GetInt32(i); }; Action< int, object[]> readString = ( i, o) => { if (reader.IsDBNull(i)) o[i] = DBNull.Value; else o[i] = reader.GetString(i); }; Action< int, object[]> readDateTime = ( i, o) => { if (reader.IsDBNull(i)) o[i] = DBNull.Value; else o[i] = reader.GetDateTime(i); }; Action< int, object[]>[] readerActions = { readInt,readString,readString,readDateTime }; // do { User item = (User)t0.Clone(false); for (int i = 0; i < readerActions.Length; i++) { readerActions[i]( i, item.PropertyValues); } list.Add(item); } while (reader.Read()); } } //return list; watch.Stop(); Console.WriteLine("EntityQuery2 List (10000 item) 耗時:(ms)" + watch.ElapsedMilliseconds); return watch.ElapsedMilliseconds; }
以上4種測試方法準備完畢,下面準備測試數據,使用SQL Server Express LocalDB 建立一個數據庫文件,在此文件數據庫中建立一個User實體類對應的數據表,而後插入10萬條數據,這個功能能夠經過SOD框架下面的代碼實現:
private static void InitData(AdoHelper db, System.Diagnostics.Stopwatch watch) { //自動建立數據庫和表 LocalDbContext context = new LocalDbContext(); Console.WriteLine("須要初始化數據嗎?(Y/N) "); string input= Console.ReadLine(); if (input.ToLower() != "y") return; Console.WriteLine("正在初始化數據,請稍後。。。。"); context.TruncateTable<User>(); Console.WriteLine("..."); watch.Restart(); List<User> batchList = new List<User>(); for (int i = 0; i < 100000; i++) { User zhang_yeye = new User() { ID = 1000 + i, Name = "zhang yeye" + i, Pwd = "pwd" + i ,RegistedDate =DateTime.Now }; //count += EntityQuery<User>.Instance.Insert(zhang_yeye);//採用泛型 EntityQuery 方式插入數據 batchList.Add(zhang_yeye); } watch.Stop(); Console.WriteLine("準備數據 耗時:(ms)" + watch.ElapsedMilliseconds); watch.Restart(); int count = EntityQuery<User>.Instance.QuickInsert(batchList); watch.Stop(); Console.WriteLine("QuickInsert List (100000 item) 耗時:(ms)" + watch.ElapsedMilliseconds); System.Threading.Thread.Sleep(1000); }
代碼說明:
上面的方法中首先初始化數據庫,經過DbContext對象自動建立數據表,而且經過TruncateTable 方法快速清除原來的測試數據。接着在內存中添加10萬條數據,而後將它使用QuickInsert方法快速插入到數據庫。
下面就能夠給出完整的測試過程了,直接看代碼:
static void Main(string[] args) { System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch(); watch.Start(); AdoHelper db = MyDB.GetDBHelperByConnectionName("local"); InitData(db, watch); long[] useTime1 = new long[10]; long[] useTime2 = new long[10]; long[] useTime3 = new long[10]; long[] useTime4 = new long[10]; for (int i = 0; i < 10; i++) { useTime1[i]= HandQuery(db, watch); System.Threading.Thread.Sleep(1000); //便於觀察CPU、內存等資源變化 useTime2[i] = QueryPOCO(db, watch); System.Threading.Thread.Sleep(1000); useTime3[i] = EntityQuery(db, watch); System.Threading.Thread.Sleep(1000); useTime4[i] = EntityQuery2(db, watch); System.Threading.Thread.Sleep(1000); Console.WriteLine("run test No.{0},sleep 1000 ms", i + 1); Console.WriteLine(); } //去掉熱身的第一次 useTime1[0] = 0; useTime2[0] = 0; useTime3[0] = 0; useTime4[0] = 0; Console.WriteLine("Avg HandQuery={0} ms, \r\n Avg QueryPOCO={1} ms, \r\n Avg SOD EntityQuery={2} ms,\r\n Avg EntityQuery2={3} ms" , useTime1.Average(),useTime2.Average(),useTime3.Average(), useTime4.Average()); Console.ReadLine(); }
測試過程去掉第一次循環測試的「熱身」過程,計算剩餘9次不一樣方式的平均執行時間,下面是在筆者筆記本電腦(Intel i7-4720HQ CPU 2.6GHz,12G RAM,普通硬盤)的測試結果:
須要初始化數據嗎?(Y/N) y 正在初始化數據,請稍後。。。。 ... 準備數據 耗時:(ms)225 QuickInsert List (100000 item) 耗時:(ms)5363 HandQuery List (100000 item) 耗時:(ms)158 QueryPOCO List (100000 item) 耗時:(ms)188 SOD QueryList List (100000 item) 耗時:(ms)251 EntityQuery2 List (10000 item) 耗時:(ms)281 run test No.1,sleep 1000 ms HandQuery List (100000 item) 耗時:(ms)139 QueryPOCO List (100000 item) 耗時:(ms)192 SOD QueryList List (100000 item) 耗時:(ms)194 EntityQuery2 List (10000 item) 耗時:(ms)283 run test No.2,sleep 1000 ms HandQuery List (100000 item) 耗時:(ms)156 QueryPOCO List (100000 item) 耗時:(ms)177 SOD QueryList List (100000 item) 耗時:(ms)224 EntityQuery2 List (10000 item) 耗時:(ms)289 run test No.3,sleep 1000 ms HandQuery List (100000 item) 耗時:(ms)183 QueryPOCO List (100000 item) 耗時:(ms)179 SOD QueryList List (100000 item) 耗時:(ms)213 EntityQuery2 List (10000 item) 耗時:(ms)265 run test No.4,sleep 1000 ms HandQuery List (100000 item) 耗時:(ms)172 QueryPOCO List (100000 item) 耗時:(ms)179 SOD QueryList List (100000 item) 耗時:(ms)226 EntityQuery2 List (10000 item) 耗時:(ms)273 run test No.5,sleep 1000 ms HandQuery List (100000 item) 耗時:(ms)172 QueryPOCO List (100000 item) 耗時:(ms)211 SOD QueryList List (100000 item) 耗時:(ms)192 EntityQuery2 List (10000 item) 耗時:(ms)229 run test No.6,sleep 1000 ms HandQuery List (100000 item) 耗時:(ms)202 QueryPOCO List (100000 item) 耗時:(ms)229 SOD QueryList List (100000 item) 耗時:(ms)191 EntityQuery2 List (10000 item) 耗時:(ms)240 run test No.7,sleep 1000 ms HandQuery List (100000 item) 耗時:(ms)190 QueryPOCO List (100000 item) 耗時:(ms)177 SOD QueryList List (100000 item) 耗時:(ms)218 EntityQuery2 List (10000 item) 耗時:(ms)274 run test No.8,sleep 1000 ms HandQuery List (100000 item) 耗時:(ms)166 QueryPOCO List (100000 item) 耗時:(ms)191 SOD QueryList List (100000 item) 耗時:(ms)197 EntityQuery2 List (10000 item) 耗時:(ms)229 run test No.9,sleep 1000 ms HandQuery List (100000 item) 耗時:(ms)179 QueryPOCO List (100000 item) 耗時:(ms)192 SOD QueryList List (100000 item) 耗時:(ms)213 EntityQuery2 List (10000 item) 耗時:(ms)253 run test No.10,sleep 1000 ms Avg HandQuery=155.9 ms, Avg QueryPOCO=172.7 ms, Avg SOD EntityQuery=186.8 ms, Avg EntityQuery2=233.5 ms
測試結果說明,SOD框架的QueryPOCO「微型ORM」功能性能不錯,雖然有數據裝箱過程,但仍然接近手寫代碼數據映射的方式。SOD框架最經常使用的EntityQuery實體查詢性能接近於QueryPOCO方式,而本次的測試方法4嘗試將類型化數據讀取到object數組對象也有裝箱過程,性能卻遠低於EntityQuery實體查詢方式。那麼測試方法4的EntityQuery2方法中若是不裝箱,直接採用讀取指定位置數據爲object類型可否性能明顯提高呢?好比將方法中下面的代碼:
Action< int, object[]> readInt = ( i, o) => { if (reader.IsDBNull(i)) o[i] = DBNull.Value; else o[i] = reader.GetInt32(i); };
修改成:
Action< int, object[]> readInt = ( i, o) => { if (reader.IsDBNull(i)) o[i] = DBNull.Value; else o[i] = reader.GetValue(i); };
經測試,修改先後性能沒用明顯的改善,二者性能基本相同。看來DataReader對象是否使用類型化數據讀取對性能沒用明顯的影響,也就是讀取的數據是否裝箱對於ORM的數據映射性能沒有明顯影響,ORM查詢過程當中對性能影響最大的應該是數據庫,而不是數據裝箱。測試方法4還說明了,將DataReader的數據一次性讀取到object[]對象數組中,性能要明顯高於逐字段讀取,不論是類型化讀取仍是非類型化讀取。
此次測試也說明,SOD框架的ORM性能與手寫代碼查詢映射的性能接近,沒有明顯的差距,SOD框架仍然是一個簡單、高效、可靠的,值得使用的數據開發框架。本次測試的所有代碼都在SOD項目解決方案的「SODTest」程序集項目中,源碼倉庫地址:https://github.com/znlgis/sod
------------------------------------------------------------------------------------------
最後值此元旦之際,向奮鬥在一線的廣大程序員朋友致敬!
爲感謝廣大SOD框架(原PDF.NET框架)用戶朋友和全部支持、關心的朋友,讓你們把「增刪改查」的項目作的更快、更好,筆者花了一年多時間寫了一本有關數據開發與架構實戰的書:《SOD框架「企業級」應用數據架構實戰》,目前出版社已經在校對階段,預計年後將跟讀者朋友見面,歡迎你們關注!
SOD框架高級用戶QQ羣:18215717