動(dòng)態(tài)多條件查詢分頁(yè)以及排序(一)--MVC與Entity Framework版url分頁(yè)版
一.前言
多條件查詢分頁(yè)以及排序 每個(gè)系統(tǒng)里都會(huì)有這個(gè)的代碼 做好這塊 可以大大提高開(kāi)發(fā)效率 所以博主分享下自己的6個(gè)版本的 多條件查詢分頁(yè)以及排序
二.目前狀況
不論是ado.net 還是EF 在做多條件搜索時(shí) 都有這類(lèi)似的代碼

這樣有幾個(gè)不好的地方
1.當(dāng)增加查詢條件,需要改代碼,對(duì)應(yīng)去寫(xiě)相應(yīng)的代碼。
2.對(duì)多表查詢以及or的支持 不是很好。而我們很常見(jiàn)的需求不可能是一個(gè)表的查詢
3. 這樣寫(xiě)表示層直接出現(xiàn) 了SQL語(yǔ)句 或者 linq 的拉姆達(dá)表達(dá)式 這是很不好的 表示層不應(yīng)該知道數(shù)據(jù)訪問(wèn)技術(shù)
4.有的時(shí)候 我們的業(yè)務(wù)邏輯層接口是這樣的 IList<***> seach(string name,string age,string classname,int pageindex,int pagesize,string oderby)
這個(gè)時(shí)候 多一個(gè)查詢條件 對(duì)應(yīng)的還要去修改業(yè)務(wù)邏輯層 EF由于傳遞的是表達(dá)式樹(shù),則更是苦不堪言.
三.我們接下來(lái)應(yīng)該實(shí)現(xiàn)的目標(biāo)
1.當(dāng)增加條件時(shí) 不需要修改代碼 只需要在view上 增加相應(yīng)的查詢框即可
2.我們的多條件查詢 應(yīng)該做到無(wú)關(guān)表示層技術(shù)(是否是MVC或webform)
3.應(yīng)該支持多表查詢 以及OR的操作
4.應(yīng)該支持更多的查詢 like in 不等于 等操作
5.關(guān)于分頁(yè) 不應(yīng)該與數(shù)據(jù)訪問(wèn)耦合在一起 個(gè)人感覺(jué) 分頁(yè)只需要知道總條數(shù) 以及當(dāng)前頁(yè)數(shù) 和每頁(yè)多少條 然后生成分頁(yè)代碼即可 不應(yīng)該與EF等耦合到一起 分頁(yè)應(yīng)該是獨(dú)立出來(lái) 可控制的
6.客戶可以自己添加搜索條件 這是個(gè)強(qiáng)大的功能 想怎么查 客戶自己添加即可
7. 統(tǒng)一查詢接口 做到有條件增加 不修改代碼
8.分頁(yè)應(yīng)該支持 url重寫(xiě)或者 mvc路由 不應(yīng)該生成的連接只是?pageindex=值 這種的
四.我實(shí)現(xiàn)的幾個(gè)多條件查詢分頁(yè)版本以適應(yīng)各種需求(每篇會(huì)寫(xiě)一個(gè)版本的實(shí)現(xiàn)以及代碼的提供,有好意見(jiàn)的歡迎留言)
1.url get提交版 實(shí)現(xiàn)URL分頁(yè)多條件查詢以及排序的好處是 我們可以把當(dāng)前的搜索條件 當(dāng)前頁(yè)數(shù) 排序等 都在url上顯示 可以方便的發(fā)給好友 以及后退等瀏覽器操作(個(gè)人給dudu老大建議,博客園應(yīng)該做成這種的)
2.post 提交版本的 搜索條件較大 不適合用url的
3.ajax+mvc版本的 (關(guān)于AJAX實(shí)現(xiàn) 我認(rèn)為有兩種 1.服務(wù)端實(shí)現(xiàn)好內(nèi)容的拼接,傳輸給客戶端 2直接傳遞json給客戶端 客戶端來(lái)做拼接)
這個(gè)版本會(huì)實(shí)現(xiàn)服務(wù)端拼接內(nèi)容 好處是 服務(wù)端做拼接簡(jiǎn)單 能做更多的事情 維護(hù)服務(wù)端代碼方便 尤其是強(qiáng)大的Razor
4.ajax+webapi+Knockoutjs 版本
這個(gè)版本 我實(shí)現(xiàn)的是 服務(wù)端只是傳遞 json 這樣服務(wù)端效率很高 喜歡這樣開(kāi)發(fā)方式的朋友 就是前端拼接字符是很不好的 代碼會(huì)顯得很亂 這個(gè)時(shí)候 前端就需要一個(gè)模版引擎我用的是 jquery-temp 配合強(qiáng)大的Knockoutjs
5.動(dòng)態(tài)增加查詢條件版
這個(gè)版本我實(shí)現(xiàn)的是 客戶可以自己添加查詢條件 查詢條件是動(dòng)態(tài)的
6.移植到webform版
7.EF應(yīng)該得到表達(dá)式樹(shù) 讓EF自己生成SQL語(yǔ)句 這樣方便擴(kuò)展 實(shí)現(xiàn)其他方法
五.url get提交版開(kāi)始
廢話了那么多 今天就寫(xiě)下url get版的多條件查詢 以及分頁(yè) 排序
先上個(gè)丑陋界面的截圖 界面雖丑 但是功能齊全 查詢 分頁(yè) 排序齊全

