top
Loading...
創建用于ASP.NET的分頁控件
天極IT資訊短信服務 電腦小技巧
資費:包月5元
手機:
介紹:細處著手,巧處用功。高手和菜鳥之間的差別就是:高手什么都知道,菜鳥知道一些。電腦小技巧收集最新奇招高招,讓你輕松踏上高手之路。


從程序員的角度來看,Microsoft SQL Server? 查詢的最大缺陷之一就是返回的行數通常比應用程序的用戶界面實際可以容納的行數要多得多。這種尷尬情形經常將開發人員陷于困境。開發人員是應該創建一個非常長的頁面,讓用戶花時間去滾動瀏覽,還是應該通過設置一個手動分頁機制來更好地解決這個問題?

哪種解決方案更好,在很大程度上取決于要檢索的數據的特性。由多個項目(如搜索結果)組成的較長列表,最好通過各頁大小相等、每頁相對較短的多個頁面顯示。由單個項目(如文章的文本)組成的較長列表,如果整個插入在一個頁面中,使用起來會更方便。最后得出的分析結果是,應該根據應用程序的總體用途來做決定。那么,Microsoft ASP.NET 是如何解決數據分頁問題的呢?

ASP.NET 提供了功能強大的數據綁定控件,以便將查詢結果格式化為 HTML 標記。但是,這些數據綁定控件中只有一種控件(即 DataGrid 控件)本來就支持分頁。其他控件(如 DataList、Repeater 或 CheckBoxList)則不支持分頁。這些控件及其他列表控件不支持分頁,不是因為它們在結構上不支持分頁,而是因為它們與 DataGrid 不同,不包含任何處理分頁的特定代碼。但是,處理分頁的代碼是相當樣板化的,可以添加到所有這些控件中。

Scott Mitchell 在最近的一篇題目為“Creating a Pageable, Sortable DataGrid”的文章中,介紹了DataGrid分頁。該文還引用了Web 上的其他有用信息,為您提供了有關網格分頁基礎知識和其他信息。如果想查看如何使 DataList 控件可以進行分頁的示例,可以查看此文章(英文)。該文演示了如何創建一個自定義的 DataList 控件,該控件具有當前索引和頁面大小屬性,并可以啟動頁面更改事件。

同樣的代碼也可以用于滿足其他列表控件(如 ListBox 和 CheckBoxList)的分頁需要。不過,向各個控件添加分頁功能實際上并不是一種非常好的做法,因為,如上所述,分頁代碼是相當樣板化的。因此,對于聰明的程序員來說,有什么方法比使用一種新的通用分頁程序控件來實現所有這些控件的分頁功能更好的呢?

本文中將建立一個分頁程序控件,它將使合作者列表控件能夠對 SQL Server 的查詢結果進行分頁。該控件名為 SqlPager,它支持兩種類型的合作者控件 - 列表控件和基礎數據列表控件。

SqlPager控件的顯著特點

SqlPager控件是一個 ASP.NET復合控件,包含一個單行表格。該行又包含兩個單元格 - 導航條和頁面描述符。該控件的用戶界面呈條形,理想情況下,其寬度與合作者控件的寬度相同。導航條部分提供了可單擊的元素,以便在頁面之間移動;頁面描述符部分為用戶提供了有關當前顯示的頁面的一些反饋信息。


圖 1:Visual Studio .NET 網頁設計器中顯示的 SqlPager 控件

與 DataGrid 控件的嵌入式分頁程序一樣,SqlPager 控件具有兩種導航模式,即下一頁/上一頁和數字頁面。此外,其特殊屬性 PagerStyle 使您能夠選擇更方便的樣式。該控件與列表控件協同工作。您可以通過 ControlToPaginate 字符串屬性為分頁程序指定一個這樣的合作者控件。

SqlPager1.ControlToPaginate = "ListBox1";

一般情況下,分頁程序首先獲取 SQL Server 的查詢結果,準備一個適當的記錄頁面,然后通過合作者控件的 DataSource 屬性顯示該頁面。當用戶單擊以查看新頁面時,分頁程序將檢索請求的數據并再次通過合作者控件來顯示數據。分頁機制對于列表控件是完全透明的。列表控件的數據源是通過編程方式進行更新的,任何時候都只包含適合當前頁面的記錄。

