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

這樣有幾個(gè)不好的地方
1.當(dāng)增加查詢條件,需要改代碼,對(duì)應(yīng)去寫相應(yīng)的代碼。
2.對(duì)多表查詢以及or的支持 不是很好。而我們很常見的需求不可能是一個(gè)表的查詢
3. 這樣寫表示層直接出現(xiàn) 了SQL語句 或者 linq 的拉姆達(dá)表達(dá)式 這是很不好的 表示層不應(yīng)該知道數(shù)據(jù)訪問技術(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á)式樹,則更是苦不堪言.
三.我們接下來應(yīng)該實(shí)現(xiàn)的目標(biāo)
1.當(dāng)增加條件時(shí) 不需要修改代碼 只需要在view上 增加相應(yīng)的查詢框即可
2.我們的多條件查詢 應(yīng)該做到無關(guān)表示層技術(shù)(是否是MVC或webform)
3.應(yīng)該支持多表查詢 以及OR的操作
4.應(yīng)該支持更多的查詢 like in 不等于 等操作
5.關(guān)于分頁 不應(yīng)該與數(shù)據(jù)訪問耦合在一起 個(gè)人感覺 分頁只需要知道總條數(shù) 以及當(dāng)前頁數(shù) 和每頁多少條 然后生成分頁代碼即可 不應(yīng)該與EF等耦合到一起 分頁應(yīng)該是獨(dú)立出來 可控制的
6.客戶可以自己添加搜索條件 這是個(gè)強(qiáng)大的功能 想怎么查 客戶自己添加即可
7. 統(tǒng)一查詢接口 做到有條件增加 不修改代碼
8.分頁應(yīng)該支持 url重寫或者 mvc路由 不應(yīng)該生成的連接只是?pageindex=值 這種的
四.我實(shí)現(xiàn)的幾個(gè)多條件查詢分頁版本以適應(yīng)各種需求(每篇會(huì)寫一個(gè)版本的實(shí)現(xiàn)以及代碼的提供,有好意見的歡迎留言)
1.url get提交版 實(shí)現(xiàn)URL分頁多條件查詢以及排序的好處是 我們可以把當(dāng)前的搜索條件 當(dāng)前頁數(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給客戶端 客戶端來做拼接)
這個(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ù)端效率很高 喜歡這樣開發(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á)式樹 讓EF自己生成SQL語句 這樣方便擴(kuò)展 實(shí)現(xiàn)其他方法
五.url get提交版開始
廢話了那么多 今天就寫下url get版的多條件查詢 以及分頁 排序
先上個(gè)丑陋界面的截圖 界面雖丑 但是功能齊全 查詢 分頁 排序齊全

看下控制器的代碼

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

