asp.net 使用ObjectDataSource控制項在ASP.NET中實現Ajax真分頁

來源:互聯網
上載者:User

ListView控制項本身並沒有分頁功能,不過藉助於ASP.NET中新增加的DataPager控制項,我們可以非常方便地對ListView中的資料設定分頁,這幾乎不需要開發人員寫一行代碼,將ListView控制項放到頁面上,設定好布局和DataSource,然後再添加一個DataPager控制項,將它的PagedControlID屬性設定成ListView的ID,PageSize中設定每頁要顯示的資料條數,然後在Fields中設定好分頁的樣式(當然你完全可以不用去管樣式,ASP.NET會根據內建的樣式來定義分頁UI),運行Web程式,你就會看到一個支援分頁的ListView頁面,整個過程非常簡單。在ASP.NET 3.5中,微軟將資料繫結控制項和分頁控制項分離開來了,這樣使用者就可以在頁面的任何地方設定分頁,並根據自己的需要定義各種不同的分頁樣式,同時,分頁控制項可以控制任意一個資料繫結控制項,它們之間通過PagedControlID屬性來指定,基本上對於分頁操作,開發人員可以隨心所欲了。如何使用ListView控制項和DataPager控制項不是本文的重點,感興趣的讀者可以去查一下微軟的MSDN,我想它應該比我講得要詳細得多。

先說說資料分頁的原理。我們在開發資料繫結頁面的Web應用程式時,常常會遇到資料量比較多的情況,為了防止頁面變得過大載入資料慢的問題,大家都會將一個頁面上要顯示的資料通過分頁來完成,使用者訪問頁面時通過分頁功能來查看不同頁面中的資料,這是一個非常好的解決辦法,而且幾乎所有的程式設計人員和開發人員都會不約而同樂此不彼地採用分頁的方式來顯示頁面上的資料,這沒有什麼問題! 問題在於分頁的方式。

一般情況下,最簡單的實現方法是一次性將所有頁面的資料讀到緩衝媒介中(這個媒介一般都是伺服器的記憶體),然後每次只顯示一頁的資料。這種方式實現起來很容易,而且ASP.NET之前幾乎所有的支援分頁的資料繫結控制項都是採用的這種方式,以至於很多ASP.NET的初學者都採用了這樣的方式來開發分頁資料繫結頁面,並且沒有覺察出任何問題。是的,程式開發中採用最簡單有效方法一般都是不會出現什麼問題的,況且微軟提供的標準控制項都是這樣做的,會有什麼問題呢?對於一些小的Web應用程式而言,這確實沒有什麼問題,因為它涉及到的資料量比較小,即使我們將所有的資料都讀到記憶體中,充其量也才幾兆,多一點十幾兆,幾十兆。如果這些資料都是純文字的話(一般而言我們儲存在資料庫中的資料都是文本資訊),幾十兆的資料已經是成千上萬條記錄了,現在的伺服器硬體條件都比較好,記憶體都在G級以上,處理這點資料根本不在話下。但是,如果資料庫中的一個表的記錄達到上億條,並且有些欄位儲存的是檔案資料(也就是位元據),這樣一次性將所有的資料讀到記憶體中就不是一個理想的做法了,這個時候就需要採用“真分頁”方式讀取資料。

大部分情況下,我們還是需要採用“真分頁”的方式來擷取資料的。給定每頁記錄的起始位置(或者頁面的索引),再給定一個每頁顯示的資料的條數和總記錄數,我們希望每次取到的只是當前頁面的資料。每次當使用者分頁時,根據這些條件從資料庫中取一部分資料繫結到頁面上,這樣可以大大減少伺服器的開銷,並且再大的資料量也不是問題。這種方式似乎是理想的,然而結合使用者的需求,我們會發覺即使採用“真分頁”方式對資料進行分頁擷取,也還是會遇到問題。試想,在當今Ajax橫行的Web世界裡,利用Ajax方式改善使用者體驗的網站層出不窮,如果你恰好有一個採用Ajax方式提供的分頁資料繫結頁面,那問題就會出現了。由於Ajax的使用者體驗效果是頁面的局部重新整理,在分頁資料繫結頁面中,使用者點擊分頁按鈕後頁面會以較快的速度更新分頁後的資料,這個體驗對使用者來說是相當不錯的,但是貪婪的使用者有可能會想試試頻繁地點擊分頁按鈕,甚至於瘋狂的使用者狂點分頁按鈕,這個時候你的應用程式由於需要非常頻繁地去資料庫中擷取分頁資料而來不及更新頁面上的資料而出現指令碼錯誤,最終給使用者的體驗就是頁面的分頁功能不正常,程式崩潰了。