控件的分頁引擎具有多個 public 屬性,如 CurrentPageIndex、ItemsPerPage 和 PageCount,通過這些屬性來獲取并設置當前頁面的索引、每個頁面的大小以及要顯示的頁面的總數。分頁程序管理數據檢索和分頁所需的任何邏輯。

SelectCommand 屬性設置獲取數據所用的命令文本。ConnectionString 屬性定義數據庫的名稱和位置以及連接憑據。執行查詢時采用的方式取決于 PagingMode 屬性的值。該屬性的可能值為與其同名的 PagingMode 枚舉的值 - Cached 和 NonCached。如果選擇 Cached 選項,則將使用數據適配器和 DataTable 對象檢索整個結果集。可以選擇將結果集放置在 ASP.NET 的 Cache 對象中,該結果集可以重復使用直到過期。如果選擇 NonCached 選項,則查詢只檢索適合當前頁面的記錄。這時,ASP.NET 的 Cache 中不放置任何數據。NonCached 模式與 DataGrid 控件的自定義分頁模式幾乎相同。

下表列出 SqlPager 控件的全部編程接口。

表 1:SqlPager 控件的編程接口

名稱類型說明
CacheDuration屬性指示數據在 ASP.NET 的緩存中保留的秒數。只用于 Cached 模式。默認值為60。
ConnectionString屬性用來訪問所選擇的 SQL Server 數據庫的連接字符串。
ControlToPaginate屬性同一個 .aspx 頁面中的控件 ID,它將顯示分頁程序檢索的記錄頁面。這是合作者控件。
CurrentPageIndex屬性獲取并設置基于 0 的頁面索引。
ItemsPerPage屬性獲取并設置每頁要顯示的記錄數量。默認值為每頁顯示 10 個項目。
PagerStyle屬性該值指示分頁程序用戶界面的樣式。它可以為 PagerStyle 枚舉值:NextPrev 和 NumericPages 之一。在 NextPrev 模式中,將顯示 VCR 式的按鈕,來轉到第一頁、上一頁、下一頁和最后一頁。而在 NumericPages 模式中,將顯示一個下拉列表,列出所有可用頁面的索引。
PagingMode屬性該值指示檢索數據的方式。它可以為 PagingMode 枚舉值:Cached 和 NonCached 之一。如果為 Cached,則將使用數據適配器,且整個結果集將臨時放置在 ASP.NET 緩存中。如果為 NonCached,則只檢索當前頁面中的記錄。在這種情況下,不進行緩存。
SelectCommand屬性 用來進行查詢的命令文本。最好為 SELECT-FROM-WHERE 形式。不支持 ORDER BY 子句。排序是通過 SortField 屬性另外指定的。
SortField屬性用來排序的字段的名稱。此字段用于為查詢提供動態的 ORDER BY 子句。排序是由 SQL Server 執行的。
ClearCache方法刪除存儲在 ASP.NET 緩存中的任何數據。
PageIndexChanged 事件默認事件,當分頁程序移動到另一個頁面時發生。事件的數據結構為 PageChangedEventArgs 類,包含舊頁面和新頁面的索引。
   
   

由于 SqlPager 控件繼承了 WebControl,因此它也具有很多與 UI 相關的屬性來管理字體、邊框和顏色。



生成 SqlPager 控件

將作為復合控件來生成 SqlPager 控件并讓其繼承 WebControl 類。復合控件是 ASP.NET 服務器控件所特有的,它是由一個或多個服務器控件組成。

public class SqlPager : WebControl, INamingContainer
{ ... }

除非生成完全自定義的控件或擴展現有的控件,否則,創建新控件時,大多數時間實際上是在生成復合控件。要創建 SqlPager,組合一個 Table 控件,并根據分頁程序的樣式,組合幾個 LinkButton 控件或者一個 DropDownList 控件。

生成復合控件時,需要注意幾條原則。首先,需要覆蓋 CreateChildControls protected 方法。CreateChildControls 方法是從 Control 繼承來的,當服務器控件為了顯示而要創建子控件時或在返回后,將調用此方法。

