問題: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}\r\n{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的會員和用戶朋友一直以來的支持,你的批評是我們進步的力量!歡迎加入框架的開源項目。