前些日子,做了一個物業收費系統,cs模式,用到了linq to sql 技術,這是我第一次使用這個東東寫程序存取數據庫,迷迷糊糊搞得一塌糊塗,當時有個同學他們找好的分頁組件,然后寫好了調用方法,由於時間比較急,而且第一次用,所以沒有怎么研究就直接按照注釋使用他們寫好的分頁方法,然而開發過程中一直都對他們寫的方法有懷疑,會不會是一種投機取巧,胡編亂造的?后來我也做過一些簡單分析,我發現程序在業務邏輯層中每次都從數據庫中將數據全部讀取出來,然后循環將數據轉成特定的List,也就是遍歷整個數據集合,然后在顯示層中將List進行分頁,最后放到DataGridView中,其中列名自動設定為類的屬性值,剛開始我還一直認為這樣的方法好方便啊,一下子全部生成了,直到程序馬上接近尾聲時,我發現程序的操作日志記錄已經達到了4500條,每次打開日志管理界面時,程序都要加載上半天才能出來,我徹底對linq產生了懷疑,確切的說並不是對linq產生懷疑,而是分頁方法以及程序算法產生了懷疑。
顯示層的分頁代碼部分:
顯示層的分頁代碼部分:
1 dataGridView.DataSource = data.Take(pagerControl1.PageSize * pagerControl1.PageIndex).Skip(pagerControl1.PageSize * (pagerControl1.PageIndex - 1)).ToList();,其中data是業務邏輯層獲取到的數據,業務邏輯層: 2 3 4 var data = from d in jx.LogTable 5 orderby d.OperTime descending 6 where Tflag.Equals("全部") || 7 ((d.OperTime >= t1 && d.OperTime <= t2) && (d.OperName.Contains(content) || d.UserID.Contains(content))) 8 select d; 9 List<LogTable> items = new List<LogTable>(); 10 foreach (var d in data) 11 { 12 LogTable item = new LogTable(); 13 item.編號 = trim(d.ID); 14 item.事件 = trim(d.OperName); 15 item.操作員 = trim(d.UserID); 16 item.標志 = trim(d.mark); 17 item.操作時間 = trim(d.OperTime); 18 items.Add(item); 19 } 20 return items;
此處不僅將所有數據全部讀取出來了,而且還遍歷了一遍,當數據達到4500條時就出現了卡頓現象,情況糟糕程度可想而知,好丑陋的代碼啊,而且程序牽扯到這個的地方簡直多的要命,如果要改動的話,一定是一場災難!
之前一直在忙,沒有時間去測試程序中所存在的問題以及如何能更高效的使用linq開發應用程序,今天擠出點時間來做了個小小的測試,這讓我重新對linq產生了興趣。
測試中的數據庫表仍然不改變,還是操作日志表,不同的是我在該表中追加了更多的數據,總記錄數幾乎達到 110萬條,然后對這110萬條數據進行測試。具體測試如下:
當點擊數據總條數時,彈出數據庫操作日志表的總記錄數,點擊加載數據時,對110萬條數據進行分頁 , 每頁10條,取第三頁的10條數據,點清空數據時,對表格中加載的數據進行清空處理:
總條數如下:
首次運行,點擊加載數據時,共耗時9100毫秒:
於是,為了測試效率,我將數據追加到了220萬條,重新測試:
之前一直在忙,沒有時間去測試程序中所存在的問題以及如何能更高效的使用linq開發應用程序,今天擠出點時間來做了個小小的測試,這讓我重新對linq產生了興趣。
測試中的數據庫表仍然不改變,還是操作日志表,不同的是我在該表中追加了更多的數據,總記錄數幾乎達到 110萬條,然后對這110萬條數據進行測試。具體測試如下:
當點擊數據總條數時,彈出數據庫操作日志表的總記錄數,點擊加載數據時,對110萬條數據進行分頁 , 每頁10條,取第三頁的10條數據,點清空數據時,對表格中加載的數據進行清空處理:



然后點擊清空數據,再次點擊加載數據時,共耗時2899毫秒:

之后重復測試,時間一直保持在3000毫秒左右,也就是3秒鍾,第二次點擊后發現時間明顯加快了,3倍有余,但是我想說的是這個速度還是非常非常慢的,因為表中的數據列所存儲的值很小,沒有什么大的數據,為什么會這么慢呢?看代碼:
1 Stopwatch sw = new Stopwatch(); 2 sw.Start(); //開始計時 3 4 var data = (from d in db.Log select d).ToList();//將數據全部查詢出來,並且ToList() 5 dataGridView1.DataSource = data.Skip(20).Take(10).ToList();//分頁 6 7 sw.Stop(); //計時結束 8 MessageBox.Show("共耗時:"+sw.ElapsedMilliseconds.ToString()+"毫秒"); 9
然后我對代碼做了改變,如下:
1 Stopwatch sw = new Stopwatch(); 2 sw.Start(); //開始計時 3 4 var data = (from d in db.Log select d);//只是寫好了查詢條件,注意此處 5 dataGridView1.DataSource = data.Skip(20).Take(10).ToList();//分頁 6 7 sw.Stop(); //計時結束 8 MessageBox.Show("共耗時:"+sw.ElapsedMilliseconds.ToString()+"毫秒");
其實只是將上邊標紅的地方刪掉了,也就是ToList()部分, 采用控制變量法,其他數據和條件全部不做任何變動,看效果:
首次點擊加載數據時,共耗時185毫秒:
首次點擊加載數據時,共耗時185毫秒:

清空后,第二次點擊加載數據時,共耗時39毫秒:

之后測試,一直保持在40毫秒左右,差距為什么會這么大?
其實,LINQ執行過程的一個重要特征是延遲加載,就是知道要獲取數據時,才會進行計算。大家可能認為執行完var data = from db.Log select d語句,然后在開始skip,take函數進行分頁,所有的值都會存到data中了,實際上,這條語句會延遲到foreach或者ToList()調用時才會執行。而var data = (from db.Log select d)Skip(20).Take(10);語句執行完之后,程序只是生成了一個比較完美的sql語句等待着執行,直到ToList()或者foreach出現,也就是一直等待程序需要獲取數據時才開始執行數據庫查詢,這就解釋了為什么差距會這么大的問題,同時也說明了linq進行分頁的效率還是非常可以的,在110萬條記錄下進行分頁最多需要大約200毫秒時間,最快大約40毫秒。
一定要注意:先分頁在獲取,而不是先獲取再分頁!
一定要注意:先分頁在獲取,而不是先獲取再分頁!



大功告成,經過測試,在linq分頁前不能調用讀取方法,應該分頁后再查詢...看來本次的項目開發可以大膽的繼續使用linq來操作數據庫了。當然,之前的物業管理系統,等忙完這一段時間,我得幫忙改改去,做人得負責任啊!!!
沒文化真可怕,這么簡單的知識竟然現在才知道!
我的網址是:http://www.yxxrui.cn