protected override void CreateChildControls()
{
// 清除現有的子控件及其 ViewState
Controls.Clear();
ClearChildViewState();

// 生成控件樹
BuildControlHierarchy();
}

覆蓋此方法時,需要執行幾項重要的操作。創建并初始化任何所需的子控件實例并將它們添加到父控件的 Controls 集合中。但是,生成新控件樹之前,應該刪除任何現有的子控件并清除子控件可能留下的任何 ViewState 信息。

復合組件還需要實現 INamingContainer 接口,以便 ASP.NET 運行時可以為其創建一個新的命名范圍。這就確保了復合控件中的所有控件都具有唯一的名稱。這還將確保能夠自動處理子控件的返回數據。

對于 SqlPager 控件來說,成為命名容器非常重要。事實上,SqlPager 包含一些 LinkButton 控件,并且需要獲取并處理其單擊事件以便導航頁面。正如 ASP.NET 頁面中的任何其他控件一樣,LinkButton 也被賦予了一個 ID,用于標識處理返回事件的控件。

處理返回事件時,ASP.NET 運行時試圖查找事件的目標 ID 與主窗體的任何直接子控件之間是否存在匹配關系。LinkButton 是分頁程序的子控件,因此不能運行其服務器端的代碼。這是否意味著只有窗體的直接子控件才能啟動并處理服務器事件?當然不是,只要您使用命名容器。

通過使 SqlPager 控件實現 INamingContainer 接口,可以將嵌入式鏈接按鈕的實際 ID 從“First”更改為“SqlPager1:First”。當用戶單擊以查看新頁面時,返回事件將 SqlPager1:First 作為目標控件。實際上,運行時用來識別目標控件的算法比上面介紹的要復雜一些。運行時將事件目標控件的名稱看作是用冒號分隔開的字符串。實際上,這種匹配是在窗體的子控件和用冒號分隔開的字符串(如 SqlPager1:First)的第一個標記之間進行的。由于分頁程序是窗體的子控件,因此匹配時會成功,分頁程序獲取單擊事件。如果您認為這種解釋不夠充分或者令人費解,只要下載 SqlPager 控件的源代碼,刪除 INamingContainer 標記接口并進行重新編譯即可。您將看到分頁程序能夠返回,但不能內部處理單擊事件。

INamingContainer 接口是一個不具備方法的標記接口,其實現只需在類聲明中指定名稱即可,無需進行任何其他操作。

復合控件的另一個重要方面是,它們通常不需要自定義邏輯來進行顯示。復合控件的顯示遵循其組成控件的顯示。生成復合控件時,通常無需覆蓋 Render 方法。

控件的 SqlPager 樹由一個包含兩個單元格的單行表格組成。該表格繼承了分頁程序的大部分可視設置,如前景顏色和背景顏色、邊框、字體信息和寬度等。第一個單元格包含導航條,其結構取決于 PagerStyle 屬性的值。如果分頁程序的樣式為 NextPrev,則導航條將由四個 VCR 式的按鈕組成。否則,它將由一個下拉列表組成。

private void BuildControlHierarchy()
{
// 生成環境表格(一行,兩個單元格)
Table t = new Table();
t.Font.Name = this.Font.Name;
t.Font.Size = this.Font.Size;
t.BorderStyle = this.BorderStyle;
t.BorderWidth = this.BorderWidth;
t.BorderColor = this.BorderColor;
t.Width = this.Width;
t.Height = this.Height;
t.BackColor = this.BackColor;
t.ForeColor = this.ForeColor;

// 生成表格中的行
TableRow row = new TableRow();
t.Rows.Add(row);

// 生成帶有導航條的單元格
TableCell cellNavBar = new TableCell();
if (PagerStyle == this.PagerStyle.NextPrev)
BuildNextPrevUI(cellNavBar);
else
BuildNumericPagesUI(cellNavBar);
row.Cells.Add(cellNavBar);

// 生成帶有頁面索引的單元格
TableCell cellPageDesc = new TableCell();
cellPageDesc.HorizontalAlign = HorizontalAlign.Right;
BuildCurrentPage(cellPageDesc);
row.Cells.Add(cellPageDesc);

// 將表格添加到控件樹
this.Controls.Add(t);
}

