在DataGrid的web版控件中提供了自动分页的功能,但是我从来没用过它,因为它实现的分页只是一种假相。我们为什么需要分页?那是因为符合条件的记录可能很多,如果一次读取所有的记录,不仅延长获取数据的时间,而且也极度浪费内存。而分页的存在的主要目的正是为了解决这两个问题(当然,也不排除为了UI美观的需要而使用分页的)。而web版的DataGrid是怎样实现分页的了?它并没有打算解决上述两个问题,而还是一次读取所有的数据,然后以分页的样子表现出来。这是对效率和内存的极大损害!
于是我自己写了一个分页管理器,关于它的描述和实现如下所示:

**/**///// <summary>
/// IDataPaginationManager 用于实现数据查询的分页操作。
/// 当表中的数据记录很多时,用Apdater一次读出所有的数据即耗费时间又浪费内存,这时就要用到分页了。
/// DataPaginationManager每次从数据库中读取指定的一页,并且把历史页缓存在Stack中,这样,如果再次访问历史页,
/// 就不用再访问数据库了,直接从Stack中取出即可。
///
/// 作者:朱伟 sky.zhuwei@163.com
/// </summary>
public interface IDataPaginationManager

{
//complexIDName 如"ID"或"sta.ID"(用于复合查询)
//selectStr 中不允许包括Group By 和 Order By 等字段
//void Initialize(IDBAccesser accesser ,string selectStr ,string complexID_Name) ;


int ItemCount
{get ;}

int PageCount
{get ;}

int CurrentPageIndex
{get ;}
DataTable StartPage() ;
DataTable NextPage() ;
DataTable PrePage() ;

DataTable CurrentPage
{get ;}
DataTable GetPage(int index) ; //只能随机访问曾经读取过的页

event PageChanged CurrentPageIndexChanged ;
}


/**//**//**//// <summary>
/// DataPagination 是IDataPagination的默认实现。遵守SkyDataAccess协议--有一个表示唯一索引的字段"ID"
/// </summary>
public class DataPaginationManager : IDataPaginationManager

