一步步构建半自动数据分页模块.docx
- 文档编号:23537004
- 上传时间:2023-05-18
- 格式:DOCX
- 页数:19
- 大小:102.33KB
一步步构建半自动数据分页模块.docx
《一步步构建半自动数据分页模块.docx》由会员分享,可在线阅读,更多相关《一步步构建半自动数据分页模块.docx(19页珍藏版)》请在冰豆网上搜索。
一步步构建半自动数据分页模块
一步步构建“半自动”数据分页模块
引言
GridView等数据控件提供的数据分页功能非常方便,甚至不需要编写代码就可以完成分页,但是它有一个缺陷,就是每次都会取出所有数据但仅显示一页数据,这在大数据量的情况下显然是不合适的。
除此以外,在Web站点的前台页面,我们仍会大量使用Repeater控件,这样避免不了进行手工数据分页。
通常,我都是在数据库中写成存储过程,但是这样有一个很大的缺陷:
没有代码重用,针对不同表或者不同视图都需要去编写存储过程。
本文将一步步构建一个进行数据分页的代码模块,来尝试解决这个问题。
分页思路
这个程序的思路不过是将采用存储过程进行分页的思路转移到了代码中:
根据表名、主键、Where子句、Orderby子句、当前页码、分页大小等参数,来动态生成一个SQL语句,然后将这个SQL语句提交给数据库去执行,最后获得返回结果。
而这个SQL语句的分页算法分为下面几个步骤,其中花括号中的内容代表变量:
1.声明一个表变量,它含有两个字段,一个名为recordNo,并将它的类型设置为int类型的自增量,它用于标识记录的顺序号;另一个字段的名称和类型与要进行分页的表的主键相同。
2.根据where子句和order子句获取要进行分页的表中的所有记录的主键,并将主键插入到声明的表变量中,代码类似于:
InsertInto@temp({key})Select{key}From{table}{where}{order},其中@temp为表变量。
通过这样便会自动为表中的所有记录添加一个序号,因为recordNo是一个自增量。
3.使用Top语句,根据分页大小和起始索引获得@temp中的主键列。
语句例如Selecttop{pageSize}{key}From@tempWhererecordNo>{startIndex},其中startIndex是起始的顺序号,可以由分页大小和当前页码算得。
4.根据上一步获得的主键获得所需的数据。
语句例如Select{columns}From{table}where{key}in{上一步获得的主键}{order}。
实现过程1-辅助类
从上面我们可以看出有非常多的变量需要定义,而对于数据库来说,这些语句又受一定格式的制约,比如order子句就只能是类似这样的字符串“orderbyColunmA,ColumnBdesc”;而对于主键来说,@temp表变量和想要进行分页的表中的主键又必须一致。
所以我们免不了需要定义一些辅助类型来约束它们。
开始之前先创建一个空解决方案,然后在其下加入一个Web站点项目WebSite。
SortDirection枚举
我们首先在App_Code中添加文件SortDirection.cs,并在其中创建一个SortDirection枚举,它可以代表排序的顺序,升序或者降序:
public enum SortDirection {
Asc=0,
Desc
}
OrderColumn类
这个类定义了用于排序的表的字段:
//进行排序的字段
public class OrderColumn {
private string name;
private SortDirection direction;
public OrderColumn(string name,SortDirectiondirection){
this.name=name;
this.direction=direction;
}
public OrderColumn(string name):
this(name,SortDirection.Asc){}
public string Name{
get{ return name;}
}
public SortDirectionDirection{
get{ return direction;}
}
//覆盖基类的ToString()方法
public override string ToString(){
return String.Format("{0}{1}",name,direction);
}
}
注意到上面覆盖了基类的ToString()方法,因为Order子句是一个字符串的形式。
同时我们可以考虑到这样两个问题:
∙排序经常是多个字段的排序,可以预见到进行分页的方法需要接收一个OrderColumn的数组,那么它就需要进行这样一个操作:
将这个数组转化为一个合法的Order语句。
我们可以将这个操作定义在OrderColunm类型中。
∙如果对多个字段排序,尽管创建数组能保证格式的正确性,但是却会比较麻烦。
如果直接输入合法的Order字符串会更加方便一些,所以我们将来应该提供重载的方法接收一个字符串作为Order子句,那么就需要对这个字符串的格式加以限制,我们也添加到这个类中。
根据上面两点分析,添加下面两个静态方法到OrderColumn中:
//获取用于排序的子字符串
public static string GetOrderQuery(OrderColumn[]columnArray){
if (columnArray== null ||columnArray.Length==0)
return "";
List
foreach (OrderColumncolumn in columnArray){
columnList.Add(column.ToString());
}
string orderColumns= String.Join(",",columnList.ToArray());
return "Orderby" +orderColumns;
}
//判断输入一个子查询的格式是否合法
//合法格式举例:
Column1asc,Column2desc
public static string GetOrderQuery(string subQuery){
//如果为空,则不对列进行排序
if (String.IsNullOrEmpty(subQuery))
return "";
//如果不小心又输入了"OrderBy",则删除之
subQuery=subQuery.Replace("Orderby","");
subQuery=subQuery.Trim();
string pattern=@"\w[\w\d]*(\s+(asc|desc))?
(\s*,\s*\w[\w\d]*\s+(asc|desc))*";
if (Regex.IsMatch(subQuery,pattern))
return "Orderby" +subQuery;
throw new ArgumentException("subQuery并非有效的SQLOrder子句.");
}
ColumnType结构
从最上面的分页方法可以看到,在@temp表变量中,我们需要声明一个和想要进行分页的表的主键相匹配的字段。
因此,我们定义ColumnType结果来表示主键的类型,我只添加了常见的三种类型:
//主键的类型,生成SQL语句的时候用
public struct ColumnType {
private string type;
private ColumnType(string type){
this.type=type;
}
public static readonly ColumnType Int= new ColumnType("Int");
public static readonly ColumnType NVarchar= new ColumnType("NVarchar(400)");
public static readonly ColumnType Varchar= new ColumnType("Varchar(200)");
public override string ToString(){
return type;
}
}
这个结构比较有趣,大家知道枚举对应的都是数字类型,上面这个结构模拟了字符串类型的枚举。
PrimayKey类
这个类定义了表的主键,还是用于生成SQL语句:
public class PrimaryKey {
private string name;
private ColumnType type;
public PrimaryKey(string name,ColumnTypetype){
this.name=name;
this.type=type;
}
public PrimaryKey(string name):
this(name,ColumnType.Int){}
public string Name{
get{ return name;}
}
public ColumnTypeType{
get{ return type;}
}
}
实现过程2-核心类
终于把这些辅助类定义完毕,接下来我们来看一下含有核心算法的类BasePager:
public class BasePager :
IDisposable {
private SqlConnection conn; //连接对象
private PrimaryKey key; //主键
private string table; //表格或者视图的名称
private string whereQuery; //where子句
private int recordCount; //记录集总数
public BasePager(string connString, string table,PrimaryKeykey, string whereQuery){
this.conn= new SqlConnection(connString);
this.table=table;
this.key=key;
if (!
String.IsNullOrEmpty(whereQuery))
this.whereQuery= "Where"+whereQuery.Replace("Where","");
else
this.whereQuery= "";
recordCount=-1;
}
public BasePager(string table,PrimaryKeykey, string whereQuery){}
public BasePager(string table, string keyName, string whereQuery){}
public BasePager(string table, string keyName){}
//根据页码获得某一页的数据
public DataSetGetDataSet(int pageIndex, int pageSize, string[]columnArray,OrderColumn[]orderColumnArray){}
public DataSetGetDataSet(int pageIndex, int pageSize, string[]columnArray, string orderSubQuery){}
public DataSetGetDataSet(int pageIndex, int pageSize, string columns){}
public DataSetGetDataSet(int pageIndex, int pageSize){}
public DataSetGetDataSet(int pageIndex){}
public DataSetGetDataSet(int pageIndex, int pageSize, string columns, string orderSubQuery){
if (pageIndex<=0)
throw new ArgumentException("pageIndex必须大于0");
//起始索引的位置
int startIndex=(pageIndex-1)*pageSize;
//调用实际获取数据集的方法
return getDataSet(startIndex,pageSize,columns,orderSubQuery);
}
//根据起始索引获得数据集
private DataSetgetDataSet
(int startIndex, int pageSize, string columns, string orderSubQuery){
if (startIndex<0)
throw new ArgumentException("startIndex必须大于0");
if (pageSize<=0)
throw new ArgumentException("pageSize必须大于0");
string orderQuery=OrderColumn.GetOrderQuery(orderSubQuery);
CheckColumns(columns); //对列的格式做一个简单的约束
string sql=
String.Format(@"Declare@temptable(
recordNo int identity(1,1),
"+key.Name+"" +key.Type.ToString()+@"
)
InsertInto@temp("+key.Name+@")
Select "+key.Name+"From"+table+""+whereQuery+"" +orderQuery+@"
--获得记录总数
Select@RecordCount=Count(*)From "+table+"" +whereQuery+@"
Select "+columns+"From"+table+"where"+key.Name+@" in
(Selecttop "+pageSize+""+key.Name+@" From@temp
whererecordNo> "+startIndex+@" orderbyrecordNoasc
)"+orderQuery);
SqlCommand cmd=conn.CreateCommand();
cmd.CommandText=sql;
cmd.Parameters.Add(new SqlParameter("@recordCount",SqlDbType.Int));
cmd.Parameters["@recordCount"].Direction=ParameterDirection.Output;
SqlDataAdapter adapter= new SqlDataAdapter(cmd);
DataSet ds= new DataSet();
try {
//填充数据表
adapter.Fill(ds);
//获得记录集总数
recordCount=(int)cmd.Parameters["@recordCount"].Value;
} finally {
conn.Close();
adapter.Dispose();
cmd.Dispose();
}
return ds;
}
//获取记录集总数
private int getRecordCount(){
string sql= "SelectCount(*) as resultFrom"+table+"" +whereQuery;
SqlCommand cmd=conn.CreateCommand();
cmd.CommandText=sql;
int total=-1;
if (conn.State!
=ConnectionState.Open)
conn.Open();
try {
total=(int)cmd.ExecuteScalar();
} finally {
cmd.Dispose();
conn.Close();
}
//给recordCount赋值
recordCount=total;
return total;
}
//获取记录集总数
public int GetRecordCount(){
if (recordCount!
=-1)
return recordCount;
return getRecordCount();
}
public void Dispose(){
conn.Dispose();
}
//检查列的格式
//正确格式为:
“*”,或者“Column1,Column2”等
private void CheckColumns(string columns){}
}
上面提供了很多的方法重载,重载无非是对参数进行了一些转换,所以我就没有将它们列出来,想要了解的话可以查看本文附带的代码。
上面最重要的方法就是getDataSet()了,它根据起始索引、分页大小、所需字段、Order子句获取了数据集。
注意在构造函数我传入了表名、主键和where子句。
这是因为当使用GetRecordCount()方法获取记录数目时,仍然需要使用表名和where子句。
实现过程3-控件绑定
既然要进行演示,那么就首先需要填充点数据进行测试,在本文所附带的代码中,App_Date中已经有一个SampleDB数据库,它只含有一个Article表,以及一些数据。
我还提供了一个FillData.Sql文件,你可以通过这个文件将数据生成到其他地方。
Repeater控件绑定
在引言中我就已经说过了创建它的两个目的,其中之一就是配合Repeater控件使用。
有了上面的类型,我们再在BasePager中添加一个绑定到Repeater控件的方法并非难事:
//绑定到Repeater
public virtual void BindRepeater
(Repeaterrepeater, int pageIndex, int pageSize,
string[]columnArray,OrderColumn[]orderColumnArray)
{
DataSet ds=GetDataSet(pageIndex,pageSize,columnArray,orderColumnArray);
repeater.DataSource=ds;
repeater.DataBind();
}
这里我没有添加重载方法,当然你可以自己添加。
下面我们看下如何使用它来进行绑定,因为使用Repeater控件时我们还需要显示页码链接,这个我在本文的姐妹篇――Asp.Net分页显示控件 中已经实现了,所以我们直接拿来使用。
在Web站点下添加一个文件BindRepeater.aspx,然后放置一个Repeater控件,我将它命名为了rpArticle,另外在放置一个PlaceHolder控件用来放置我们即将动态创建的分页显示控件,我将它命名为了pnHolder。
接下来我们看下代码后置文件:
public partial class BindRepeater :
System.Web.UI.Page{
BasePager pager= new BasePager("Article","Id","PostDate>'2007-1-1'");
private int recordCount{
get{
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 一步步 构建 半自动 数据 分页 模块