將各個控件添加到正確的 Controls 集合對于分頁程序的正確顯示極其重要。最外層的表格必須添加到分頁程序的 Controls 集合中。鏈接按鈕和下拉列表必須添加到相應表格單元格的 Controls 集合中。

下面給出了用來生成鏈接按鈕導航條的代碼。每個按鈕都顯示有一個 Webdings 字符,可以根據需要禁用,并被綁定到內部的 Click 事件處理程序。

private void BuildNextPrevUI(TableCell cell)
{
bool isValidPage = ((CurrentPageIndex >=0) &&
(CurrentPageIndex <= TotalPages-1));
bool canMoveBack = (CurrentPageIndex>0);
bool canMoveForward = (CurrentPageIndex<TotalPages-1);

// 顯示 << 按鈕
LinkButton first = new LinkButton();
first.ID = "First";
first.Click += new EventHandler(first_Click);
first.Font.Name = "webdings";
first.Font.Size = FontUnit.Medium;
first.ForeColor = ForeColor;
first.ToolTip = "第一頁";
first.Text = "7";
first.Enabled = isValidPage && canMoveBack;
cell.Controls.Add(first);
:
}

分頁程序的另一種樣式(在下拉列表中列出數字頁面)的生成方法如下所示:

private void BuildNumericPagesUI(TableCell cell)
{
// 顯示一個下拉列表
DropDownList pageList = new DropDownList();
pageList.ID = "PageList";
pageList.AutoPostBack = true;
pageList.SelectedIndexChanged += new EventHandler(PageList_Click);
pageList.Font.Name = this.Font.Name;
pageList.Font.Size = Font.Size;
pageList.ForeColor = ForeColor;

if (TotalPages <=0 || CurrentPageIndex == -1)
{
pageList.Items.Add("No pages");
pageList.Enabled = false;
pageList.SelectedIndex = 0;
}
else // 填充列表
{
for(int i=1; i<=TotalPages; i++)
{
ListItem item = new ListItem(i.ToString(), (i-1).ToString());
pageList.Items.Add(item);
}
pageList.SelectedIndex = CurrentPageIndex;
}
}

所有事件處理程序(Click 和 SelectedIndexChanged)最終都會更改當前顯示的頁面。這兩種方法都會調用一個公用的 private 方法 GoToPage。

private void first_Click(object sender, EventArgs e)
{
GoToPage(0);
}
private void PageList_Click(object sender, EventArgs e)
{
DropDownList pageList = (DropDownList) sender;
int pageIndex = Convert.ToInt32(pageList.SelectedItem.Value);
GoToPage(pageIndex);
}
private void GoToPage(int pageIndex)
{
// 準備事件數據
PageChangedEventArgs e = new PageChangedEventArgs();
e.OldPageIndex = CurrentPageIndex;
e.NewPageIndex = pageIndex;

// 更新當前的索引
CurrentPageIndex = pageIndex;

// 啟動頁面更改事件
OnPageIndexChanged(e);

// 綁定新數據
DataBind();
}

其他導航按鈕的處理程序與 first_Click 的區別僅在于它們傳遞給 GoToPage 方法的頁碼不同。GoToPage 方法負責處理 PageIndexChanged 事件,并負責啟動數據綁定過程。它準備事件數據(舊頁面和新頁面索引)并觸發事件。GoToPage 被定義為 private,但是可以使用 CurrentPageIndex 屬性通過編程的方式更改顯示的頁面。

public int CurrentPageIndex
{
get {return Convert.ToInt32(ViewState["CurrentPageIndex"]);}
set {ViewState["CurrentPageIndex"] = value;}
}

與表 1 中所列的所有屬性一樣,CurrentPageIndex 屬性的實現方法也相當簡單。它將其內容保存到 ViewState 中并從中進行還原。在數據綁定過程中,會驗證和使用頁面索引。

數據綁定過程

DataBind 方法是所有 ASP.NET 控件公用的,對于數據綁定控件來說,它將觸發用戶界面的刷新以反映新數據。SqlPager 控件根據 SelectCommand 和 ConnectionString 屬性的值,使用此方法啟動數據檢索操作。不言而喻,如果這些屬性中的任何一個為空,該過程將終止。同樣,如果合作者控件不存在,數據綁定過程將被取消。要查找合作者控件,DataBind 方法使用 Page 類中的 FindControl 函數。由此可見,合作者控件必須為主窗體的直接子控件。