看下控制器的代碼

我們這里沒(méi)有各種條件判斷 判斷哪個(gè)為空 使用哪個(gè)排序的判斷 我們的業(yè)務(wù)邏輯層接口 沒(méi)有接受表達(dá)式樹(shù)的參數(shù) 與數(shù)據(jù)訪問(wèn)層不是耦合的 而是使用了Querymodel對(duì)象 來(lái)抽象所有查詢條件
這樣 這個(gè)對(duì)象 可以翻譯成 EF的表達(dá)式樹(shù) 也可以翻譯成SQL語(yǔ)句 所以我們的 表示層MVC 不用非要使用EF的的底層
而我們的分頁(yè) 只需要知道當(dāng)前頁(yè)數(shù) 總數(shù)據(jù) 以及 每頁(yè)大小 去自動(dòng)生成分頁(yè)
關(guān)于分頁(yè) 很多人喜歡把這個(gè)擴(kuò)展htmlhelper 做成 html.pager 這種做法 這樣很多表示層的展示 都會(huì)耦合到 這個(gè)里面 舉個(gè)例子,比如把分頁(yè)的布局 從 table 變成 ul 這樣的 這是純表示層的
本應(yīng)該修改 view 現(xiàn)在卻要去改htmlhelper 而且你的分頁(yè)代碼越強(qiáng)大 則htmlhelper 里的內(nèi)容越多 修改起來(lái)越不容易 所以我的意見(jiàn)是 做分頁(yè)的 最好使用html.Partial 然后把分頁(yè)的邏輯寫(xiě)到
部分頁(yè)里 這樣就實(shí)現(xiàn)了分頁(yè) 只關(guān)注分頁(yè) 與其他的一切都沒(méi)有關(guān)系 我們要做的就是構(gòu)建 Pager類(lèi) 然后傳遞給模版即可 例如

