Dynamic Query是一個支持動態Entity Framework查詢的庫。它的設計初衷是為了減少在管理系統中大量出現的對一個數據集進行查找、排序、分頁的這類場景的開發工作量,其設計思想是”markup is code ”。通過在View上編寫具有語義信息的標簽,來實現這類常見的功能需求,而不再需要額外的代碼。它不是一個完整的ORM,是基於Entity Framework的,因此開發者仍然可以利用Entity Framework提供的大量特性,和現有的EF項目保持兼容。
Dynamic Query分為兩個部分,其中之一是一個查詢接口,這是一個IQueryable<T>的擴展方法:
public static IQueryable<T> Query<T>(this IQueryable<T> data, QueryDescriptor descriptor)
其中QueryDescriptor是一個簡單的類,包含了一個查詢的必要信息,例如,篩選條件,排序信息,分頁信息等。
例如:
QueryDescriptor descriptor = new QueryDescriptor { OrderBy = new OrderByClause { Key = "Price", Order = OrderSequence.ASC }, PageSize = 3, PageIndex = 1, Conditions=new QueryCondition[] { new QueryCondition { Key = "Name",Value = "Rice", Operator = QueryOperator.CONTAINS } } }; int pageCount; var res=ctx.Products.Query(descriptor, out pageCount);
這相當於執行了查詢:
select * from Product where [Name] like N'%Rice%' order by Price asc
並且對結果進行分頁,每頁3條數據,返回第一頁。注意,這里返回的結果是IQueryable<T>,這實際上是一個Entity Framework的查詢,在沒有序列化之前,並沒有對數據庫進行操作,分頁也是發生在服務器端的,這對於大數據來說能夠極大的減少網絡傳輸和內存使用量。 當然,手動構造這樣一個QueryDescriptor也是一件非常無趣的事情,為此Dynamic Query還為asp.net MVC 實現了一系列的helper方法和一個model binder,來自動生成QueryDescriptor。我們最終的目標是獲得頁面提交的數據,自動生成QueryDescriptor對象。為此,需要注冊一下一個自定義的binder,只需要在Global.asax的Application_Start中添加一行代碼:
ModelBinders.Binders.Add(typeof(QueryDescriptor), new QueryDescriptorBinder());
假如我們有如下的EF模型:
我們來實現一個列表,對Product的Name進行篩選。這時候,可以使用QueryTextbox擴展方法來生成查詢字段,View的代碼如下:
<div class="container"> <form class=".form-search"> @Html.QueryTextbox("Name", "Product Name", QueryOperator.CONTAINS) <input type="submit" value="Search" class="btn"/> </form> </div> <div class="row"> <div class="span12 offset2 "> <table class="table table-striped"> <thead> <tr> <td>ID</td> <td>Category</td> <td>Name</td> <td>Price</td> <td>Description</td> </tr> </thead> <tbody> @foreach (var p in @Model) { <tr> <td>@p.Id</td> <td>@p.Category.Name</td> <td>@p.Name</td> <td>@p.Price</td> <td>@p.Description</td> </tr> } </tbody> </table> </div> </div>
上半部分是一個form,里面有一個QueryTextBox,下半部分就是一個列表,非常簡單。看對應的Action:
public ActionResult Index(QueryDescriptor descriptor) { ShopContainer ctx = new ShopContainer(); var result = ctx.Products.Query(descriptor); return View("Product",result); }
由於Model Binder的存在,Action會從頁面獲得QueryDescriptor的信息。這樣一個篩選頁面就做好了。如果客戶說,我還要增加對種類名稱和價格范圍的篩選,那需要改什么地方?只需要在View的form中添加幾個QueryTextbox就可以了。
<form class=".form-search"> @Html.QueryTextbox("Name", "Product Name", QueryOperator.CONTAINS) @Html.QueryTextbox("Category.Name", "Product Category", QueryOperator.CONTAINS) @Html.QueryTextbox("Price.1", "Price Between", QueryOperator.GREATEROREQUAL,"decimal") @Html.QueryTextbox("Price.2", "", QueryOperator.LESSOREQUAL,"decimal") <input type="submit" value="Search" class="btn"/> </form>
注意,Price出現了兩次,需要加上數字后綴區分一下就可以,如果不是string類型,加上類型的說明,這樣就OK。Action方法是不需要有任何改動的。
如果客戶說這個要分頁怎么辦? 分頁需要稍微多些兩行代碼,但是也只需要2分鍾就足夠,先看Action方法:
public ActionResult Product(QueryDescriptor descriptor) { descriptor.PageSize = 5; descriptor.OrderBy = new OrderByClause { Key = "Id", Order = OrderSequence.ASC }; ShopContainer ctx = new ShopContainer(); int pageCount; var result = ctx.Products.Query(descriptor, out pageCount); Pager pager = new Pager(pageCount, descriptor); ViewBag.Pager = pager; return View("Index",result); }
首先指定一頁顯示的數量,因為要分頁,必須要有排序信息,這里是根據Id,升序排列。接下來還是調用Query方法獲得數據,注意,這里是Query的一個重載的方法,能夠返回總共有多少頁,這一般是分頁控件需要的信息。接下來實例化一個Pager對象,這個Pager是一個分頁器,包含在Dynamic Query中,如果你想用其他第三方的分頁器,也是可以的。Pager需要的額外信息就是總頁數,把這個Pager放到ViewBag上面,然后Action的工作就完成了。View上面也不想要任何改變,如果你想加上分頁鏈接的話,只需要一行代碼:
@Html.QueryPager((Pager)ViewBag.Pager);
看看效果:
項目主頁和源代碼在:http://dynamicquery.codeplex.com/ 上面有一個樣例程序,更多文檔在完善中。
這個項目才開始沒多久,還有很多細節需要完善,主要是樣式、支持更多的控件,比如checkbox,dropdownlist等。以后慢慢補充。