你應當知道的ORM的底層細節,DataReader高效使用的姿勢。 ...
前言
在各種ORM框架或者SQL映射框架(例如MyBatis,SOD框架之SQL-MAP功能)中,都有將查詢的結果映射為記憶體對象的需求,包括映射到實體類、簡單類型(例如Java的POJO,.NET的POCO)的對象。在.NET中,這個過程可以通過ADO.NET的DataReader對象來讀取數據,然後將讀取的數據映射到記憶體對象。本篇文章來討論下不同方式的數據讀取方式對性能的影響。
在寫這篇文章之前,我在想現在都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的非類型化數據讀取與類型化數據讀取的性能差異,演示下正確使用兩者的方式。
映射對象
下麵的測試方法都是將資料庫同樣的數據通過DataReader讀取出來映射到不同的對象中,本篇文章測試用來映射的對象一個是SOD框架的實體類,一個是普通的DTO對象,DTO是POCO的一種。下麵是這兩種對象的定義:
SOD實體對象類User的定義:
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完全一樣的屬性名稱和屬性類型:
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; } }
下麵開始不同的查詢方式測試。
1,手寫查詢映射
測試方案為將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方法的查詢等同於最高效的手寫查詢方式。
2,映射數據到POCO對象
上面的手寫測試代碼看起來簡單,但是必須清楚當前讀取欄位的索引位置和當前欄位的數據類型,當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; }
3,SOD框架的DataReader非類型化數據讀取
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,但不會有太大的性能差距。
4,類型化讀取到數組元素中
如果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方式,而本次嘗試將類型化數據讀取到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查詢過程中對性能影響最大的應該是資料庫,而不是數據裝箱。
這次測試也說明,SOD框架的ORM性能與手寫代碼查詢映射的性能接近,沒有明顯的差距,SOD框架仍然是一個簡單、高效、可靠的,值得使用的數據開發框架。本次測試的全部代碼都在SOD項目解決方案的“SODTest”程式集項目中,源碼倉庫地址:https://github.com/znlgis/sod
------------------------------------------------------------------------------------------
最後值此元旦之際,向奮鬥在一線的廣大程式員朋友致敬!
為感謝廣大SOD框架(原PDF.NET框架)用戶朋友和所有支持、關心的朋友,讓大家把“增刪改查”的項目做的更快、更好,筆者花了一年多時間寫了一本有關數據開發與架構實戰的書:《SOD框架“企業級”應用數據架構實戰》,目前出版社已經在校對階段,預計年後將跟讀者朋友見面,歡迎大家關註!
SOD框架高級用戶QQ群:18215717