進行分頁顯示的控件不能為任意的 ASP.NET 服務器控件。它必須為列表控件或基本數據列表控件。更一般來說,合作者控件必須具有 DataSource 屬性并實現 DataBind 方法。可能進行分頁的控件實際上只需要滿足這些要求。Microsoft? .NET Framework 中所有繼承 ListControl 或 BaseDataList 的控件都滿足第一個要求;而所有 Web 控件通過設計都滿足 DataBind 要求。使用當前的實現方法,無法使用 SqlPager 控件來對 Repeater 進行分頁。Repeater 與合作者控件 DataList 和 DataGrid 不同,不繼承 BaseDataList,也不提供列表控件的功能。下表列出了可以使用 SqlPager 進行分頁的控件。

表 2:可以由 SqlPager 控件進行分頁的數據綁定控件

控件說明
CheckBoxList從 ListControl 派生而來,顯示為復選框列表。
DropDownList從 ListControl 派生而來,顯示為字符串下拉列表。
ListBox從 ListControl 派生而來,顯示為字符串可滾動列表。
RadioButtonList 從 ListControl 派生而來,顯示為單選按鈕列表。
DataList從 BaseDataList 派生而來,顯示為模板化數據項目列表。
DataGrid 從 BaseDataList 派生而來,顯示為數據項目的表格網格。DataGrid 是唯一一個內置有功能強大的分頁引擎的 ASP.NET 控件。

以下代碼說明由 SqlPager 控件實現的數據綁定過程。

public override void DataBind()
{
// 啟動數據綁定事件
base.DataBinding();

// 數據綁定后必須重新創建控件
ChildControlsCreated = false;

// 確保控件存在且為列表控件
_controlToPaginate = Page.FindControl(ControlToPaginate);
if (_controlToPaginate == null)
return;
if (!(_controlToPaginate is BaseDataList || _controlToPaginate is ListControl))
return;

// 確保具有足夠的連接信息并指定查詢
if (ConnectionString == "" || SelectCommand == "")
return;

// 獲取數據
if (PagingMode == PagingMode.Cached)
FetchAllData();
else
FetchPageData();

// 將數據綁定到合作者控件
BaseDataList baseDataListControl = null;
ListControl listControl = null;
if (_controlToPaginate is BaseDataList)
{
baseDataListControl = (BaseDataList) _controlToPaginate;
baseDataListControl.DataSource = _dataSource;
baseDataListControl.DataBind();
return;
}
if (_controlToPaginate is ListControl)
{
listControl = (ListControl) _controlToPaginate;
listControl.Items.Clear();
listControl.DataSource = _dataSource;
listControl.DataBind();
return;
}
}

根據 PagingMode 屬性的值調用不同的獲取例程。在任何情況下,結果集都綁定到 PagedDataSource 類的實例上。此類提供了一些用來對數據進行分頁的功能。特別是,當整個數據集都存儲在緩存中時,該類將自動檢索當前頁面的記錄并返回布爾值,來提供有關第一頁和最后一頁的信息。稍后將回來介紹此類的內部結構。在上述列表中,幫助程序的 PagedDataSource 對象是由 _dataSource 變量表示的。

然后,SqlPager 控件經過計算得出合作者控件的類型,并將 PagedDataSource 對象的內容綁定到合作者控件的 DataSource 屬性。

有時,上述的 DataBind 方法還將 ChildControlsCreated 屬性重新設置為 false。那么,為什么要這樣做呢?

當包含分頁程序的頁面返回時,所有控件都要重新創建;分頁程序也不例外。通常情況下,所有控件及其子控件都是在準備顯示頁面之前創建的。在每個控件接收到 OnPreRender 通知之前的一瞬間,protected EnsureChildControls 方法將被調用,這樣,每個控件都可以生成自己的控件樹。此事件發生后,數據綁定過程完成,新數據已存儲到緩存中。

