[移文]DataList 分頁(運用 T-SQL Paging)

[移文]DataList 分頁(運用 T-SQL Paging)

今天在某論壇看到一個問題:如何只使用 SqlDataSource 做到 DataList 分頁的功能?提問者很明確的提到,他的 DataList 已經給定一個 DataSourceID,能否在不使用 PagedDataSource 的情況下,也可以做到分頁效果?

這個問題的答案是肯定的!而且其實不難,主要的重點就是從資料來源下手,畢竟資料繫結控制項 (Data-Bound Controls) 處理的就是資料來源裡的資料,也就是說,你給它甚麼資料(集),它就呈現甚麼,舉例來說,你希望每頁呈現 10 筆資料,那就真的查詢 10 筆資料出來。當然你隨便搜尋一下 "DataList 分頁" 就可以查到很多詳細範例,但絕大多數都是用 PagedDataSource 做的,本文的目的是利用 T-SQL 分頁(),以更有效率的方式來解決分頁問題。

廢話不多說,直接來寫看看,配置第一個下拉選單指定每頁顯示資料筆數,第二個下拉選單則是顯示欄位數,然後是 DataList、上一頁、下一頁巡覽按鈕、頁次提示文字等。參考底下原始檔:

    分頁筆數:<asp:DropDownList ID="DropDownList1" runat="server" AutoPostBack="True" OnSelectedIndexChanged="DropDownList1_SelectedIndexChanged">
        <asp:ListItem>5</asp:ListItem>
        <asp:ListItem>10</asp:ListItem>
        <asp:ListItem>20</asp:ListItem>
        <asp:ListItem>50</asp:ListItem>
        <asp:ListItem>100</asp:ListItem>
    </asp:DropDownList>
    &nbsp; 資料欄數:<asp:DropDownList ID="DropDownList2" runat="server" AutoPostBack="True"
        OnSelectedIndexChanged="DropDownList2_SelectedIndexChanged">
        <asp:ListItem>1</asp:ListItem>
        <asp:ListItem Selected="True">5</asp:ListItem>
        <asp:ListItem>10</asp:ListItem>
        <asp:ListItem>20</asp:ListItem>
    </asp:DropDownList>
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>">
    </asp:SqlDataSource>
    <asp:DataList ID="DataList1" runat="server" DataSourceID="SqlDataSource1" BorderColor="Gray"
        BorderStyle="Solid" BorderWidth="1px" OnItemDataBound="DataList1_ItemDataBound"
        RepeatColumns="10">
        <ItemTemplate>
            <div style="border: solid 1px #0000ff;">
                No.:
                <asp:Label ID="NoLabel" runat="server" Text='<%# Eval("NO") %>' />
                <br />
                CategoryName:
                <asp:Label ID="CategoryNameLabel" runat="server" Text='<%# Eval("CategoryName") %>' />
                <br />
                ProductName:
                <asp:Label ID="ProductNameLabel" runat="server" Text='<%# Eval("ProductName") %>' />
                <br />
                QuantityPerUnit:
                <asp:Label ID="QuantityPerUnitLabel" runat="server" Text='<%# Eval("QuantityPerUnit") %>'></asp:Label>
                <br />
                UnitsInStock:
                <asp:Label ID="UnitsInStockLabel" runat="server" Text='<%# Eval("UnitsInStock") %>'></asp:Label>
                <br />
                Discontinued:
                <asp:Label ID="DiscontinuedLabel" runat="server" Text='<%# Eval("Discontinued") %>' />
                <br />
                <br />
            </div>
        </ItemTemplate>
        <FooterTemplate>
            <asp:Button ID="btnPrevious" runat="server" AccessKey="P" Text="&lt; 上一頁" OnClick="btnPrevious_Click" />
            <asp:Button ID="btnNext" runat="server" AccessKey="N" Text="下一頁 &gt;" OnClick="btnNext_Click" />
            <asp:Literal ID="litIndicator" runat="server" EnableViewState="False"></asp:Literal>
        </FooterTemplate>
    </asp:DataList>
</div>


T-SQL 分頁的部分,用 Northwind 裡的 Products by Category 檢視表為例,要取得指定範圍的資料大概會用如下的查詢指令 (其中的參數 @start、@end 會在網頁程式碼動態指定):

	[NO], 
	CategoryName, 
	ProductName, 
	QuantityPerUnit, 
	UnitsInStock, 
	Discontinued
