天天看點

OEA ORM中的分頁支援

 本篇部落客要描述分頁的常見技術方案,以及在 oea 架構中的分頁的應用及實作原理。

分頁的幾種方案

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

界面層分頁 

    界面層的分頁,類似于界面的虛拟化技術,是隻顯示需要的資料的一種技術。oea 的 wpf 界面中目前已經實作了 ui 虛拟化,是以不再實作界面層分頁。

優點:

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

* 換頁時,響應快。(在 c/s 結構下使用這種方案,資料都已經到達用戶端,是以在分頁時不需要額外的資料查詢,響應速度較快。)

缺點:

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

實體層分頁

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

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

* 簡單。

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

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

* 占用記憶體,依然不能用于太大的資料分頁。

資料層分頁 

    這種方案一般使用 idatareader 實作。查詢的 sql 依然是查詢所有的資料,但是在對查詢出的 idatareader 進行周遊讀取每一行時,隻讀取對應頁的資料,其它頁的資料則忽略。同時,周遊到記錄集的最後一行,即可獲得資料的總行數。

* 不占用大量記憶體。隻把需要的資料讀取到記憶體中。

* 查詢的 sql 會查詢很大的一張表。周遊依然需要耗費一定的時間。

資料庫分頁 

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

* 性能最好。速度快、占用記憶體小。

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

* 對于架構開發而言,要生成分頁相關的 sql,較麻煩。

* 方案與特定資料庫相關。通用性低。

    雖然提到了這幾種不同層面的分頁方案。但是對應應用開發而言,資料庫的分頁是最常用的。隻是在做 oea 架構開發時,由于要支援多種資料庫,是以需要在合适時采用不同的方案。同時,也不會考慮使用存儲過程來輔助分頁。

oea 分頁 - 應用層接口

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

OEA ORM中的分頁支援

圖1 位于 common(原 hxy)程式集中的 paginginfo 類型

OEA ORM中的分頁支援

圖2 paginginfo 類型接口

    在查詢資料時,我們指定了查詢的具體頁碼 pageindex、一頁所含資料行數 pagesize,就可以把該頁的資料顯示在界面上了。但是,在分頁時,往往要在界面中顯示一個分頁腳,用于顯示目前頁号、所有頁數。是以在進行查詢的同時,往往還需要對結果集中所有資料的總行數進行統計,并把之與查詢出的實體清單資料一同傳回。是以,我為 paginginfo 添加了額外的兩個屬性,isneedcount、totalcount,當 isneedcount 被設定為真時,架構在資料層進行查詢時,會把統計出來的總行數指派給 totalcount。

oea 分頁 - 使用方法

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

OEA ORM中的分頁支援

應用層需要構造 paginginfo,并指定需要統計行數。查詢後,直接使用 paginginfo.totalcount。(這種接口方案從 06 年使用至今,比較好用。

OEA ORM中的分頁支援

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

OEA ORM中的分頁支援

最後,再實作該查詢對應的資料層即可:

OEA ORM中的分頁支援

可以看到,在資料通路層的 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; 

}

通用,又簡單。

OEA ORM中的分頁支援

待改進點

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

對分頁 sql 的轉換不支援複雜的嵌套 sql。這時可能出錯。

希望大夥拍磚。