• 基于存储过程的MVC开源分页控件--LYB.NET.SPPager


    摘要

        现在基于ASP.NET MVC的分页控件我想大家都不陌生了,百度一下一大箩筐。其中有不少精品,陕北吴旗娃杨涛大哥做的分页控件MVCPager(http://www.webdiyer.com/)算作当下开源里面的佼佼者,曾经在使用过程中感觉效果非常棒,拜读其源码也受益非浅。但我在使用其中的linq进行分页操作时,如下:

                    var log = from m in db.TESTA
                              join n in db.TESTB on m.id equals n.id
                              select new ResultModel
                              {
                                  ....
                              };  //linq联合查询
                    return log.ToPagedList(pageindex ?? 1, 20);

        发现当数据量在百万级的时候,性能下降非常厉害,追踪了一下代码发现linq的底层分页用的是Row_Number分页,当数据量变大时,性能会下降的厉害。(具体分页性能可以参考我文章后面那位兄弟的总结,不过貌视不怎么准,这不是我篇文章的重点,有兴趣的可以自己测试一下,反正我在用的时候百万数据,点任意一页都要等5S左右⊙﹏⊙‖∣)。

        于是写下LYB.NET.SPPager控件,并提交到CodePlex,希望大家多提意见。

        项目源地址:https://lybpager.codeplex.com/

        项目下载地址:https://lybpager.codeplex.com/releases 其中包括分页控件源码,演示工程,分页控件DLL

    需求分析

        在一般的网页开发中,在数据库中写通用分页存储过程,然后在WEB中调用是一种最常见的方法。于是基本这个大前提下,规划出以下需求:

    •     1、控件可以实现有一个默认存储过程,无需客户端用户关心存储过程;
    •     2、控件可以扩展支持用户自定义存储过程;
    •     3、控件获取的数据列表以List<T>的形式传递给客户端,使用户不用去操作诸如SqlDataReader之类与数据库相关的东西;
    •     4、完成前端页码导航;

    客户端使用

    最终调用效果(无样式表,纯天然的)

    多页效果

    客户调用代码说明

    使用到的默认分页存储过程.LYBPager可以在演示项目根目录中找到。测试表数据结构(数据库名:Shop)如下:

    1、建一个基于MVC3.0的网站。文件结构如下图所示:

    2、控制器HomeController代码如下:

    using LYB.NET.SPPager;
    using LYB.NET.WEBTEST.Models;
    
    namespace LYB.NET.WEBTEST.Controllers
    {
        public class HomeController : Controller
        {
            public ActionResult Index(int? id)
            {
                string connection = "Data Source=玉宝;Initial Catalog=Shop;Integrated Security=True;Pooling=False";
                //采用默认LYBSPPager分页存储过程
                StoredProcedureBase sql = new LYBSPPager(connection)
                {
                    TableName = "Products",
                    PrimaryKey = "ProductId",
                    SortField = "ProductId desc",
                    OrderBy = 1
                };
                IRepository target = new SPRepository(sql); 
    
                int pageindex = id ?? 1;
                var actual = target.GetList<Product>(pageindex, 5);
    
                if (Request.IsAjaxRequest())
                    return PartialView("PageList", actual);
    
                return View(actual);
            }
    
        }
    }

    3、查询的数据模型Product如下:

    using LYB.NET.SPPager;
    
    namespace LYB.NET.WEBTEST.Models
    {
        public class Product
        {
            [BindingFieldAttribute("ProductId")]
            public int ProductId { get; set; }
            [BindingFieldAttribute("ProductName")]
            public string ProductName { get; set; }
        }
    }

    采用C#自定义的特性BindingFieldAttribute实现数据库字段与对象属性进行绑定,从而实现为具体的数据库操作分离;

    4、主界面Index.cshtml如下:

    @using LYB.NET.SPPager
    @using LYB.NET.WEBTEST.Models
    @model PageList<Product><h2>Index</h2>
    @Html.Partial("PageList", Model)

    5、分页列表PageList.cshtml(部分视图)代码如下:

    @using LYB.NET.SPPager
    @using LYB.NET.WEBTEST.Models
    @model PageList<Product>
    
    <div id = "mainpage">
    <table>
        <tr>
            <th>
                Product ID
            </th>
            <th>Product Name</th>
        </tr>
    
    @foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.ProductId)
            </td>
            <td>
               @Html.DisplayFor(modelItem => item.ProductName)
            </td>
        </tr>
    }
    
    </table>
    @Html.AjaxPager(Model, new PageOption { }, new AjaxOptions { UpdateTargetId="mainpage" })
    </div>

    到这里,完成全部的调用过程,点一下你的VS运行试试吧。

    分页源码分析

    源代码文件结构如下:

    类图结构如下:

    部分核心代码解释:

    1、IPageList接口是参考杨涛大哥的MVCPager中的实现,能够为前端页码导航提供当前页码、总记录数、每页记录数待信息;

    2、IRepository接口可以为将来控件扩展或用户自己扩展时,提供获取数据的接口,而不用改动WEB端调用代码;

    3、StoredProcedureBase是个抽象基类,如果用户想使用自己的存储过程,只要继承这个类,并重写以下两个属性,然后添加自己想要的属性(即自定义存储过程的参数)即可:

        public abstract class StoredProcedureBase
        {
            public abstract SqlParameter[] Parament { get; }
            public abstract int RecordCount { get; }
    ...//其它略 }

    这里特别注意的是自定义存储过程时,必须要有RecordCount这个参数,并为输出类型,可以参考我的默认LYBSPPager的写法

    namespace LYB.NET.SPPager
    {
        ///<summary>
        ///<para>默认LYBPager存储过程</para> 
        ///<para>[带*号为初始化时必须指定字段]</para>
        ///<para>*TableName --要显示的表或多个表的连接 </para> 
        ///<para>*PrimaryKey --用于排序的主键  </para>
        ///<para>*SortField --用于排序,如:id desc (多个id desc,dt asc)</para>
        ///<para>QueryField --要查询出的字段列表,*表示全部字段  </para>
        ///<para>Where --查询条件,不需where  </para>
        ///<para>OrderBy --排序,0-顺序,1-倒序,与SortField保持一致  </para>
        ///<para>RecordCount --查询到的总记录数[readonly] </para> 
        /// </summary>
        public class LYBSPPager : StoredProcedureBase
        {
            public string TableName { get; set; }
            public string QueryField { get; set; }
            public string PrimaryKey { get; set; }
            public string SortField { get; set; }
            public string Where { get; set; }
            public int OrderBy { get; set; }
    
            public LYBSPPager(string strconn)
                : base(strconn, ".LYBPager") { }
    
            /// <param name="strconn">连接字符串名称</param>
            /// <param name="tablename">要显示的表或多个表的连接 </param>
            /// <param name="primarykey">主键</param>
            /// <param name="sortfield">用于排序,如:id desc (多个id desc,dt asc)</param>
            public LYBSPPager(string strconn, string tablename, string primarykey, string sortfield)
                : base(strconn, ".LYBPager")
            {
                TableName = tablename;
                PrimaryKey = primarykey;
                SortField = sortfield;
            }
    
            public override SqlParameter[] Parament
            {
                get
                {
                    if (string.IsNullOrEmpty(TableName))
                        throw new ApplicationException("LYBSPPager对象TableName属性不能为空");
    
                    if (string.IsNullOrEmpty(PrimaryKey))
                        throw new ApplicationException("LYBSPPager对象PrimaryKey属性不能为空");
    
                    if (string.IsNullOrEmpty(SortField))
                        throw new ApplicationException("LYBSPPager对象SortField属性不能为空");
    
                    SqlParameter[] para = new SqlParameter[]{  
                    new SqlParameter("@strTable",SqlDbType.VarChar,-1),  
                    new SqlParameter("@strField",SqlDbType.VarChar,-1),  
                    new SqlParameter("@pageSize",SqlDbType.Int),  
                    new SqlParameter("@pageIndex",SqlDbType.Int),  
                    new SqlParameter("@strSortKey",SqlDbType.VarChar,-1),  
                    new SqlParameter("@strSortField",SqlDbType.VarChar,-1), 
                    new SqlParameter("@strOrderBy",SqlDbType.Bit),  
                    new SqlParameter("@RecordCount",SqlDbType.Int),  
                    new SqlParameter("@strWhere",SqlDbType.VarChar,-1)
                    };
    
                    para[0].Value = TableName;
                    para[1].Value = QueryField;
                    para[2].Value = PageSize;
                    para[3].Value = CurrentPageIndex;
                    para[4].Value = PrimaryKey;
                    para[5].Value = SortField;
                    para[6].Value = OrderBy;
                    para[7].Direction = ParameterDirection.Output;
                    para[8].Value = Where;
    
                    return para; 
                }
            }
            public override int RecordCount { get { return int.Parse(_command.Parameters["@RecordCount"].Value.ToString()); } }
        }
    }

    总结

        第一次写C#的DLL,代码中存在很多不完善的地方,还请志同道合的同仁一起帮忙。谢谢大家。

        项目中存储过程用的是MAX的分页,在100W级数据测试下,速度还是非常快的。关于存储过程分页速度方面,有位兄弟已经做过类似总结:http://www.cnblogs.com/yangyy753/archive/2013/01/23/2872753.html

        最后,说一下关于分页这个东西,在很多人看来,这是个很小的东西。看过有些弟兄的评论说:一个分页至于整的这么复杂吗?其实不怕各位高手笑话,我以前分页确实都是直接写的,方便,快速,后来发现每次都要自己重新写。做了很多重复的工作。直到用了杨涛大哥的分页,才告别这种COPY代码的生活。

       

    ---工作也有好几年了,做过ASP,弄过嵌入式开发,写过单片机,整过无线通信协议,该静下心来了,为了自己的事业,加油!!

  • 相关阅读:
    视频4K技术的解读
    C语言野指针
    获取一个整数所有的质因数(C语言实现)
    乘法口诀表(C语言实现)
    完全平方数(C语言实现)
    Socket网络编程系列教程序
    求1-2/3+3/5-4/7+......49/97和(C语言实现)
    反射
    Cloneable接口和Object的clone()方法
    Comparable和Comparator的区别
  • 原文地址:https://www.cnblogs.com/yubaolee/p/3144355.html
Copyright © 2020-2023  润新知