问题:在传统的分页查询的实现中不可避免的需要两条sql语句,一条用于查询数据一条用于查询总记录数。如下面的实际代码所示:
Img1
当然如果使用存储过程的话是可以渐少一次数据库来回的,但是它依然是两条sql语句,一条查数据一条查总记录数。但我们在架构层面拒绝使用存储过程,因为它将逻辑分散在了应用程序和数据库中,不好维护,并且在SqlServer2008中也没有足够的证据证明存储过程更高效。
很多人的直觉认为查询总记录数的那行没有必要,但是直觉只是第一步,项目中需要的是解决方案。
解决方案:
从客户端输入总记录数,从而避免一次对总记录数的查询。首先将分页查询的参数封装为一个简单对象PagingInput,它属于模型分类中的输入模型类别所以以Input作为命令的后缀,其类图如下:
Img2
pagingInput的代码
class="code_img_closed" src="/Upload/Images/2013081218/0015B68B3C38AA5B.gif" alt="" />logs_code_hide('6e5ee1e6-3d15-42ea-b995-d4a06e29ed9c',event)" src="/Upload/Images/2013081218/2B1B950FA3DF188F.gif" alt="" />1 namespace jDTS { 2 /// <summary> 3 /// 分页输入参数 4 /// </summary> 5 public class PagingInput { 6 /// <summary> 7 /// 构造分页参数 8 /// </summary> 9 /// <param name="pageIndex">页索引</param> 10 /// <param name="pageSize">页尺寸</param> 11 /// <param name="sortField">排序字段</param> 12 /// <param name="sortOrder">排序方向</param> 13 public PagingInput(int pageIndex, int pageSize, string sortField, string sortOrder) { 14 this.pageIndex = pageIndex; 15 this.pageSize = pageSize; 16 this.sortField = sortField; 17 this.sortOrder = sortOrder; 18 } 19 20 /// <summary> 21 /// 构造分页参数 22 /// </summary> 23 /// <param name="pageIndex">页索引</param> 24 /// <param name="pageSize">页尺寸</param> 25 /// <param name="sortField">排序字段</param> 26 /// <param name="sortOrder">排序方向</param> 27 /// <param name="total">总记录数</param> 28 public PagingInput(int pageIndex, int pageSize, string sortField, string sortOrder, int? total) 29 : this(pageIndex, pageSize, sortField, sortOrder) { 30 this.total = total; 31 } 32 33 /// <summary> 34 /// 页索引。零基索引,即第一页对应0 35 /// </summary> 36 public int pageIndex { get; set; } 37 38 /// <summary> 39 /// 页尺寸 40 /// </summary> 41 public int pageSize { get; set; } 42 43 /// <summary> 44 /// 排序字段 45 /// </summary> 46 public string sortField { get; set; } 47 48 /// <summary> 49 /// 排序方向 50 /// </summary> 51 public string sortOrder { get; set; } 52 53 /// <summary> 54 /// 归档数据有一个特性,就是它的总记录数是不改变的,所以传入total以渐少一次数据库count查询 55 /// </summary> 56 public long? total { get; set; } 57 58 /// <summary> 59 /// 查看total字段是否为空或者0 60 /// </summary> 61 public bool IsTotalNullOrZero { get { return !total.HasValue || total.Value == 0; } } 62 63 /// <summary> 64 /// pageSize * pageIndex的计算值 65 /// </summary> 66 public int SkipCount { get { return pageSize * pageIndex; } } 67 68 /// <summary> 69 /// 判断分页输入参数是否合法 70 /// </summary> 71 /// <returns></returns> 72 public bool IsValid() { 73 if (string.IsNullOrEmpty(sortField) || string.IsNullOrEmpty(sortOrder)) { 74 throw new jDTSException("排序是必须的"); 75 } 76 if (sortOrder != "asc" && sortOrder != "desc") { 77 throw new jDTSException("排序方向只能是asc或desc,小写形式"); 78 } 79 return true; 80 } 81 } 82 }View Code
将GetPlistNodeELementPermissionTrs的与分页有关的形参替换为PagingInput类,重构后的代码如下:
Img3
相应的表现层的代码也需要做调整,从表现层收集来自界面层输入的total参数,注意total是可空的类型(int?)所以界面层可以选择是否传入:
Img4
现在服务端代码已经实现了一个接收可空的total参数的契约,如果本模块确实要使用这个一次查询机制的话则只需在客户端传入total参数即可。像下面这样在beforload事件中附加一个total参数:这里使用的是国产的miniui框架,成熟的js表现层框架都有类似的事件
Img5
另外要记得在前台的js方法search中将total置为0,因为search不是翻页,调用search即表示查询条件有变化,查询条件变化则总记录数通常会变化,所以需将total置为0表示让服务器端进行count查询(还记得前文的PagingInput模型上有个只读的IsNullOrZero属性吗?服务器端就是根据它来判断是否要count一次数据库的)。下面是归档模块中的一个search方法,它在每次search的时候都将传向服务器端的total参数置为0:
Img6
适用场景:
适用这个解决方案的场景并不常见,因为它要求查询的总记录数是固定的或者是不频繁改变的,在我们的项目中目前发现有两个场景适用:1查询归档档案(Record),因为数据归档后它的总记录数是不变的;2查询本人的登录记录(VisitingLog),因为自己的登录次数在下次登录的时候才会改变。
当然如果界面上配上一个刷新按钮,刷新的时候将传向服务器端的total参数置为0的话,增删改记录不频繁的模块也是可以这么干的。