採用“真分頁”和“假分頁”相結合的方式可以很有效得解決上面提到的這個問題。我將上面提到的第一種資料分頁方式稱之為“假分頁”,而將第二種資料分頁方式稱之為“真分頁”。這兩種分頁方式的結合,就是說一次性讀取n頁的資料到緩衝中,分頁時根據需要判斷是否從緩衝中直接擷取資料還是重新從資料庫中載入資料到緩衝裡。畢竟,從緩衝中載入資料效率要高得多。這樣,每次使用者點擊分頁按鈕時,只要資料存在於緩衝裡,就可以以非常快的速度載入資料,如果緩衝到期或者使用者要擷取的資料超出了緩衝,就從資料庫中重新載入新的n頁資料到緩衝中。當然,更新緩衝的過程你可以在Ajax中採用同步採用,以限制使用者在這個過程中的UI操作。

其實,分頁中所涉及到的細節問題是很多的,要想詳細敘述並講清楚這其中的所有問題,光靠本文的隻言片語恐怕是遠遠不夠的,這裡我只想向大家介紹一種在ASP.NET Ajax方式下進行真分頁編程的一種方法。為了比較簡單地使用Ajax方式,我在Visual Studio中直接使用了微軟提供的ajaxToolkit包裡的Ajax控制項,這些控制項一般來說都還是挺好用的,這裡不對這些控制項的使用做介紹了。

在寫這篇文章之前我也查閱了很多資料,其實大家在開發資料繫結頁面時一般都會採用“真分頁”的方式來對資料進行分頁處理,ASP.NET 3.5中的DataPager控制項是一個用於資料分頁的不錯的控制項,有的人把微軟提供的資料繫結控制項不支援資料“真分頁”的缺陷歸到它的頭上,我認為這是對它的冤枉。DataPager只負責分頁操作,它不管資料來源的事情,它更重要的工作在於如何處理分頁UI以及與使用者的互動。那麼,資料來源怎麼處理呢?資料繫結控制項如何知道我的資料來源被分成了多少頁,我當前取的是哪一頁的資料呢?

這些問題也一度讓我很苦惱,我嘗試過使用.NET中的PagedDataSource對象對資料進行分頁,但是後來發覺這個對象也是需要一次性將所有的資料讀到記憶體中才支援分頁的,說白了,它也是一個“假分頁”資料來源對象,和DataGrid、GridView沒有什麼不同。記得從.NET 2.0開始,微軟提供了一系列資料來源控制項(諸如SqlDataSource、XmlDataSource、LinqDataSource等等)來簡化對資料繫結控制項的資料來源指定,其實我覺得這些控制項除了簡化代碼外沒有什麼大的價值,有的時候還會破壞程式本身的結構,我一向都反對在頁面上直接使用這些控制項(當然,做一些示範用的程式使用這些控制項還是非常便捷的)。不過我在研究Ajax真分頁的過程中無意間看到了Visual Studio工具箱中的ObjectDataSource這個控制項,起初我只是認為它應該是那些DataSource控制項的基控制項,後來通過查資料才知道,這個控制項是所有的DataSource控制項中唯一支援“真分頁”操作的控制項,它可以通過設定幾個簡單的屬性就達到資料分頁的功能,下面我就向大家介紹一下如何使用這個控制項。

這個控制項的使用很簡單,我們只需要配置幾個屬性就可以了。

SelectMethod:指定用於擷取分頁資料的方法名。這個方法是一個自訂的.NET方法,你可以寫在頁面的CodeBehind代碼中,將方法的名字給ObjectDataSource控制項的SelectMethod即可。ObjectDataSource控制項會通過委託的方式自動去執行你所指定的這個方法。