FROM (
	SELECT (ROW_NUMBER() OVER (ORDER BY CategoryName)) 'NO', * 
	FROM [Products by Category]
) 
AS [p] 
WHERE ([NO] BETWEEN @start AND @end);


程式碼需處理的地方,底下列出兩個關鍵:
  1. 上一頁、下一頁按鈕需巡覽至正確頁次,所以要算出查詢的起始與結束範圍,相關程式碼如下:
    
    /// 動態繫結 DataList 呈現資料 
    /// </summary>
    private void BindList()
    {
        // 目前頁次
        int currentPage = (Session["page"] == null) ? 1 :
            (String.IsNullOrEmpty(Session["page"].ToString())) ? 1 :
            Convert.ToInt32(Session["page"].ToString());
    
        // 透過下拉選單 1 指定每頁筆數
        int pageSize = Convert.ToInt32(this.DropDownList1.SelectedValue);
    
        // 透過下拉選單 2 指定顯示欄數
        this.DataList1.RepeatColumns = Convert.ToInt32(this.DropDownList2.SelectedValue);
    
        // 計算起始與結束範圍
        int startNo = 1 + ((currentPage - 1) * pageSize);
        int endNo = startNo + pageSize - 1;
    
        // 動態指定 SQL 查詢
        this.SqlDataSource1.SelectParameters.Clear();
        this.SqlDataSource1.SelectCommand = "SELECT * FROM (SELECT (ROW_NUMBER() OVER (ORDER BY CategoryName)) 'NO', * FROM [Products by Category]) AS [p] WHERE ([NO] BETWEEN @start AND @end) ";
        this.SqlDataSource1.SelectParameters.Add("@start", System.Data.DbType.Int32, startNo.ToString());
        this.SqlDataSource1.SelectParameters.Add("@end", System.Data.DbType.Int32, endNo.ToString());
    
        // 回寫 Session 紀錄目前頁次
        Session["page"] = currentPage;
    }
    
    protected void btnPrevious_Click(object sender, EventArgs e)
    {
        // 由目前頁次巡覽上一頁
        int currentPage = (Session["page"] == null) ? 1 :
            (String.IsNullOrEmpty(Session["page"].ToString())) ? 1 :
            Convert.ToInt32(Session["page"].ToString());
    
        Session["page"] = (currentPage == 1) ? currentPage : currentPage - 1;
    
        BindList();
    }
    
    protected void btnNext_Click(object sender, EventArgs e)
    {
        // 由目前頁次巡覽下一頁
        int currentPage = (Session["page"] == null) ? 1 :
            (String.IsNullOrEmpty(Session["page"].ToString())) ? 1 :
            Convert.ToInt32(Session["page"].ToString());
    
        Session["page"] = currentPage + 1;
    
        BindList();
    }
  2. 第一頁沒有上一頁功能,最後一頁沒有下一頁功能,所以要判斷其停用條件:
    
    {
        if (e.Item.ItemType == ListItemType.Footer)
        {
            int currentPage = Convert.ToInt32((Session["page"] == null) ?
                "1" : (String.IsNullOrEmpty(Session["page"].ToString())) ? "1" : Session["page"].ToString());
    
            int pageSize = Convert.ToInt32(this.DropDownList1.SelectedValue);
    
            // 判斷是否顯示上一頁、下一頁按鈕
            if (currentPage == 1)
            {
                ((Button)e.Item.FindControl("btnPrevious")).Enabled = false;
            }
    
            if (this.DataList1.Items.Count < pageSize)
            {
                ((Button)e.Item.FindControl("btnNext")).Enabled = false;
            }
    
            // 顯示頁次資訊
            ((Literal)e.Item.FindControl("litIndicator")).Text = "頁次:" + currentPage.ToString();
        }
    }

大致如此,需特別說明的是,本例利用 Session 存放目前頁次,若用其他方式存放,例如:隱藏欄位(或 ViewState)、QueryString...等,應該也是 OK 的,至於優缺點則不在本文討論範圍,或許大家可以回應一些意見,我很樂意與大家討論。最後附上內含 C#、VB.NET 版完整程式碼,有需要的人可以下載測試,若有寫得不好的地方,也希望能提供點建議給我,謝謝!

PagedDataList.rar


※ 探討 T-SQL 分頁幾篇不錯的文章


[SubText心得]ASP.NET TSQL 效能較佳分頁方法及友善 SEO 的分頁
用 SQL Server 2005 做分頁查詢
Paging of Large Resultsets in ASP.NET