但是,當由于單擊分頁程序的一個組成控件而使頁面返回時(即用戶單擊以更改頁面),就會生成分頁程序的控件樹,這時遠未達到顯示階段。特別是,當處理相關的服務器端事件時,就必須生成控件樹,因而是在數據綁定開始之前生成控件樹。困難在于,數據綁定將修改頁面索引,而這必須反映在用戶界面中。如果不采取某些對策的話,當用戶切換到另一頁時,分頁程序中的頁面索引將不會被刷新。

有各種方法可以解決此問題,但重要的是要弄清楚問題及其真正的原因。您可以避免生成控件樹,并在 Render 方法中生成所有輸出。另外,您還可以修改樹中受數據綁定更改影響的部分。本文選擇了第三種方法,這種方法需要較少的代碼,而且,不管控件的用戶界面的哪個部分受到數據綁定更改的影響,都能夠解決問題。通過將 ChildControlsCreated 屬性設置為 false,可以使以前創建的任何控件樹無效。這樣,在顯示之前將重新創建控件樹。

分頁引擎

SqlPager 控件支持兩種檢索數據的方法 - 緩存和非緩存。如果采用前者,選擇命令原樣執行,整個結果集將綁定到在內部進行分頁的數據源對象上。PagedDataSource 對象將自動返回適合特定頁面的記錄。PagedDataSource 類也是在 DataGrid 默認分頁機制背后運行的系統組件。

檢索所有記錄但只顯示適合頁面的幾個記錄,這通常不是一種明智的方法。由于 Web 應用程序的無狀態特性,事實上,每次請求頁面時都可能運行大量的查詢。要使操作有效,采用緩存的數據檢索方法必須依賴于某種緩存對象,ASP.NET 的 Cache 對象就是一個很好的候選對象。緩存技術的使用加快了應用程序的運行速度,但其提供的數據快照不能反映最新的更改。另外,它需要使用 Web 服務器上的較多內存。而且荒謬的是,如果大量的數據按會話緩存的話,甚至可能造成很大的問題。Cache 容器對于應用程序來說是全局的;如果數據按會話存儲在其中,還需生成會話特有的項目名稱。

Cache 對象的上面是完全支持過期策略的。換句話說,存儲在緩存中的數據在一段時間后可以自動被釋放。以下代碼說明 SqlPager 類中用來獲取數據并將其存儲在緩存中的一個 private 方法。

private void FetchAllData()
{
// 在 ASP.NET Cache 中查找數據
DataTable data;
data = (DataTable) Page.Cache[CacheKeyName];
if (data == null)
{
// 使用 order-by 信息修改 SelectCommand
AdjustSelectCommand(true);

// 如果數據過期或從未被獲取,則轉到數據庫
SqlDataAdapter adapter = new SqlDataAdapter(SelectCommand, ConnectionString);
data = new DataTable();
adapter.Fill(data);
Page.Cache.Insert(CacheKeyName, data, null,
DateTime.Now.AddSeconds(CacheDuration),
System.Web.Caching.Cache.NoSlidingExpiration);
}

// 配置分頁的數據源組件
if (_dataSource == null)
_dataSource = new PagedDataSource();
_dataSource.DataSource = data.DefaultView;
_dataSource.AllowPaging = true;
_dataSource.PageSize = ItemsPerPage;
TotalPages = _dataSource.PageCount;

// 確保頁面索引有效
ValidatePageIndex();
if (CurrentPageIndex == -1)
{
_dataSource = null;
return;
}

// 選擇要查看的頁面
_dataSource.CurrentPageIndex = CurrentPageIndex;
}

控件和請求的緩存項目名稱是唯一的。它包括頁面的 URL 和控件的 ID。在指定的時間(以秒計算)內,數據被綁定到緩存。要使項目過期,必須使用 Cache.Insert 方法。以下較簡單的代碼將項目添加到緩存,但不包括任何過期策略。

Page.Cache[CacheKeyName] = data;

PagedDataSource 對象通過其 DataSource 屬性獲取要進行分頁的數據。值得注意的是,PagedDataSource 類的 DataSource 屬性只接受 IEnumerable 對象。DataTable 不滿足此要求,這就是為什么采取 DefaultView 屬性的原因。