{
private PaginationParas curParas = null ;
private IADOBase adoBase = null ;
private int pageCount = 0 ;
private int itemCount = 0 ;

private DataTable currentPage = null ;
private int curPageIndex = 0 ;

//用Stack来存储历史页,以实现前一页操作
private Stack statusStackForward = new Stack() ;
private Stack statusStackBackWard = new Stack() ;
private bool preForward = true ;//上一次是正向?

public event PageChanged CurrentPageIndexChanged ;


IDataPagination 成员IDataPagination 成员#region IDataPagination 成员
public DataPaginationManager(IDBAccesser accesser ,string selectStr ,string complexID_Name ,int page_size)

{
this.curParas = new PaginationParas() ;
this.curParas.ComplexIDName = complexID_Name ;
this.curParas.PageSize = page_size ;
this.curParas.SelectString = this.CheckSelectString(selectStr) ;
this.curParas.ConnectString = accesser.ConnectString ;
this.curParas.DbType = accesser.DataBaseType ;
this.curParas.DBTableName = accesser.DbTableName ;

this.InitializeAdoBase(accesser.DataBaseType ,accesser.ConnectString) ;
this.pageCount = this.GetPageCount() ;
}

public DataPaginationManager(PaginationParas paras)

{
this.curParas = paras ;
this.InitializeAdoBase(this.curParas.DbType ,this.curParas.ConnectString) ;
this.pageCount = this.GetPageCount() ;
}


privateprivate#region private
private void InitializeAdoBase(DataBaseType dbType ,string connStr)

{
switch(dbType)

{
case DataBaseType.SqlServer :

{
this.adoBase = new SqlADOBase(connStr) ;
break ;
}
case DataBaseType.Ole :

{
this.adoBase = new OleADOBase(connStr) ;
break ;
}
default:

{
throw new Exception("The target DataBaseType is not implemented !") ;
}
}
}

private string CheckSelectString(string selectStr)

{
if((selectStr == null) || (selectStr == ""))

{
throw new Exception("SelectStr is invalid !") ;
}

string str = selectStr.ToLower() ;
if((str.IndexOf("order by") != -1) || (str.IndexOf("group by") != -1))

{
throw new Exception("SelectStr Can't contain 'order by' or 'group by' !") ;
}

selectStr = selectStr.ToLower() ;
string ss = string.Format("select top {0} " ,this.curParas.PageSize) ;
return selectStr.Replace("select" ,ss );
}

private string ConstructSelectString(bool first ,bool forward ,PageStatus curSta)

{
if(first)

{
return this.curParas.SelectString ;
}


string comp = " >= " ;
string curIDValue = curSta.preIDValueHead ;
if(forward)

{
comp = " > " ;
curIDValue = curSta.curIDValueEnd ;
}

if(-1 == this.curParas.SelectString.IndexOf("where"))

{
return this.curParas.SelectString + string.Format(" where {0} {1} '{2}'" ,this.curParas.ComplexIDName ,comp ,curIDValue) ;
}
return this.curParas.SelectString + string.Format(" and {0} {1} '{2}'" ,this.curParas.ComplexIDName ,comp ,curIDValue) ;
}

private int GetPageCount()

{
string str = null ;
int index = this.curParas.SelectString.IndexOf("where") ;
if(-1 == index)

{
str = string.Format("Select Count(*) from {0}" ,this.curParas.DBTableName) ;
}
else

{
string whereStr = this.curParas.SelectString.Substring(index) ;
str = string.Format("Select Count(*) from {0} {1}" ,this.curParas.DBTableName ,whereStr) ;
}

DataSet ds = this.adoBase.DoQuery(str) ;
if(ds.Tables[0].Rows.Count != 0)

{
int num = int.Parse(ds.Tables[0].Rows[0][0].ToString()) ;
this.itemCount = num ;
int pageCount = num/this.curParas.PageSize ;
if(num%this.curParas.PageSize > 0)

{
pageCount += 1 ;
}

return pageCount ;
}

this.itemCount = 0 ;
return 0 ;
}
#endregion


PageCount ,CurrentPageIndex ,CurrentPagePageCount ,CurrentPageIndex ,CurrentPage#region PageCount ,CurrentPageIndex ,CurrentPage
public int PageCount

{
get

{
return this.pageCount ;
}
}

public int ItemCount

{
get

{
return this.itemCount ;
}
}

public int CurrentPageIndex

{
get

{
return this.curPageIndex ;
}
}

public DataTable CurrentPage

{
get

{
return this.currentPage ;
}
}
#endregion


StartPageStartPage#region StartPage
public DataTable StartPage()

{
if(this.pageCount == 0)

{
return null ;
}

this.statusStackBackWard.Clear() ;
this.statusStackForward.Clear() ;

string select = this.ConstructSelectString(true ,true ,null) ;
DataSet ds = this.adoBase.DoQuery(select) ;

PageStatus sta = new PageStatus() ;
sta.curIDValueEnd = ds.Tables[0].Rows[ds.Tables[0].Rows.Count-1]["ID"].ToString() ;
sta.preIDValueHead = ds.Tables[0].Rows[0]["ID"].ToString() ;
sta.curTable = ds.Tables[0] ;
this.statusStackForward.Push(sta) ;

this.curPageIndex = 0 ;
this.currentPage = sta.curTable ;
this.ActivePageIndexChanged(this.curPageIndex) ;

return this.currentPage ;
}
#endregion


NextPageNextPage#region NextPage
public DataTable NextPage()

{
if(this.curPageIndex >= this.pageCount-1)

{
return null ;
}

if(this.statusStackBackWard.Count >0)

{
PageStatus staRes = (PageStatus)this.statusStackBackWard.Pop() ;
this.statusStackForward.Push(staRes) ;
if(! this.preForward)

{
if(this.statusStackBackWard.Count > 0)

{
staRes = (PageStatus)this.statusStackBackWard.Pop() ;
this.statusStackForward.Push(staRes) ;
}
}
return this.ReturnCurrentPage(staRes.curTable ,true) ;
}

PageStatus curSta = (PageStatus)this.statusStackForward.Peek() ;
string select = this.ConstructSelectString(false ,true ,curSta) ;
DataSet ds = this.adoBase.DoQuery(select) ;

PageStatus sta = new PageStatus() ;
sta.curIDValueEnd = ds.Tables[0].Rows[ds.Tables[0].Rows.Count-1]["ID"].ToString() ;
sta.preIDValueHead = curSta.curTable.Rows[0]["ID"].ToString() ;
sta.curTable = ds.Tables[0] ;
this.statusStackForward.Push(sta) ;

return this.ReturnCurrentPage(sta.curTable ,true) ;
}
#endregion


PrePagePrePage#region PrePage
public DataTable PrePage()

{
if(this.curPageIndex < 1)

{
return null ;
}

PageStatus oldSta = (PageStatus)this.statusStackForward.Pop() ;
this.statusStackBackWard.Push(oldSta) ;

if(this.preForward)

{
if(this.statusStackForward.Count > 0)

{
oldSta = (PageStatus)this.statusStackForward.Pop() ;
this.statusStackBackWard.Push(oldSta) ;
}
}

return this.ReturnCurrentPage(oldSta.curTable ,false) ;
}
#endregion


ReturnCurrentPageReturnCurrentPage#region ReturnCurrentPage
private DataTable ReturnCurrentPage(DataTable curPage ,bool foward)

{
if(curPage == null)

{
return null ;
}

if(foward)

{
++ this.curPageIndex ;
}
else

{
-- this.curPageIndex ;
}

this.preForward = foward ;
this.currentPage = curPage ;
this.ActivePageIndexChanged(this.curPageIndex) ;

return this.currentPage ;
}
#endregion


GetPageGetPage#region GetPage
//如果历史记录中有对应的page,则返回它,否则返回null
public DataTable GetPage(int index)

{
if(index > (this.statusStackBackWard.Count + this.statusStackForward.Count -1) || index < 0)

{
return null ;
}

int distance = index - this.curPageIndex ;

if(distance == 0)

{
return this.currentPage ;
}
else if(distance > 0)

{
for(int i=0 ;i<distance ;i++)

{
this.NextPage() ;
}

return this.currentPage ;
}
else

{
for(int i=distance ;i<0 ;i++)

{
this.PrePage() ;
}

return this.currentPage ;
}
}
#endregion


ActivePageIndexChangedActivePageIndexChanged#region ActivePageIndexChanged
private void ActivePageIndexChanged(int index)

{
if(this.CurrentPageIndexChanged != null)

{
this.CurrentPageIndexChanged(index) ;
}
}
#endregion

#endregion
}