TypeName:使用ObjectDataSource控制項的類的全名稱(包括名稱空間和類名)。這個屬性必須指定,ASP.NET會通過反射來載入相應的方法和對象。

DataObjectTypeName:資料來源對象的類型全名稱。ObjectDataSource控制項最大的亮點就在於它完全支援物件導向資料操作。在分層應用程式開發中,資料訪問層的代碼會將資料庫中的表抽象為class對象,將資料庫表中的欄位抽象為class對象中的屬性,DataObjectTypeName屬性所指定的就是這個資料庫類對象。

EnablePaging:如果你想開啟資料分頁功能,就需要將這個屬性的值設定為True。

MaximumRowsParameterName:這個屬性的值是一個參數名(只是一個參數名,而不是每頁顯示的資料條數),用於指示每頁要顯示資料的條數,ObjectDataSource控制項根據委託在之前SelectMethod屬性所指定的方法中傳遞該參數並執行其中的代碼。注意,這個參數的名稱必須與SelectMethod屬性所指定的方法中的參數名稱完全一樣。

StartRowIndexParameterName:這個屬性也是一個參數名,用於指示每頁起始記錄的索引。用法與MaximumRowsParameterName相同。

SelectCountMethod:這個屬性是一個方法的簽名,用來告訴ObjectDataSource控制項通過什麼方式得知資料來源中總記錄的條數。ObjectDataSource控制項同樣通過委託來執行這個方法,所以方法的簽名必須與屬性的值完全一樣。

然後我們在頁面上放置一個ListView控制項(或者其它任何一個資料繫結控制項),將它的DataSourceID屬性的值設定為ObjectDataSource的ID,然後添加一個DataPager控制項,將PagedControlID屬性的值設定為ListView的ID。

這是我所取的資料來源中的三張資料表的結構關係圖,其中主表是Shoutout表,Shoutout中的一條記錄對應著多個Image,它們通過BaseComment表進行關聯。在下面我會給出如果擷取Shoutout分頁資料的預存程序的代碼。

複製代碼 代碼如下:<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="AllShoutout.aspx.cs" Inherits="ShoutoutWallTest.AllShoutout" %>