SelectCommand 屬性確定在 SQL Server 數據庫上運行的查詢。此字符串最好為 SELECT-FROM-WHERE 形式。不支持 ORDER BY 子句,如果指定了該子句,將被刪除。這正是 AdjustSelectCommand 方法所做的。使用 SortField 屬性可以指定任何排序信息。AdjustSelectCommand 方法本身將根據 SortField 的值添加一個正確的 ORDER BY 子句。這樣做有什么原因嗎?

當分頁程序以 NonCached 模式工作時,原始的查詢將被修改以確保只檢索當前頁面的記錄。在 SQL Server 上執行的實際查詢文本將采取以下形式。

SELECT * FROM
(SELECT TOP ItemsPerPage * FROM
(SELECT TOP ItemsPerPage*CurrentPageIndex * FROM
(SelectCommand) AS t0
ORDER BY SortField ASC) AS t1
ORDER BY SortField DESC) AS t2
ORDER BY SortField

該查詢彌補了 SQL Server 2000 中 ROWNUM 子句的缺陷,并且對記錄進行重新排序,使得只有 x 項目的“第 n 個”塊經過正確排序后返回。您可以指定基礎查詢,分頁程序將它分解為多個較小的頁面。只有適合某個頁面的記錄被返回。正如您看到的那樣,除了查詢命令以外,上述查詢需要處理排序字段。這就是為什么另外添加了 SortField 屬性。此代碼的唯一缺陷是默認情況為升序排序。通過使 ASC/DESC 關鍵字參數化,可以使此代碼真的非常完美:

private void FetchPageData()
{
// 需要經過驗證的頁面索引來獲取數據。
// 還需要實際的頁數來驗證頁面索引。
AdjustSelectCommand(false);
VirtualRecordCount countInfo = CalculateVirtualRecordCount();
TotalPages = countInfo.PageCount;

// 驗證頁碼(確保 CurrentPageIndex 有效或為“-1”)
ValidatePageIndex();
if (CurrentPageIndex == -1)
return;

// 準備并運行命令
SqlCommand cmd = PrepareCommand(countInfo);
if (cmd == null)
return;
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
DataTable data = new DataTable();
adapter.Fill(data);

// 配置分頁的數據源組件
if (_dataSource == null)
_dataSource = new PagedDataSource();
_dataSource.AllowCustomPaging = true;
_dataSource.AllowPaging = true;
_dataSource.CurrentPageIndex = 0;
_dataSource.PageSize = ItemsPerPage;
_dataSource.VirtualCount = countInfo.RecordCount;
_dataSource.DataSource = data.DefaultView;
}

在 NonCached 模式中,PagedDataSource 對象并不提供整個數據源,因此不能計算出要進行分頁的總頁數。進而必須對 AllowCustomPaging 屬性進行標記,并提供數據源中的實際記錄數量。通常,實際數量是使用 SELECT COUNT(*) 查詢進行檢索的。此模型與 DataGrid 的自定義分頁幾乎相同。此外,PagedDataSource 對象中選擇的當前頁面索引通常為 0,因為實際上已經存儲了一頁記錄。

SqlPager 控件的實現方法就介紹到這里,下面我們介紹一下它的使用方法。

使用 SqlPager 控件

假設存在一個包含 ListBox 控件的示例頁面。要使用分頁程序,請確保 .aspx 頁面正確地注冊了該控件的程序集。

<%@ Register TagPrefix="expo" Namespace="DevCenter" Assembly="SqlPager" %>

控件的標記取決于實際設置的屬性。以下標記是一個合理的示例:

<asp:listbox runat="server" id="ListBox1" Width="300px" Height="168px"
DataTextField="companyname" />
<br>
<expo:SqlPager runat="server" id="SqlPager1" Width="300px"
ControlToPaginate="ListBox1"
SelectCommand="SELECT customerid, companyname FROM customers"
ConnectionString="SERVER=localhost;DATABASE=northwind;UID=..."
SortKeyField="companyname" />
<br>
<asp:button runat="server" id="LoadFirst1" Text="加載第一頁" />

除了分頁程序以外,頁面還包含一個列表框和一個按鈕。列表框將顯示每個頁面的內容;按鈕只用于首次填充列表框。該按鈕具有一個單擊事件處理程序,定義如下。