這個(gè)版本要注意的就是 分頁后要保存查詢條件
六.url get提交版實(shí)現(xiàn)分析
1.先來說下多條件查詢吧
我們要做的是把各個(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" />
這樣我們可以通過正則獲取想要信息的部分 然后在模型綁定構(gòu)建出QueryModel對(duì)象 然后再把這個(gè)對(duì)象 翻譯成SQL語句 或者表達(dá)式樹 就可以用于ado 或者 EF操作了
順便重點(diǎn)說下 這個(gè)思路 園子里的重典早都實(shí)現(xiàn)了 而且實(shí)現(xiàn)的很好 大家可以去他的博客看下 以后的各個(gè)版本的分頁 都會(huì)用這個(gè)表達(dá)式樹構(gòu)建為基礎(chǔ) 這是他文章的鏈接 重典老哥的實(shí)現(xiàn)
(ps:這周剛剛見過他本人,聊得甚歡,開心.希望以后還可以多聚聚)
不過重典老哥的表達(dá)式樹構(gòu)建 我改成自己的實(shí)現(xiàn)了 整體思路還是一樣的 自戀的說下 ~ 我的可讀性更高些 哈哈 因?yàn)橹氐湟呀?jīng)描述的很詳細(xì)了 具體可以看我的代碼和他的文章
當(dāng)然我們得到 通過QueryModel 得到的表達(dá)式樹 通過擴(kuò)展方法 直接調(diào)用Where方法即可 讓我們看下我們的EF的業(yè)務(wù)邏輯層
public class StudentService:IStudentService { /// <summary> /// 按條件搜索 /// </summary> /// <param name="query">搜索條件</param> /// <param name="pageIndex">當(dāng)前頁數(shù)(索引從1開始)</param> /// <param name="pageSize">每頁顯示條數(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ù)庫里原數(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í)行分頁排序查詢sql } }
這里說下 為了大家調(diào)試方便 不用真正的數(shù)據(jù)庫 用了測(cè)試神奇NBuilder 來模擬的 然后轉(zhuǎn)換成AsQueryable 來模擬EF的
看上面代碼 可以看到 沒有了各個(gè)分支的條件判斷 以及排序的判斷 以后多出查詢條件 不需要修改業(yè)務(wù)邏輯層了~
2.接下來說下url get提交的思路
上問說過自己的觀點(diǎn) 分頁最好能放到部分頁 而不是擴(kuò)展htmlhelper 所以我的實(shí)現(xiàn)方式是把構(gòu)建Pager分頁視圖類 傳遞給 部分視圖 來做如何展示 一些分頁有關(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)前第幾頁 /// </summary> public int CurrentPageIndex { get; set; } /// <summary> /// 每頁顯示多少條 /// </summary> public int PageSize { get; set; } /// <summary> /// 總共多少條記錄 /// </summary> public int TotalItemCount { get; set; } /// <summary> /// 總共多少頁 /// </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> /// 是否顯示上一頁 /// </summary> public bool HasPreviousPage { get { return (CurrentPageIndex > 1); } } /// <summary> /// 是否顯示下一頁 /// </summary> public bool HasNextPage { get { return (CurrentPageIndex < TotalPageCount); } } }
而分頁的部分視圖 只用根據(jù)這個(gè)類 去管理如何展示
這里分享幾個(gè)小技巧
技巧一.
因?yàn)槲覀円獙?shí)現(xiàn)下面需求
1. 我們要保存以前的URL 信息再里面 不能讓以前的消失 2 需要支持 url路由后的 分頁 比如 控制器/方法/Page1 而不是?pageIndex=1 3.因?yàn)楹芏鄠€(gè)頁面都要用到 所以要與方法名解耦
實(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]; }
通過上面的代碼 就可以保存住以前的參數(shù)了 接著就是
dict["PageIndex"] = 1;//設(shè)置頁碼 @Html.RouteLink("首頁", dict);
這樣就可以設(shè)置分頁 以及解耦控制器 和方法名了~
如果你設(shè)置了類似的路由
routes.MapRoute("Page", "{controller}/{action}/page{PageIndex}", new { controller = "WLFQuery", action = "Index", PageIndex = 1 });
那么也是支持的~
2.技巧二
因?yàn)槲覀兊姆猪?支持通過下拉框選擇頁碼后自動(dòng)跳頁 有點(diǎn)兒像webform 的autopostback 而我們又是get 提交版的 需要解決一個(gè)問題 選擇后自動(dòng)跳頁 需要帶上以前的查詢條件 不能跳完頁以后 查詢條件消失了
這里采用的辦法 借鑒了一下 aspnetpager 作者的 urlpager的思路
用一個(gè)隱藏的a 標(biāo)簽 這個(gè)a 標(biāo)簽生成的連接 保存了當(dāng)前的查詢條件等 他的頁面數(shù) 顯示成"*pageindex* 當(dāng)我們選擇下拉框跳頁后 用選擇的值 替換這個(gè)"*pageindex* 然后跳轉(zhuǎn)到當(dāng)前href 則解決了上述問題
上代碼
<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>頁</text>
3.點(diǎn)擊搜索時(shí) get 提交 并回到第一頁
<form action="@Url.Action("index", new { PageIndex = 1 })" method="get">
</form>
具體代碼可以下載源碼看~
七. URL GET提交的遺憾
上面基本功能已經(jīng)實(shí)現(xiàn) 但是有些遺憾的是 大家知道 mvc沒有viewstate機(jī)制 而且我們的又是URL分頁 及時(shí)是webform 也會(huì)面臨這個(gè)問題
雖然我們通過url保存了搜索的狀態(tài) 但是沒有解決點(diǎn)擊分頁后 搜索框的內(nèi)容還在。所以會(huì)出來一個(gè)奇怪的現(xiàn)象 按條件搜索完 比如搜索姓名為hy的 然后點(diǎn)擊分頁 搜索框的內(nèi)容沒了
但是分頁后內(nèi)容卻還是hy的 因?yàn)槲覀兊膗rl 里存的有搜索結(jié)果 但是重新加載后 文本框的內(nèi)容卻沒了 這樣雖然無傷大雅 但是還是略感不爽 如果非要解決 也不是沒有辦法 只是覺得都不是很完美
方案1.
ID(like操作): <input type="text" name="[Contains]StuId" value="@Request.QueryString["[Contains]StuId"]" /><br />
因?yàn)槲覀兊膗rl里保存的有真實(shí)的搜索條件 所以我們可以通過Request.QueryString["[Contains]StuId"] 直接得到 但是我覺得在MVC里 出現(xiàn)Request.QueryString 這樣的信息 不好看
方案2.
ViewContext.Controller.ValueProvider.GetValue("[Contains]StuId").RawValue 通過這樣 也可以得到值
方案3.
在控制器里
ViewBag.Query = querymodel; 記錄搜索條件 然后在view里讀出來
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è)方式 感覺都有不完美的地方 因?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á)式樹的構(gòu)建。所以學(xué)會(huì)并理解表達(dá)式樹很關(guān)鍵,這個(gè)不盡在這里有用,用來代替反射也可以提高效率~不過表達(dá)式樹的生成 建議實(shí)際項(xiàng)目中 加上緩存。
表達(dá)式樹的學(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
短短文章 花了半天才寫完~ 希望對(duì)大家有幫助 或者能給大家一些啟示 有問題可以留言交流 歡迎批評(píng)和建議 感謝閱讀~
順便幫忙宣傳下 我在的兩個(gè)QQ群吧 33353329 205217091 有興趣的可以進(jìn)來討論
過幾天介紹另兩個(gè)版本 POST提交版和 MVC+EF AJAX 多條件查詢 分頁 排序
最后附上本節(jié)代碼下載 (代碼比較粗糙,主要做演示)
轉(zhuǎn)載請(qǐng)說明來自 http://www.rzrgm.cn/wlflovenet/archive/2012/11/30/MVC_EntityFramework_Query.html

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