這個(gè)版本要注意的就是 分頁(yè)后要保存查詢條件
六.url get提交版實(shí)現(xiàn)分析
1.先來(lái)說(shuō)下多條件查詢吧
我們要做的是把各個(gè)條件構(gòu)建成QueryModel 而如何構(gòu)建則是關(guān)鍵 我們想下 獲得mvc提交的內(nèi)容的是根據(jù)name 所以我們可以固定一個(gè)name的格式 如
ID(like操作): <input type="text" name="[Contains]StuId" value="@StuId" />
這樣我們可以通過(guò)正則獲取想要信息的部分 然后在模型綁定構(gòu)建出QueryModel對(duì)象 然后再把這個(gè)對(duì)象 翻譯成SQL語(yǔ)句 或者表達(dá)式樹(shù) 就可以用于ado 或者 EF操作了
順便重點(diǎn)說(shuō)下 這個(gè)思路 園子里的重典早都實(shí)現(xiàn)了 而且實(shí)現(xiàn)的很好 大家可以去他的博客看下 以后的各個(gè)版本的分頁(yè) 都會(huì)用這個(gè)表達(dá)式樹(shù)構(gòu)建為基礎(chǔ) 這是他文章的鏈接 重典老哥的實(shí)現(xiàn)
(ps:這周剛剛見(jiàn)過(guò)他本人,聊得甚歡,開(kāi)心.希望以后還可以多聚聚)
不過(guò)重典老哥的表達(dá)式樹(shù)構(gòu)建 我改成自己的實(shí)現(xiàn)了 整體思路還是一樣的 自戀的說(shuō)下 ~ 我的可讀性更高些 哈哈 因?yàn)橹氐湟呀?jīng)描述的很詳細(xì)了 具體可以看我的代碼和他的文章
當(dāng)然我們得到 通過(guò)QueryModel 得到的表達(dá)式樹(shù) 通過(guò)擴(kuò)展方法 直接調(diào)用Where方法即可 讓我們看下我們的EF的業(yè)務(wù)邏輯層
public class StudentService:IStudentService { /// <summary> /// 按條件搜索 /// </summary> /// <param name="query">搜索條件</param> /// <param name="pageIndex">當(dāng)前頁(yè)數(shù)(索引從1開(kāi)始)</param> /// <param name="pageSize">每頁(yè)顯示條數(shù)</param> /// <param name="total">總條數(shù)</param> /// <param name="orderBy">排序字段</param> /// <param name="ascending">是否升序</param> /// <returns></returns> public IList<Student> Search(QueryModel query, int pageIndex, int pageSize, out int total, string orderBy, bool ascending) { IList<Student> stulist = Builder<Student>.CreateListOfSize(321).TheFirst(44).With(x => x.StuName = "hy").And(x => x.Nullint = 1).And(x => x.LoveGril = "LILI").And(x => x.CreateTime = new DateTime(2012, 02, 03)).And(x => x.Birthday = new DateTime(2012, 09, 01)).And(x => x.Stuclass = new StuClass() { ClassId = "2", ClassName = "二班" }).TheNext(33).With(x => x.StuName = "wlf").And(x => x.Nullint = 2).And(x => x.LoveGril = "MM").And(x => x.CreateTime = new DateTime(2012, 06, 06)).And(x => x.Birthday = new DateTime(2012, 09, 010)).And(x => x.Stuclass = new StuClass() { ClassId = "1", ClassName = "一班" }).TheNext(244).And(x=>x .Stuclass=new StuClass(){ClassId = "3", ClassName = "三班"}).Build(); var dbcontext = stulist.AsQueryable(); //模擬EF context 假設(shè)數(shù)據(jù)庫(kù)里原數(shù)據(jù)為200條 dbcontext = dbcontext.Where(query); total = dbcontext.Count();//執(zhí)行查詢數(shù)量sql dbcontext = dbcontext.OrderBy(orderBy, ascending).Skip(pageSize * (pageIndex - 1)).Take(pageSize); return dbcontext.ToList();//執(zhí)行分頁(yè)排序查詢sql } }
這里說(shuō)下 為了大家調(diào)試方便 不用真正的數(shù)據(jù)庫(kù) 用了測(cè)試神奇NBuilder 來(lái)模擬的 然后轉(zhuǎn)換成AsQueryable 來(lái)模擬EF的
看上面代碼 可以看到 沒(méi)有了各個(gè)分支的條件判斷 以及排序的判斷 以后多出查詢條件 不需要修改業(yè)務(wù)邏輯層了~
2.接下來(lái)說(shuō)下url get提交的思路
上問(wèn)說(shuō)過(guò)自己的觀點(diǎn) 分頁(yè)最好能放到部分頁(yè) 而不是擴(kuò)展htmlhelper 所以我的實(shí)現(xiàn)方式是把構(gòu)建Pager分頁(yè)視圖類(lèi) 傳遞給 部分視圖 來(lái)做如何展示 一些分頁(yè)有關(guān)的邏輯封裝在 Pager里
下面是Pager的代碼
public class Pager { public Pager(int currentPageIndex, int totalItemCount, int pagesize = 20) { this.TotalItemCount = totalItemCount; this.PageSize = pagesize; this.CurrentPageIndex = currentPageIndex > TotalPageCount ? 1 : currentPageIndex; } /// <summary> /// 當(dāng)前第幾頁(yè) /// </summary> public int CurrentPageIndex { get; set; } /// <summary> /// 每頁(yè)顯示多少條 /// </summary> public int PageSize { get; set; } /// <summary> /// 總共多少條記錄 /// </summary> public int TotalItemCount { get; set; } /// <summary> /// 總共多少頁(yè) /// </summary> public int TotalPageCount { get { double pageCount = (double)TotalItemCount / (double)PageSize; pageCount = Math.Ceiling(pageCount); return (int)pageCount; } } /// <summary> /// 是否顯示 /// </summary> public bool IsShow { get { if (TotalPageCount > 0) { return true; } else { return false; } } } /// <summary> /// 是否顯示上一頁(yè) /// </summary> public bool HasPreviousPage { get { return (CurrentPageIndex > 1); } } /// <summary> /// 是否顯示下一頁(yè) /// </summary> public bool HasNextPage { get { return (CurrentPageIndex < TotalPageCount); } } }
而分頁(yè)的部分視圖 只用根據(jù)這個(gè)類(lèi) 去管理如何展示
這里分享幾個(gè)小技巧
技巧一.
因?yàn)槲覀円獙?shí)現(xiàn)下面需求
1. 我們要保存以前的URL 信息再里面 不能讓以前的消失 2 需要支持 url路由后的 分頁(yè) 比如 控制器/方法/Page1 而不是?pageIndex=1 3.因?yàn)楹芏鄠€(gè)頁(yè)面都要用到 所以要與方法名解耦
實(shí)現(xiàn)這個(gè)的技巧關(guān)鍵 就是使用RouteValueDictionary
View Code
var queryString = ViewContext.HttpContext.Request.QueryString; var dict = new System.Web.Routing.RouteValueDictionary(ViewContext.RouteData.Values); foreach (string item in queryString.Keys) { dict[item] = queryString[item]; }
通過(guò)上面的代碼 就可以保存住以前的參數(shù)了 接著就是
dict["PageIndex"] = 1;//設(shè)置頁(yè)碼 @Html.RouteLink("首頁(yè)", dict);
這樣就可以設(shè)置分頁(yè) 以及解耦控制器 和方法名了~
如果你設(shè)置了類(lèi)似的路由
routes.MapRoute("Page", "{controller}/{action}/page{PageIndex}", new { controller = "WLFQuery", action = "Index", PageIndex = 1 });
那么也是支持的~
2.技巧二
因?yàn)槲覀兊姆猪?yè) 支持通過(guò)下拉框選擇頁(yè)碼后自動(dòng)跳頁(yè) 有點(diǎn)兒像webform 的autopostback 而我們又是get 提交版的 需要解決一個(gè)問(wèn)題 選擇后自動(dòng)跳頁(yè) 需要帶上以前的查詢條件 不能跳完頁(yè)以后 查詢條件消失了
這里采用的辦法 借鑒了一下 aspnetpager 作者的 urlpager的思路
用一個(gè)隱藏的a 標(biāo)簽 這個(gè)a 標(biāo)簽生成的連接 保存了當(dāng)前的查詢條件等 他的頁(yè)面數(shù) 顯示成"*pageindex* 當(dāng)我們選擇下拉框跳頁(yè)后 用選擇的值 替換這個(gè)"*pageindex* 然后跳轉(zhuǎn)到當(dāng)前href 則解決了上述問(wèn)題
上代碼
<text>跳轉(zhuǎn)至</text>
<select id="pageselect" onchange="selectchange()">
@for (int i = 1; i <=@Model.TotalPageCount; i++)
{
var selected = "";
if (i==Model.CurrentPageIndex)
{
selected = "selected='selected'";
}
<option value="@i" @selected>@i</option>
}
</select>
{
dict["PageIndex"] = "*pageindex*";
}
<a style="display:none" id="pagelink" href="@Url.RouteUrl(dict)" ></a>
//
<script type="text/javascript">
function selectchange() {
var pageselect = document.getElementById("pageselect");
var pageselectValue = pageselect.options[pageselect.selectedIndex].value;
var linkdom= document.getElementById("pagelink");
var href = linkdom.href;
href = href.replace("*pageindex*", pageselectValue);
window.location = href;
}
</script>
<text>頁(yè)</text>
3.點(diǎn)擊搜索時(shí) get 提交 并回到第一頁(yè)
<form action="@Url.Action("index", new { PageIndex = 1 })" method="get">
</form>
具體代碼可以下載源碼看~
七. URL GET提交的遺憾
上面基本功能已經(jīng)實(shí)現(xiàn) 但是有些遺憾的是 大家知道 mvc沒(méi)有viewstate機(jī)制 而且我們的又是URL分頁(yè) 及時(shí)是webform 也會(huì)面臨這個(gè)問(wèn)題
雖然我們通過(guò)url保存了搜索的狀態(tài) 但是沒(méi)有解決點(diǎn)擊分頁(yè)后 搜索框的內(nèi)容還在。所以會(huì)出來(lái)一個(gè)奇怪的現(xiàn)象 按條件搜索完 比如搜索姓名為hy的 然后點(diǎn)擊分頁(yè) 搜索框的內(nèi)容沒(méi)了
但是分頁(yè)后內(nèi)容卻還是hy的 因?yàn)槲覀兊膗rl 里存的有搜索結(jié)果 但是重新加載后 文本框的內(nèi)容卻沒(méi)了 這樣雖然無(wú)傷大雅 但是還是略感不爽 如果非要解決 也不是沒(méi)有辦法 只是覺(jué)得都不是很完美
方案1.
ID(like操作): <input type="text" name="[Contains]StuId" value="@Request.QueryString["[Contains]StuId"]" /><br />
因?yàn)槲覀兊膗rl里保存的有真實(shí)的搜索條件 所以我們可以通過(guò)Request.QueryString["[Contains]StuId"] 直接得到 但是我覺(jué)得在MVC里 出現(xiàn)Request.QueryString 這樣的信息 不好看
方案2.
ViewContext.Controller.ValueProvider.GetValue("[Contains]StuId").RawValue 通過(guò)這樣 也可以得到值
方案3.
在控制器里
ViewBag.Query = querymodel; 記錄搜索條件 然后在view里讀出來(lái)
QueryModel query=ViewBag.Query as QueryModel; string StuId=query.Items.Where(x => x.Field == "StuId").Select(x=>x.Value).FirstOrDefault()!=null?query.Items.Where(x => x.Field == "StuId").Select(x=>x.Value).FirstOrDefault().ToString():"";
上面三個(gè)方式 感覺(jué)都有不完美的地方 因?yàn)槲覀冞€原的內(nèi)容 不只是 text 有可能是 多選框 下拉列表 等 所以要做處理 就稍微麻煩了下 比如
string[] newarrlovewgril = (string[])arrlovegril; MMischeck= newarrlovewgril.Contains("MM") ? "checked='checked'" : ""; LUCIischeck = newarrlovewgril.Contains("LILI") ? "checked='checked'" : ""; GAGAischeck = newarrlovewgril.Contains("GAGA") ? "checked='checked'" : ""; MM<input type="checkbox" name="lovegril" value="MM" @MMischeck /> LILI<input type="checkbox" name="lovegril" value="LILI" @LUCIischeck /> GAGA<input type="checkbox" name="lovegril" value="GAGA" @GAGAischeck />
我再想 把這些還原也做成自動(dòng)化的 不用任何代碼的 求集思廣益 希望大家給些建議
八.總結(jié)
EF實(shí)現(xiàn)動(dòng)態(tài)查詢以及排序的關(guān)鍵 就在于表達(dá)式樹(shù)的構(gòu)建。所以學(xué)會(huì)并理解表達(dá)式樹(shù)很關(guān)鍵,這個(gè)不盡在這里有用,用來(lái)代替反射也可以提高效率~不過(guò)表達(dá)式樹(shù)的生成 建議實(shí)際項(xiàng)目中 加上緩存。
表達(dá)式樹(shù)的學(xué)習(xí) 可以看下這幾篇
http://www.rzrgm.cn/Terrylee/archive/2008/08/01/custom-linq-provider-part-1-expression-tree.html
http://www.rzrgm.cn/Ninputer/archive/2009/08/28/expression_tree1.html
http://msdn.microsoft.com/en-us/library/bb397951.aspx
短短文章 花了半天才寫(xiě)完~ 希望對(duì)大家有幫助 或者能給大家一些啟示 有問(wèn)題可以留言交流 歡迎批評(píng)和建議 感謝閱讀~
順便幫忙宣傳下 我在的兩個(gè)QQ群吧 33353329 205217091 有興趣的可以進(jìn)來(lái)討論
過(guò)幾天介紹另兩個(gè)版本 POST提交版和 MVC+EF AJAX 多條件查詢 分頁(yè) 排序
最后附上本節(jié)代碼下載 (代碼比較粗糙,主要做演示)
轉(zhuǎn)載請(qǐng)說(shuō)明來(lái)自 http://www.rzrgm.cn/wlflovenet/archive/2012/11/30/MVC_EntityFramework_Query.html

浙公網(wǎng)安備 33010602011771號(hào)