private void LoadFirst1_Click(object sender, EventArgs e) {
SqlPager1.CurrentPageIndex = 0;
SqlPager1.DataBind();
}


圖 2 顯示操作中的頁面。


圖 2:與 ListBox 控件協同工作的 SqlPager 控件。

使用 DataList 控件可以生成一個更有意思的示例。目標是使用分頁程序瀏覽每個 Northwind 職員的個人記錄。該 DataList 如以下列表所示。

<asp:datalist runat="server" id="DataList1" Width="300px"
Font-Names="Verdana" Font-Size="8pt">
<ItemTemplate>
<table bgcolor="#f0f0f0" style="font-family:verdana;font-size:8pt;">
<tr><td valign="top">
<b><%# DataBinder.Eval(Container.DataItem, "LastName") + ", " +
DataBinder.Eval(Container.DataItem, "firstname") %></b>
</td></tr>

<tr><td>
<span style="color:blue;"><i>
<%# DataBinder.Eval(Container.DataItem, "Title")%></i></span>
<p><img style="float:right;" src='image.aspx?id=<%# DataBinder.Eval(Container.DataItem, "employeeid")%>' />
<%# DataBinder.Eval(Container.DataItem, "Notes") %>
</td></tr>
</table>
</ItemTemplate>
</asp:datalist>

表格的第一行顯示職員的姓名和職務,然后是相片,相片周圍是注釋。相片是使用特定的 .aspx 頁面檢索的,返回從數據庫中獲取的 JPEG 數據。

分頁程序可以放置在頁面中的任何位置。本例中將它放置在合作者 DataList 控件上方并緊挨著合作者控件。


圖 3:SqlPager 對 DataList 控件進行分頁

將 SqlPager 控件與 DataGrid 控件一起使用有意義嗎?這要視情況而定。DataGrid 已經與一個基于本文中使用的 PagedDataSource 對象的嵌入式分頁引擎一起工作。因此,如果您需要對以表格格式顯示的單個記錄集合進行分頁時,就無需使用 SqlPager。但是,對于重要/復雜的方案,將這兩個控件一起使用并不是一種牽強的想法。例如,如果您要向前一個屏幕快照添加一個 DataGrid 來顯示由職員管理的訂單,則可以在同一格頁面上放置兩個相關的分頁引擎,一個要對職員進行分頁,另一個用于滾動相關訂單。

小結

不管您要生成哪種類型的應用程序(Web 應用程序、Microsoft? Windows? 應用程序或 Web 服務),您都很難下載和緩存要顯示的整個數據源。有時,測試環境會使您相信這種解決方案真是太棒了,非常可取。但是測試環境也會誤導。數據源的大小非常關鍵,應用程序的規模越大,數據源的大小越關鍵。

在 ASP.NET 中,只有 DataGrid 控件具有內置的分頁功能。但是,分頁引擎是由相當樣板化的代碼實現的,只要進行少量的處理,就可以進行推廣并用于多個不同的控件。本文介紹的 SqlPager 控件就實現了此目標。它關注下載數據,將數據分成多個頁面并通過合作者控件顯示。該控件可以檢索并緩存整個數據集,或者僅在 SQL Server 中查詢要在選定的頁面中顯示的幾個記錄。我說的是 SQL Server,這是另一個重點。SqlPager 只能用于 SQL Server,不能用于通過 OLE DB 或 ODBC 來檢索數據。也不能使用它訪問 Oracle 或 DB2 存檔。

要生成真正的通用 SQL 分頁程序組件,應該形成數據訪問層,并生成一種能夠使用相應的數據提供程序創建連接、命令和適配器的工廠類。此外,要注意為各種 SQL 源設置一個分頁引擎是最糟糕的做法。這里介紹的方法只適用于 SQL Server 7.0 和更高版本。TOP 子句在不同的環境下具有不同的特性。使用服務器游標和臨時表格,它可適用于較大范圍的 DBMS 系統。但那將使代碼更為復雜。
作者:http://www.zhujiangroad.com
來源:http://www.zhujiangroad.com
北斗有巢氏 有巢氏北斗