摘要
現在基於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,弄過嵌入式開發,寫過單片機,整過無線通信協議,該靜下心來了,為了自己的事業,加油!!