1. 程式人生 > 其它 >一行程式碼呼叫實現帶欄位選取+條件判斷+排序+分頁功能的增強ORM框架

一行程式碼呼叫實現帶欄位選取+條件判斷+排序+分頁功能的增強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>();

  目的達到了,原來只要肯想法,辦法還是很簡單的,心中一陣竊喜:)

精簡:讓使用者再懶一點

  過了一會兒,再反覆看看上面這一行程式碼,發現了幾個問題:

  1. Select 方法沒法指定要選擇的表字段;
  2. 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的會員和使用者朋友一直以來的支援,你的批評是我們進步的力量!歡迎加入框架的開源專案