OEA ORM中的分頁支持


    本篇博客主要描述分頁的常見技術方案,以及在 OEA 框架中的分頁的應用及實現原理。

 

分頁的幾種方案


    分頁是解決大數據量顯示的有效方法。根據分頁技術應用的位置不同,大致可以把分頁分為以下幾種:

  • 界面層分頁 

    界面層的分頁,類似於界面的虛擬化技術,是只顯示需要的數據的一種技術。OEA 的 WPF 界面中目前已經實現了 UI 虛擬化,所以不再實現界面層分頁。

優點:

* 簡單。許多控件都支持在界面層直接進行分頁。

* 換頁時,響應快。(在 C/S 結構下使用這種方案,數據都已經到達客戶端,所以在分頁時不需要額外的數據查詢,響應速度較快。)

缺點:

* 不用於太大的數據分頁。由於沒有減少網絡傳輸,首次加載時較慢,需要把所有數據都傳輸到客戶端。

  • 實體層分頁

    在實體層進行分頁操作的方案,很少會被使用。它是把查詢出來的數據,在服務器端都轉換為實體,然后再找到具體頁的實體數據,其它的數據則直接丟棄。

優點:

* 減少了首次的網絡傳輸,對於客戶端而言,調用的是分頁的 API。

* 簡單。

* 通用性強,與數據庫無關,方案可以跨多種數據庫。

* 統計總行數不需要發起二次查詢。

缺點:

* 占用內存,依然不能用於太大的數據分頁。

  • 數據層分頁 

    這種方案一般使用 IDataReader 實現。查詢的 SQL 依然是查詢所有的數據,但是在對查詢出的 IDataReader 進行遍歷讀取每一行時,只讀取對應頁的數據,其它頁的數據則忽略。同時,遍歷到記錄集的最后一行,即可獲得數據的總行數。

優點:

* 不占用大量內存。只把需要的數據讀取到內存中。

* 簡單。

* 通用性強,與數據庫無關,方案可以跨多種數據庫。

* 統計總行數不需要發起二次查詢。

缺點:

* 查詢的 SQL 會查詢很大的一張表。遍歷依然需要耗費一定的時間。

  • 數據庫分頁 

    分頁的最終方案,自然是在數據庫中進行分頁。這也是大多數情況會選用的方案。

優點:

* 性能最好。速度快、占用內存小。

* 統計行數時,往往需要重新發起查詢。

缺點:

* 對於框架開發而言,要生成分頁相關的 SQL,較麻煩。

* 方案與特定數據庫相關。通用性低。

    雖然提到了這幾種不同層面的分頁方案。但是對應應用開發而言,數據庫的分頁是最常用的。只是在做 OEA 框架開發時,由於要支持多種數據庫,所以需要在合適時采用不同的方案。同時,也不會考慮使用存儲過程來輔助分頁。


OEA 分頁 - 應用層接口


    在說明 OEA 的分頁前。先介紹一個 PagingInfo 類型(老版本中,該類名為 PagerInfo),這關系到整個分頁方案的接口設計:

圖1 PagingInfo

圖1 位於 Common(原 hxy)程序集中的 PagingInfo 類型

圖2 PagingInfo 接口

圖2 PagingInfo 類型接口

    在查詢數據時,我們指定了查詢的具體頁碼 PageIndex、一頁所含數據行數 PageSize,就可以把該頁的數據顯示在界面上了。但是,在分頁時,往往要在界面中顯示一個分頁腳,用於顯示當前頁號、所有頁數。所以在進行查詢的同時,往往還需要對結果集中所有數據的總行數進行統計,並把之與查詢出的實體列表數據一同返回。所以,我為 PagingInfo 添加了額外的兩個屬性,IsNeedCount、TotalCount,當 IsNeedCount 被設置為真時,框架在數據層進行查詢時,會把統計出來的總行數賦值給 TotalCount。

 

OEA 分頁 - 使用方法


    下面以分頁查詢所有數據為例,簡單說明如何使用分頁查詢。先是應用層使用的代碼:

使用代碼 - 應用層代碼

