一行程式碼呼叫實現帶欄位選取+條件判斷+排序+分頁功能的增強ORM框架
問題:3行程式碼
PDF.NET 是一個開源的資料開發框架,它的特點是簡單、輕量、快速,易上手,而且是一個註釋完善的國產開發框架,受到不少朋友的歡迎,也在我們公司的專案中多次使用。但是,PDF.NET比起EF來,仍然有很大的劣勢,主要就是用起來沒有EF簡單,這個問題飽受廣大朋友的批評,但我很感謝這些朋友,他們的批評才是框架進步的動力,為此,之前我發表了《來一點反射和Emit,讓ORM的使用極度簡化》 這篇文章,使得不再需要定義實體類,只需要有介面即可訪問資料庫:
原文的程式碼:
static void TestDynamicEntity() { ITable_User user = EntityBuilder.CreateEntity<ITable_User>(); //如果介面的名稱不是"ITableName" 這樣的格式,那麼需要呼叫 MapNewTableName方法指定 //((EntityBase)user).MapNewTableName("Table_User"); OQL qUser = OQL.From((EntityBase)user).Select(user.UID, user.Name, user.Sex).END; List<ITable_User> users = EntityQuery.QueryList<ITable_User>(qUser, MyDB.Instance); }
這段程式花了3行程式碼來做一個查詢,還是有點繁瑣。如果不是這種介面型別的動態實體類,可以通過下面的擴充套件方法來簡化查詢:
public static List<T> ToList<T>(this OQL q) where T:EntityBase,new() { return EntityQuery<T>.QueryList(q); } public static OQL From<T>() where T : EntityBase, new() { T entity = new T(); return OQL.From(entity); }
有了這2個“擴充套件”方法,我們的查詢可以一行完成了:
List<User> users=OQL.From<User>.ToList<User>();
等同於
List<User> users=OQL.From<User>.Select().END.ToList<User>();
但這樣的寫法沒法選擇需要的列,如果要附加查詢條件,在V5.0之前,還得這樣做:
User user=new User(){UserName="zhangsan",Password="abc."} List<User> users=OQL.From(user) .Select(user.ID,user.UserName,user.Password) .Where(user.UserName,user.Password) .END .ToList<User>();
這樣查詢還得需要2行程式碼,而且沒有利用上泛型的優勢,最後的ToList還得指定型別User ,這樣寫仍然不優雅。
曙光:V5版本
PDF.NET Ver 5.0 在經過了脫胎換骨般的重構後,OQL增加了大量特性,OQL方法支援Lambda表示式語法,支援泛型,我們前面的程式碼有望得到簡化:
Users user = new Users();
var userList = OQL.From(user)
.Select(user.UserName, user.ID)
.Where<Users>((cmp,u)=>cmp.Compare(u.ID,">",100)
//.OrderBy(p => p.Desc(user.UserName).Asc(user.ID)) //2種排序方式
.OrderBy<Users>((o,u) => { o.Desc(u.UserName); })
.END
.ToList<Users>();
OQL V5.0.0的寫法還得藉助Users 的物件例項來選取欄位,或者動態排序,仍然多了一行程式碼:
Users user = new Users();
這一行程式碼儘管能夠給我在Where條件相等比較上代來便利,直接將條件值傳入進去,但不管怎麼說,一個查詢還是讓我多寫了一行程式碼,沒有做到EF那樣,一行程式碼解決問題。這多出來的一行程式碼,讓PDF.NET的使用者朋友很不滿意的,主要就是,EF都可以一行查詢出來,PDF.NET為什麼不行?太麻煩了!
我常常在想,為什麼“客戶”這麼難以伺候,就多寫了一行實體類的例項化的程式碼,這都顯得麻煩麼?還有各種好處呢,PDF.NET基於實體類的例項呼叫特性,構築起了OQL支援複雜查詢的特性(參見 《ORM查詢語言(OQL)簡介--高階篇(續):廬山真貌》 ),SQL能夠支援的,OQL基本上都能夠支援了。
但是,我說的好處似乎很難讓我的“客戶”朋友門滿意,還是那句話:
EF都可以做到,PDF.NET為什麼做不到?
我的理想是,EF可以做到的,PDF.NET 也儘量做到,EF做不到的,PDF.NET 要做到!
否則,在眾多ORM框架的圍攻下,PDF.NET很難生存下去。EF都開源了,說明做ORM競爭太激烈了,沒有特色,更本沒法生存。
在考慮了幾天之後,我認為基於現在PDF.NET V5.0的新版核心,有可能真正實現一行程式碼進行資料查詢的。 問題所在也很清楚了,就是那個實體類的申明語句讓我很尷尬:
Users user = new Users();
只要幹掉它,我就成功了! 而這,完全可以在下面的方法中做“手腳”實現:
public static OQL From<T>() where T : EntityBase,new()
{
T entity=new T();
return new OQL(entity);
}
很簡單嘛,這樣就可以一行程式碼實現查詢了:
var userList = OQL.From<Users>()
.Select()
.Where<Users>((cmp,u)=>cmp.Compare(u.ID,">",100)
.OrderBy<Users>((o,u) => { o.Desc(u.UserName); })
.END
.ToList<Users>();
目的達到了,原來只要肯想法,辦法還是很簡單的,心中一陣竊喜:)
精簡:讓使用者再懶一點
過了一會兒,再反覆看看上面這一行程式碼,發現了幾個問題:
- Select 方法沒法指定要選擇的表字段;
- Where,OrderBy,ToList 都需要指定泛型的具體型別,既然From<Users> 最開始已經指定過了,那麼後面的方法再指定<Users>就有點冗餘。
為了讓框架的“客戶”再少敲幾個字元,我決定構造一個OQL的泛型類,這樣它相關的操作方法就不需要反複製定具體型別了,同時想法解決問題1。於是,這個新類如下定義:
public class GOQL<T> where T:class
{
protected internal OQL currentOQL;
private T currentEntity;
public delegate object[] SelectFieldFunc(T s);
public GOQL1<T> Select(SelectFieldFunc func)
{
return new GOQL1<T>(this, currentOQL.Select(func(currentEntity)));
}
/* 其它方法略 */
}
有了SelectFieldFunc 這個委託,就可以給Select 方法使用了,選擇指定的欄位資料:
currentOQL.Select(func(currentEntity))
接下來,按照OQL的設計思路,進行SQL 語句分層 設計,目前只打算支援Where 和OrderBy字句,所以需要定義下面的子類:
public class GOQL1<T> : GOQL2<T> where T : class
{
public GOQL2<T> Where(OQLCompareFunc<T> func)
{}
}
public class GOQL2<T> where T : class
{
public GOQL<T> OrderBy(OQLOrderAction<T> orderAct)
{}
}
由於SQL語句不一定需要Where子句,可以直接在 Select 子句後跟Order By 子句,所以讓GOQL1<T>繼承 GOQL2<T> 。
OK,經過這樣的設計,整個GOQL程式碼只有95行程式碼,沒錯,只有95行,目前還沒有寫註釋,詳細程式碼請展開看下面的內容:
1 using System;
2 using System.Collections.Generic;
3 using PWMIS.DataProvider.Data;
4 using PWMIS.DataProvider.Adapter;
5
6 namespace PWMIS.DataMap.Entity
7 {
8 public class GOQL<T> where T:class
9 {
10 protected internal OQL currentOQL;
11 private T currentEntity;
12 public delegate object[] SelectFieldFunc(T s);
13
14 public GOQL(OQL oql,T entity)
15 {
16 this.currentOQL = oql;
17 this.currentEntity = entity;
18 }
19 public GOQL1<T> Select()
20 {
21 return new GOQL1<T>(this, currentOQL.Select());
22 }
23 public GOQL1<T> Select(SelectFieldFunc func)
24 {
25 return new GOQL1<T>(this, currentOQL.Select(func(currentEntity)));
26 }
27 public GOQL<T> Limit(int pageSize, int pageNumber)
28 {
29 this.currentOQL.Limit(pageSize, pageNumber);
30 return this;
31 }
32 public GOQL<T> Print(out string sqlInfo)
33 {
34 sqlInfo = string.Format("SQL:{0}rn{1}",currentOQL.ToString(), currentOQL.PrintParameterInfo());
35 return this;
36 }
37 public List<T> ToList(AdoHelper db )
38 {
39 return EntityQuery.QueryList<T>(this.currentOQL, db);
40 }
41 public List<T> ToList()
42 {
43 return ToList(MyDB.Instance);
44 }
45 public T ToObject(AdoHelper db)
46 {
47 return EntityQuery.QueryObject<T>(this.currentOQL, db);
48 }
49 public T ToObject()
50 {
51 return ToObject(MyDB.Instance);
52 }
53 public override string ToString()
54 {
55 return currentOQL.ToString();
56 }
57 }
58
59 public class GOQL1<T> : GOQL2<T> where T : class
60 {
61 private GOQL<T> currentGOQL;
62 private OQL1 currentOQL1;
63
64 public GOQL1(GOQL<T> gq,OQL1 q1):base(gq)
65 {
66 this.currentGOQL = gq;
67 this.currentOQL1 = q1;
68 }
69
70 public GOQL2<T> Where(OQLCompareFunc<T> func)
71 {
72 this.currentOQL1.Where(func);
73 return new GOQL2<T>(currentGOQL);
74 }
75 }
76
77 public class GOQL2<T> where T : class
78 {
79 private GOQL<T> currentGOQL;
80
81 public GOQL2(GOQL<T> gq)
82 {
83 this.currentGOQL = gq;
84 }
85 public GOQL<T> OrderBy(OQLOrderAction<T> orderAct)
86 {
87 OQL4 currentOQL4 = new OQL4(this.currentGOQL.currentOQL).OrderBy<T>(orderAct);
88 return this.currentGOQL;
89 }
90 public GOQL<T> END
91 {
92 get { return this.currentGOQL; }
93 }
94 }
95 }
成功:一行程式碼的真相
為了讓大家更清楚GOQL的結構和它與PDF.NET框架其它部分的關係,請看下面的類圖:
-類圖-
最後,我們就可以寫一個真正的測試程式碼了: 95行原始碼,一行程式碼呼叫實現帶欄位選取+條件判斷+排序+分頁功能的增強ORM框架
static void TestGOQL()
{
string sqlInfo="";
//下面使用 ITable_User 或者 Table_User均可
List<ITable_User> userList =
OQL.FromObject<ITable_User>()
//.Select()
.Select(s => new object[] { s.UID, s.Name, s.Sex }) //僅選取3個欄位
.Where((cmp, user) => cmp.Property(user.UID) < 100)
.OrderBy((o,user)=>o.Asc(user.UID))
.Limit(5, 1) //限制5條記錄每頁,取第一頁
.Print(out sqlInfo)
.ToList();
Console.WriteLine(sqlInfo);
Console.WriteLine("User List item count:{0}",userList.Count);
}
這次新增了 OQL.FromObject<T>() 方法,型別T即可以是一個普通介面,也可以是一個PDF.NET的實體類。
有圖有真相,下面是這個測試程式的輸出截圖:
-截圖-
收工,PDF.NET 順利實現一行程式碼查詢資料的功能,除了Where 條件的複雜寫法不那麼優美,總體上GOQL,OQL可以媲美EF了!
注意:GOQL功能,在PDF.NET框架的Ver 5.0.1 版本支援,之前的https://pwmis.codeplex.com/releases/view/104043 PDF.NET_V5.0Beta_20130807 不支援,要獲取框架的最新原始碼,請加入本框架的官方QQ群,詳細聯絡資訊請看框架官網 http://www.pwmis.com/sqlmap
最後總結下PDF.NET ORM 各個類的使用場景:
- GOQL :解決單實體類的R(Read);
- OQL+EntityQuery<T>: 解決單實體類的CRUD;
- OQL+EntityContainer: 解決多實體類的R
-----分界線----------------
感謝廣大PDF.NET的會員和使用者朋友一直以來的支援,你的批評是我們進步的力量!歡迎加入框架的開源專案。