<%@ Register Assembly="AjaxControlToolkit, Version=3.0.20820.415, Culture=neutral, PublicKeyToken=28f01b0e84b6d53e"
Namespace="AjaxControlToolkit" TagPrefix="ajaxToolkit" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
<link href="Css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<div id="shoutoutall">
<div style="float: left;">
<asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
<ContentTemplate>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="LoadShoutouts"
TypeName="ShoutoutWallTest.AllShoutout" DataObjectTypeName="Model.Shoutout" EnablePaging="True"
MaximumRowsParameterName="maxRows" StartRowIndexParameterName="startIndex" SelectCountMethod="CountAll"></asp:ObjectDataSource>
<div id="shoutoutalldescription">
<asp:ListView ID="lvShoutout" DataSourceID="ObjectDataSource1" runat="server" ItemPlaceholderID="layoutTableTemplate"
DataKeyNames="ID" OnItemDataBound="lvShoutout_ItemDataBound">
<LayoutTemplate>
<div id="layoutTableTemplate" runat="server">
</div>
</LayoutTemplate>
<ItemTemplate>
<div class="shoutoutallcontent">
<div class="shoutoutalltext">
<%#Eval("Description") %></div>
<div>
<!--Add images here-->
<asp:ListView ID="lvImages" runat="server" ItemPlaceholderID="layoutImages" DataSource='<%#Eval("Images")%>'>
<LayoutTemplate>
<div id="layoutImages" runat="server">
</div>
</LayoutTemplate>
<ItemTemplate>
<a href='Thumbnail.aspx?isthumbnail=false&basecommentid=<%#Eval("BaseCommentID").ToString() %>&imagetitle=<%#Eval("Title") %>'
target="_blank">
<img src='Thumbnail.aspx?basecommentid=<%#Eval("BaseCommentID").ToString() %>&imagetitle=<%#Eval("Title") %>'
alt="" class="shoutoutimg" /></a>
</ItemTemplate>
</asp:ListView>
</div>
<div class="shoutoutallposted">
Posted by:<%#Eval("PostedByName") %> <%#((DateTime)Eval("PostedDate")).ToShortDateString() %></div>
<div class="shoutoutallfooter">
<asp:Button ID="btEdit" CssClass="shoutoutalllistbutton" OnClick="btEdit_Click" runat="server"
Text="Edit" />
<asp:Button ID="btDel" CssClass="shoutoutalllistbutton" OnClick="btDel_Click" runat="server"
Text="Delete" OnClientClick="return confirm('Are you sure delete it?');" />
</div>
</div>
</ItemTemplate>
</asp:ListView>
</div>
<div class="shoutoutallfooter">
<asp:DataPager ID="DataPager1" runat="server" PagedControlID="lvShoutout" PageSize="25">
<Fields>
<asp:NextPreviousPagerField ButtonType="Image" FirstPageText="Go to first page" FirstPageImageUrl="./Images/ShoutOut_ViewAll_Left.gif" ShowFirstPageButton="true" ShowNextPageButton="false"
ShowPreviousPageButton="false" />
<asp:NumericPagerField NumericButtonCssClass="shoutoutallnumericpager" ButtonType="Button" PreviousPageImageUrl="./Images/ShoutOut_ViewAll_Left.gif" NextPreviousButtonCssClass="shoutoutallnextprepager" NextPageText=">>" PreviousPageText="<<" CurrentPageLabelCssClass="shoutoutallcurrentpager" ButtonCount="5" />
<asp:NextPreviousPagerField ButtonType="Image" LastPageText="Go to last page" LastPageImageUrl="./Images/ShoutOut_ViewAll_Right.gif" ShowLastPageButton="true" ShowNextPageButton="false"
ShowPreviousPageButton="false" />
</Fields>
</asp:DataPager>
</div>
</ContentTemplate>
</asp:UpdatePanel>
</div>
</div>
</form>
</body>
</html>

我把資料繫結控制項和分頁控制項都放在UploadPanel控制項中,這樣頁面就會在不重新整理的情況下執行資料繫結和分頁操作。代碼中使用了一個嵌套的ListView,原因是一條Shoutout記錄會對應多條image記錄,結合資料層的資料實體類,Shoutout class中會有一個類似於List<Image> Images的屬性,所以我直接將這個屬性作為了子ListView控制項的資料來源,它主要用於顯示每條Shoutout記錄中的縮圖。至於如何在頁面中顯示縮圖不是本文的重點,這裡不做介紹了。代碼中我們已經給ObjectDataSource控制項指定了用於進行資料分頁的參數名或方法簽名,下面我們需要實現這些方法。

只有兩個方法,LoadShoutouts()方法用於擷取資料對象Shoutout的集合,也就是List<Shoutout>類型的傳回值,事實上,該方法只需要執行資料庫中用於分頁的預存程序即可,這個預存程序可以同時返回資料集合和總記錄條數。下面我會給出這個預存程序。CountAll()方法僅僅只返回總的記錄條數。 複製代碼 代碼如下:using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Collections.Generic;
using Model;
using BLL;

namespace ShoutoutWallTest
{
public partial class AllShoutout : System.Web.UI.Page
{
public static List<Shoutout> list = null;
private static int ItemCount = 0;

protected void Page_Load(object sender, EventArgs e)
{
}

public List<Shoutout> LoadShoutouts(int startIndex, int maxRows)
{
int itemCount;
int pageIndex = 1;
if (startIndex > 0)
{
pageIndex = (startIndex) / 25 + 1;
}
list = ShoutoutBLL.GetShoutouts(13, pageIndex, maxRows, true, out itemCount);
ItemCount = itemCount;
return list;
}

public int CountAll()
{
return ItemCount;
}

/// <summary>
/// Refresh data after updating and deleting.
/// </summary>
private void RefreshData()
{
lvShoutout.DataSourceID = ObjectDataSource1.ID;
}
}
}

My Code中要求每頁顯示25條資料,ShoutoutBLL.GetShoutouts()方法有5個參數,第一個參數用於指定檢索資料的條件,這個是程式中的特例,讀者可以不用關心;第二個參數是頁面的索引,規定從1開始,我在方法中從startIndex轉換成了pageIndex;第三個參數是每頁顯示的資料條數;第四個參數是out類型的,返回記錄總行數,這個方法主要是為了對應執行資料庫的預存程序,具體代碼在BLL命名空間中,屬於商務邏輯層的代碼,這裡就不再具體給出了,Model命名空間中的代碼主要用來返回資料庫實體物件,如Shoutout和Image對象。RefreshData()方法中重新給ListView控制項的DataSourceID屬性指定了值,這樣可以重新綁定資料從而達到重新整理資料的效果。
是程式運行後的部分,可以看出分頁UI已經顯示出來了,而且對於分頁操作,我沒有寫一行代碼,這個完全由DataPager自己來控制。由於ListView和DataPager控制項都位於UpdatePanel控制項中,當使用者點擊分頁按鈕時頁面只是更新了ListView中的資料而沒有重新整理整個頁面,並且資料是逐頁從資料庫中得到的,這樣便實現了在Ajax方式下的“真分頁”操作。核心控制項是ObjectDataSource。下面是我用於獲得分頁資料的預存程序,讀者可以借鑒一下,這個預存程序採用了暫存資料表的方式進行資料分頁。 複製代碼 代碼如下:set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go

ALTER PROCEDURE [dbo].[GetShoutOuts]
-- Add the parameters for the stored procedure here
(
@LocationID INT,
@PageIndex INT, -- start from 1.
@PageSize INT,
@showimages BIT,
@ItemCount INT Output
)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;

declare @beginRowNumber bigint,@endRowNumber bigint
set @beginRowNumber = (@PageIndex - 1)*@PageSize+1;
set @endRowNumber = @PageIndex*@PageSize;

WITH TempPagingRecord AS
(
SELECT ROW_NUMBER() OVER(ORDER BY PostedDate DESC) AS RecordNumber,SO.ID AS ShoutOutID, BC.[Description], BC.PostedByName, BC.PostedDate,null as ImageTitle,null as ImageBlob,null as Type,BC.ID AS BaseCommentID,BC.DisplayUserName,
BC.IsVisible, SO.NotifyToShoutOutUser, SO.ShoutOutToUserAlias
FROM dbo.ShoutOut as SO
JOIN dbo.BaseComment as BC ON BC.ID = SO.BaseCommentID
WHERE SO.LocationID =@LocationID
AND BC.IsVisible = 1
)
SELECT RecordNumber,
ShoutOutID,
Description,
PostedByName,
PostedDate,
ImageTitle,
ImageBlob,
BaseCommentID,
DisplayUserName,
IsVisible,
NotifyToShoutOutUser,
ShoutOutToUserAlias
INTO #tempTable
FROM TempPagingRecord
Where RecordNumber between @beginRowNumber and @endRowNumber

-- Insert statements for procedure here
IF(@showimages = 1)
begin
select RecordNumber,
ShoutOutID,
Description,
PostedByName,
PostedDate,
IM.ImageTitle,
IM.ImageBlob,
IM.Type,
T.BaseCommentID,
DisplayUserName,
IsVisible,
NotifyToShoutOutUser,
ShoutOutToUserAlias from #tempTable T
Left join dbo.Image IM ON IM.BaseCommentID = T.BaseCommentID
order by PostedDate DESC
end
ELSE
begin
SELECT * FROM #tempTable
order by PostedDate DESC
end

SELECT @ItemCount = Count(*)
FROM Shoutout as SO
JOIN dbo.BaseComment as BC ON BC.ID = SO.BaseCommentID
WHERE SO.LocationID =@LocationID
AND BC.IsVisible = 1
END

個人覺得ObjectDataSource控制項是一個比較智能化的控制項,它通過函數委託的方式自動執行使用者提供的分頁代碼來完成資料庫的“真分頁”操作,省去了開發過程中的很多麻煩,還是很有必要去認真研究一下的。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.