public class PageStatus

{
public string curIDValueEnd = "" ; //本页最后一条记录ID
public string preIDValueHead = "" ; //上页第一条记录ID
public DataTable curTable = null ;
}

public class PaginationParas

{
public string ConnectString = null ;
public string SelectString = null ;
public string ComplexIDName = null ;
public string DBTableName = null ;
public int PageSize = 0 ;
public DataBaseType DbType = DataBaseType.SqlServer ;
}

public delegate void PageChanged(int pageIndex) ; 如果你使用XCodeFactory生成的数据层代码,可以像下面这样使用分页管理器:
string selectStr = "Select ID ,Title , UploadUserName ,UploadTime ,Description ,IsCasePic ,CaseID from BinaryInformationDetail " ;
this.curPageMgr = DataEntrance.GetPaginationMgr(typeof(BinaryInformationDetail) ,selectStr ,"ID" ,5) ;
DataTable dtStart = this.curPageMgr.StartPage() ;
this.DataList1.DataSource = dtPic ;
this.DataList1.DataBind() ; 否则,你需要通过DataPaginationManager的第二个构造函数来使用分页管理器。要提出的是,DataPaginationManager不能随机跳转到未访问过的页面(紧邻的下一页除外),这是由我们的实现方式(一次仅仅读取一页)决定的,我并不觉得随机跳转到任意页面是一种很必要的操作!