|
先說說數(shù)據(jù)分頁的原理。我們在開發(fā)數(shù)據(jù)綁定頁面的Web應(yīng)用程序時,常常會遇到數(shù)據(jù)量比較多的情況,為了防止頁面變得過大加載數(shù)據(jù)慢的問題,大家都會將一個頁面上要顯示的數(shù)據(jù)通過分頁來完成,用戶訪問頁面時通過分頁功能來查看不同頁面中的數(shù)據(jù),這是一個非常好的解決辦法,而且?guī)缀跛械某绦蛟O(shè)計人員和開發(fā)人員都會不約而同樂此不彼地采用分頁的方式來顯示頁面上的數(shù)據(jù),這沒有什么問題! 問題在于分頁的方式。
一般情況下,最簡單的實現(xiàn)方法是一次性將所有頁面的數(shù)據(jù)讀到緩存媒介中(這個媒介一般都是服務(wù)器的內(nèi)存),然后每次只顯示一頁的數(shù)據(jù)。這種方式實現(xiàn)起來很容易,而且ASP.NET之前幾乎所有的支持分頁的數(shù)據(jù)綁定控件都是采用的這種方式,以至于很多ASP.NET的初學(xué)者都采用了這樣的方式來開發(fā)分頁數(shù)據(jù)綁定頁面,并且沒有覺察出任何問題。是的,程序開發(fā)中采用最簡單有效的方法一般都是不會出現(xiàn)什么問題的,況且微軟提供的標準控件都是這樣做的,會有什么問題呢?對于一些小的Web應(yīng)用程序而言,這確實沒有什么問題,因為它涉及到的數(shù)據(jù)量比較小,即使我們將所有的數(shù)據(jù)都讀到內(nèi)存中,充其量也才幾兆,多一點十幾兆,幾十兆。如果這些數(shù)據(jù)都是純文本的話(一般而言我們保存在數(shù)據(jù)庫中的數(shù)據(jù)都是文本信息),幾十兆的數(shù)據(jù)已經(jīng)是成千上萬條記錄了,現(xiàn)在的服務(wù)器硬件條件都比較好,內(nèi)存都在G級以上,處理這點數(shù)據(jù)根本不在話下。但是,如果數(shù)據(jù)庫中的一個表的記錄達到上億條,并且有些字段存儲的是文件數(shù)據(jù)(也就是二進制數(shù)據(jù)),這樣一次性將所有的數(shù)據(jù)讀到內(nèi)存中就不是一個理想的做法了,這個時候就需要采用“真分頁”方式讀取數(shù)據(jù)。
大部分情況下,我們還是需要采用“真分頁”的方式來獲取數(shù)據(jù)的。給定每頁記錄的起始位置(或者頁面的索引),再給定一個每頁顯示的數(shù)據(jù)的條數(shù)和總記錄數(shù),我們希望每次取到的只是當前頁面的數(shù)據(jù)。每次當用戶分頁時,根據(jù)這些條件從數(shù)據(jù)庫中取一部分數(shù)據(jù)綁定到頁面上,這樣可以大大減少服務(wù)器的開銷,并且再大的數(shù)據(jù)量也不是問題。這種方式似乎是理想的,然而結(jié)合用戶的需求,我們會發(fā)覺即使采用“真分頁”方式對數(shù)據(jù)進行分頁獲取,也還是會遇到問題。試想,在當今Ajax橫行的Web世界里,利用Ajax方式改善用戶體驗的站點層出不窮,如果你恰好有一個采用Ajax方式提供的分頁數(shù)據(jù)綁定頁面,那問題就會出現(xiàn)了。由于Ajax的用戶體驗效果是頁面的局部刷新,在分頁數(shù)據(jù)綁定頁面中,用戶點擊分頁按鈕后頁面會以較快的速度更新分頁后的數(shù)據(jù),這個體驗對用戶來說是相當不錯的,但是貪婪的用戶有可能會想試試頻繁地點擊分頁按鈕,甚至于瘋狂的用戶狂點分頁按鈕,這個時候你的應(yīng)用程序由于需要非常頻繁地去數(shù)據(jù)庫中獲取分頁數(shù)據(jù)而來不及更新頁面上的數(shù)據(jù)而出現(xiàn)腳本錯誤,最終給用戶的體驗就是頁面的分頁功能不正常,程序崩潰了。
采用“真分頁”和“假分頁”相結(jié)合的方式可以很有效得解決上面提到的這個問題。我將上面提到的第一種數(shù)據(jù)分頁方式稱之為“假分頁”,而將第二種數(shù)據(jù)分頁方式稱之為“真分頁”。這兩種分頁方式的結(jié)合,就是說一次性讀取n頁的數(shù)據(jù)到緩存中,分頁時根據(jù)需要判斷是否從緩存中直接獲取數(shù)據(jù)還是重新從數(shù)據(jù)庫中加載數(shù)據(jù)到緩存里。畢竟,從緩存中加載數(shù)據(jù)效率要高得多。這樣,每次用戶點擊分頁按鈕時,只要數(shù)據(jù)存在于緩存里,就可以以非常快的速度加載數(shù)據(jù),如果緩存過期或者用戶要獲取的數(shù)據(jù)超出了緩存,就從數(shù)據(jù)庫中重新加載新的n頁數(shù)據(jù)到緩存中。當然,更新緩存的過程你可以在Ajax中采用同步采用,以限制用戶在這個過程中的UI操作。
其實,分頁中所涉及到的細節(jié)問題是很多的,要想詳細敘述并講清楚這其中的所有問題,光靠本文的只言片語恐怕是遠遠不夠的,這里我只想向大家介紹一種在ASP.NET Ajax方式下進行真分頁編程的一種方法。為了比較簡單地使用Ajax方式,我在Visual Studio中直接使用了微軟提供的ajaxToolkit包里的Ajax控件,這些控件一般來說都還是挺好用的,這里不對這些控件的使用做介紹了。
在寫這篇文章之前我也查閱了很多資料,其實大家在開發(fā)數(shù)據(jù)綁定頁面時一般都會采用“真分頁”的方式來對數(shù)據(jù)進行分頁處理,ASP.NET 3.5中的DataPager控件是一個用于數(shù)據(jù)分頁的不錯的控件,有的人把微軟提供的數(shù)據(jù)綁定控件不支持數(shù)據(jù)“真分頁”的缺陷歸到它的頭上,我認為這是對它的冤枉。DataPager只負責分頁操作,它不管數(shù)據(jù)源的事情,它更重要的工作在于如何處理分頁UI以及與用戶的交互。那么,數(shù)據(jù)源怎么處理呢?數(shù)據(jù)綁定控件如何知道我的數(shù)據(jù)源被分成了多少頁,我當前取的是哪一頁的數(shù)據(jù)呢?
這些問題也一度讓我很苦惱,我嘗試過使用.NET中的PagedDataSource對象對數(shù)據(jù)進行分頁,但是后來發(fā)覺這個對象也是需要一次性將所有的數(shù)據(jù)讀到內(nèi)存中才支持分頁的,說白了,它也是一個“假分頁”數(shù)據(jù)源對象,和DataGrid、GridView沒有什么不同。記得從.NET 2.0開始,微軟提供了一系列數(shù)據(jù)源控件(諸如SqlDataSource、XmlDataSource、LinqDataSource等等)來簡化對數(shù)據(jù)綁定控件的數(shù)據(jù)源指定,其實我覺得這些控件除了簡化代碼外沒有什么大的價值,有的時候還會破壞程序本身的結(jié)構(gòu),我一向都反對在頁面上直接使用這些控件(當然,做一些演示用的程序使用這些控件還是非常便捷的)。不過我在研究Ajax真分頁的過程中無意間看到了Visual Studio工具箱中的ObjectDataSource這個控件,起初我只是認為它應(yīng)該是那些DataSource控件的基控件,后來通過查資料才知道,這個控件是所有的DataSource控件中唯一支持“真分頁”操作的控件,它可以通過設(shè)定幾個簡單的屬性就達到數(shù)據(jù)分頁的功能,下面我就向大家介紹一下如何使用這個控件。
這個控件的使用很簡單,我們只需要配置幾個屬性就可以了。
SelectMethod:指定用于獲取分頁數(shù)據(jù)的方法名。這個方法是一個自定義的.NET方法,你可以寫在頁面的CodeBehind代碼中,將方法的名字給ObjectDataSource控件的SelectMethod即可。ObjectDataSource控件會通過委托的方式自動去執(zhí)行你所指定的這個方法。
TypeName:使用ObjectDataSource控件的類的全名稱(包括名稱空間和類名)。這個屬性必須指定,ASP.NET會通過反射來加載相應(yīng)的方法和對象。
DataObjectTypeName:數(shù)據(jù)源對象的類型全名稱。ObjectDataSource控件最大的亮點就在于它完全支持面向?qū)ο髷?shù)據(jù)操作。在分層應(yīng)用程序開發(fā)中,數(shù)據(jù)訪問層的代碼會將數(shù)據(jù)庫中的表抽象為class對象,將數(shù)據(jù)庫表中的字段抽象為class對象中的屬性,DataObjectTypeName屬性所指定的就是這個數(shù)據(jù)庫類對象。
EnablePaging:如果你想開啟數(shù)據(jù)分頁功能,就需要將這個屬性的值設(shè)置為True。
MaximumRowsParameterName:這個屬性的值是一個參數(shù)名(只是一個參數(shù)名,而不是每頁顯示的數(shù)據(jù)條數(shù)),用于指示每頁要顯示數(shù)據(jù)的條數(shù),ObjectDataSource控件根據(jù)委托在之前SelectMethod屬性所指定的方法中傳遞該參數(shù)并執(zhí)行其中的代碼。注意,這個參數(shù)的名稱必須與SelectMethod屬性所指定的方法中的參數(shù)名稱完全一樣。
StartRowIndexParameterName:這個屬性也是一個參數(shù)名,用于指示每頁起始記錄的索引。用法與MaximumRowsParameterName相同。
SelectCountMethod:這個屬性是一個方法的簽名,用來告訴ObjectDataSource控件通過什么方式得知數(shù)據(jù)源中總記錄的條數(shù)。ObjectDataSource控件同樣通過委托來執(zhí)行這個方法,所以方法的簽名必須與屬性的值完全一樣。
然后我們在頁面上放置一個ListView控件(或者其它任何一個數(shù)據(jù)綁定控件),將它的DataSourceID屬性的值設(shè)置為ObjectDataSource的ID,然后添加一個DataPager控件,將PagedControlID屬性的值設(shè)置為ListView的ID。
這是我所取的數(shù)據(jù)源中的三張數(shù)據(jù)表的結(jié)構(gòu)關(guān)系圖,其中主表是Shoutout表,Shoutout中的一條記錄對應(yīng)著多個Image,它們通過BaseComment表進行關(guān)聯(lián)。在下面我會給出如果獲取Shoutout分頁數(shù)據(jù)的存儲過程的代碼。
復(fù)制代碼 代碼如下:
<%@ 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>
我把數(shù)據(jù)綁定控件和分頁控件都放在UploadPanel控件中,這樣頁面就會在不刷新的情況下執(zhí)行數(shù)據(jù)綁定和分頁操作。代碼中使用了一個嵌套的ListView,原因是一條Shoutout記錄會對應(yīng)多條image記錄,結(jié)合數(shù)據(jù)層的數(shù)據(jù)實體類,Shoutout class中會有一個類似于List<Image> Images的屬性,所以我直接將這個屬性作為了子ListView控件的數(shù)據(jù)源,它主要用于顯示每條Shoutout記錄中的縮略圖。至于如何在頁面中顯示縮略圖不是本文的重點,這里不做介紹了。代碼中我們已經(jīng)給ObjectDataSource控件指定了用于進行數(shù)據(jù)分頁的參數(shù)名或方法簽名,下面我們需要實現(xiàn)這些方法。
只有兩個方法,LoadShoutouts()方法用于獲取數(shù)據(jù)對象Shoutout的集合,也就是List<Shoutout>類型的返回值,事實上,該方法只需要執(zhí)行數(shù)據(jù)庫中用于分頁的存儲過程即可,這個存儲過程可以同時返回數(shù)據(jù)集合和總記錄條數(shù)。下面我會給出這個存儲過程。CountAll()方法僅僅只返回總的記錄條數(shù)。
復(fù)制代碼 代碼如下:
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;
}
}
}
我的代碼中要求每頁顯示25條數(shù)據(jù),ShoutoutBLL.GetShoutouts()方法有5個參數(shù),第一個參數(shù)用于指定檢索數(shù)據(jù)的條件,這個是程序中的特例,讀者可以不用關(guān)心;第二個參數(shù)是頁面的索引,規(guī)定從1開始,我在方法中從startIndex轉(zhuǎn)換成了pageIndex;第三個參數(shù)是每頁顯示的數(shù)據(jù)條數(shù);第四個參數(shù)是out類型的,返回記錄總行數(shù),這個方法主要是為了對應(yīng)執(zhí)行數(shù)據(jù)庫的存儲過程,具體代碼在BLL命名空間中,屬于業(yè)務(wù)邏輯層的代碼,這里就不再具體給出了,Model命名空間中的代碼主要用來返回數(shù)據(jù)庫實體對象,如Shoutout和Image對象。RefreshData()方法中重新給ListView控件的DataSourceID屬性指定了值,這樣可以重新綁定數(shù)據(jù)從而達到刷新數(shù)據(jù)的效果。 上圖是程序運行后的部分截圖,可以看出分頁UI已經(jīng)顯示出來了,而且對于分頁操作,我沒有寫一行代碼,這個完全由DataPager自己來控制。由于ListView和DataPager控件都位于UpdatePanel控件中,當用戶點擊分頁按鈕時頁面只是更新了ListView中的數(shù)據(jù)而沒有刷新整個頁面,并且數(shù)據(jù)是逐頁從數(shù)據(jù)庫中得到的,這樣便實現(xiàn)了在Ajax方式下的“真分頁”操作。核心控件是ObjectDataSource。下面是我用于獲得分頁數(shù)據(jù)的存儲過程,讀者可以借鑒一下,這個存儲過程采用了臨時表的方式進行數(shù)據(jù)分頁。
復(fù)制代碼 代碼如下:
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控件是一個比較智能化的控件,它通過函數(shù)委托的方式自動執(zhí)行用戶提供的分頁代碼來完成數(shù)據(jù)庫的“真分頁”操作,省去了開發(fā)過程中的很多麻煩,還是很有必要去認真研究一下的。
AspNet技術(shù):asp.net 使用ObjectDataSource控件在ASP.NET中實現(xiàn)Ajax真分頁,轉(zhuǎn)載需保留來源!
鄭重聲明:本文版權(quán)歸原作者所有,轉(zhuǎn)載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯(lián)系我們修改或刪除,多謝。