應用層需要構造 PagingInfo,並指定需要統計行數。查詢后,直接使用 PagingInfo.TotalCount。(這種接口方案從 06 年使用至今,比較好用。微笑

下面是 Repository 類型上的公有接口:

使用代碼 - Repository 接口代碼

最后,再實現該查詢對應的數據層即可:

使用代碼 - 數據層代碼

可以看到,在數據訪問層的 ORM 框架中,主要是在 IQuery 條件類型上添加了一個 Paging 方法。使用這個方法指定了 PagingInfo 后,即按給定的分頁信息分頁查詢實體數據了。


OEA 中的數據層分頁實現 


    OEA 中用到的分頁有:界面層分頁、DataReader 分頁、數據庫分頁。

 

  • 數據庫分頁(分頁SQL)

    目前,OEA 已經支持了 SqlServer 2005+、Oracle 10+、SqlCE4+,但是框架的設計目標則是應對所有數據庫(接下來很可能需要對 MySql 進行支持)。這三種數據庫中,OEA 只支持前兩種大型數據庫的數據庫分頁,主要是生成分頁 SQL 進行查詢。

    經過對比、挑選,我選用了一種可以在 SqlServer、Oracle 上的一種通用方案,即使用 RowNumber。例如,如果一個 SQL 查詢是:

select ...... from ...... order by xxxx asc, yyyy desc

,則只需要把它轉換為以下格式就行了:

select * from (select ......, row_number() over(order by xxxx asc, yyyy desc) _rowNumber from ......) x where x._rowNumber<10 and x._rowNumber>5

    同時,當需要統計總行數時,數據層會生成 SELECT COUNT(0) FROM ...... 的 SQL 語句重新進行查詢,並把結果賦值給 PagingInfo.TotalCount,以及 EntityList.TotalCount。

    在 SQLCE 中,並不支持 rowNumber 函數。所以只能考慮使用 NOT IN 的 SQL 方案。其實在OEA中,鑒於實現 NOT IN 方案比較麻煩,所以決定暫時使用 DataReader 完成 SQLCE 的內存分頁。

 

  • DataReader 內存分頁 

    提供 DataReader 方案主要是簡單、同時還能與數據庫無關,解決跨庫問題。主要邏輯代碼如下:

/// <summary>
///
使用 IDataReader 的內存分頁讀取方案。
///
///
注意!!!
/// 此方法中會釋放 Reader。外層不能再用 Using。
/// </summary>
/// <param name="reader"></param>
/// <param name="rowReader">
每一行數據,會調用此方法進行調取。</param>
/// <param name="pagingInfo">
分頁信息。如果這個參數不為空,則使用其中描述的分頁規則進行內存分頁查詢。</param>
public static void MemoryPaging(IDataReader reader, Action<IDataReader> rowReader, PagingInfo pagingInfo = null)
{
   
bool isPaging = pagingInfo != null;
   
bool needCount = isPaging && pagingInfo.IsNeedCount;
   
int totalCount = 0;
   
int startRow = 1;//從一開始的行號
   
int endRow = int.MaxValue;

   
if (isPaging)
    {
        startRow = pagingInfo.PageSize * pagingInfo.PageIndex + 1;
        endRow = startRow + pagingInfo.PageSize - 1;
    }

   
using (reader)
    {
       
while (reader.Read())
        {
            totalCount++;

           
if (totalCount >= startRow)
            {
               
if (totalCount <= endRow)
                {
                    rowReader(reader);
                }
               
else
               
{
                   
//如果已經超出該頁,而且需要統計行數,則直接快速循環到最后。
                   
if (needCount)
                    {
                       
while (reader.Read()) { totalCount++; }
                       
break;
                    }
                }
            }
        }
    }

   
if (needCount)
    {
        pagingInfo.TotalCount = totalCount;
    }
}

通用,又簡單。微笑

 

待改進點


目前實現上,可能存在的缺陷是:

  • 對分頁 SQL 的轉換不支持復雜的嵌套 SQL。這時可能出錯。

 

希望大伙拍磚。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM