DataReader類型化數據讀取與裝箱性能研究

来源:https://www.cnblogs.com/bluedoctor/archive/2020/01/02/12128475.html
-Advertisement-
Play Games

你應當知道的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

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本書介紹瞭如何利用Python 3開髮網絡爬蟲,書中首先介紹了環境配置和基礎知識,然後討論了urllib、requests、正則表達式、Beautiful Soup、XPath、pyquery、數據存儲、Ajax數據爬取等內容,接著通過多個案例介紹了不同場景下如何實現數據爬取,*後介紹了pyspid... ...
  • 事務一般是指資料庫事務,是指作為一個程式執行單元執行的一系列操作,要麼完全執行,要麼完全不執行。事務就是判斷以結果為導向的標準。 一.spring的特性(ACID) (1).原子性(atomicity) 原子性就是一個不可分割的工作單元。簡單的說,就是指事務包含的所有操作要麼全部成功,要麼全部失敗回 ...
  • 背景 上文JDK8中的HashMap源碼寫了HashMap,這次寫ConcurrentHashMap ConcurrentHashMap源碼 /** * Maps the specified key to the specified value in this table. * Neither th ...
  • 背景 很久以前看過源碼,但是猛一看總感覺挺難的,很少看下去。當時總感覺是水平不到。工作中也遇到一些想看源碼的地方,但是遇到寫的複雜些的心裡就打退堂鼓了。 最近在接手同事的代碼時,有一些很長的python腳本,沒有一行註釋。就硬著頭皮一行一行的讀,把理解的都加上註釋,這樣一行行看下來,終於知道代碼的意 ...
  • 新電腦clone項目後發現Project Interpreter無法配置, New environment 選擇後無法應用, 滑鼠懸停在Location 提示 Environment location directory is not empty . 原因是項目push時, 項目下的venv文件夾也 ...
  • crm業務的流程圖,都是比較精簡的內容,後面有機會的話會繼續擴展 ...
  • 前面已經講解了task的運行、阻塞、同步、延續操作、取消等!今天我們就專門來聊聊關於async/await的那一些事,分析其實現原理,通過該文章你也該對async的使用還有更加清晰的理解 ...
  • 本筆記摘抄自:https://www.cnblogs.com/liyangLife/p/4797583.html,記錄一下學習過程以備後續查用。 一、文件系統 1.1文件系統類的介紹 文件操作類大都在System.IO命名空間里,FileSystemInfo類是所有文件系統類的基類。FileInfo ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...