(原創)通用查詢實現方案(可用於DDD)[附源碼] -- 簡介

 

【聲明】
寫做不易,轉載請註明出處( http://www.cnblogs.com/wiseant/p/3985353.html)。
 
【系列文章】
通用查詢實現方案(可用於DDD)[附源碼] -- 代碼解讀
 
【前言】
最近一直在關注和學習DDD,園子裏大牛們的博文也看了很多,如 dax.netnetfocus老趙田園裏的蟋蟀深藍醫生冰麟輕武,不一一列舉,感謝大家的無私分享。
關於DDD我一直也是看得多,作得少,能夠說未入門,天然也寫不出什麼東西。可是我對DDD相關的思考倒並很多,特別是對於查詢好像沒有一個很優雅的實現方案。
上個月dax.net寫了一篇關於統一查詢實現思路的文章: 一種通用查詢語言的定義與實踐,給了我動力和靈感,感謝dax.net兄。
緣於這個契機,我開始嘗試實現一個通用查詢方案,最近一直在作NC5.7的二次開發,只能用業餘時間來搞這個,通過半個多月的努力,實現了一個 自我感受還算優雅的通用查詢方案。但一我的的智慧始終是有限的,並且本人也是半路出家,底子比較薄,遂將項目開源出來,一方面是但願能夠幫助到初學者,以 便更多人來檢驗此查詢方案的通用性和可靠性,另外主要也但願獲得園子裏大牛們的指點,使此查詢方案獲得更一步發展和提煉而日趨成熟。
PS:順便吐槽一下NC-UAP5.7開發平臺,說文明點就是太難用了,說得不文明點那就是一坨屎!用友還枉稱什麼國內一流的軟件供應商
 
【功能】
之前一直從事基於數據庫的企業應用開發,用戶常常須要在客戶端靈活的進行數據檢索,我採用的是拼SQL的方式,雖然有各類弊端,但好處是至關自 由。到了DDD發現拼SQL基本上是行不通了,不少開發人員都採用在倉儲中定義相似FindByKey()、FindByName()、 FindByNameAndAge()這樣的方法來提供數據檢索,每當用戶須要多一種條件組合時都要改不少地方,大費周章。我但願這個查詢對象定義能夠有 代碼檢查功能,條件組合靈活多樣,能夠在先後臺以前傳輸,而且一次定義就能夠被編譯爲不一樣的目標格式,如SQL、Lambda表達式。
下面分別介紹這些功能如何使用。
1、直觀的定義查詢對象
1)支持以Linq表達式定位屬性
 
如上圖,首先咱們以傳統的方式定義了兩個字段,緊接着咱們以Linq表達式方式直接獲取了這兩個字段,通過用單元測試方法來驗證兩種方式所獲取的字段是等同的。
顯然第二種方式更優雅簡潔,更重要的是當字段更名或更改了字段類型時,第一種方式編譯依然能夠經過,而運行時將不可避免的產生異常,致使程序維 護難度增長;而第二種方式在遇到字段更名時將在編譯報告錯誤,若是你使用ReSharp之類工具的代碼重構功能的話,更是會自動作出相應修改。
鑑於以上緣由,我將在後續示例代碼中僅採用第二種方式書寫,我推薦你也使用第二種方式。
2)支持計算公式
若是僅僅支持對字段的比較顯然是不夠的,我但願平常80%~90%對查詢的需求能夠經過此通用查詢方案來解決。
顯然支持計算公式和括號優先級是很是必要的,咱們經過下面這張代碼貼圖能夠了解一個使用算式的簡單例子。
 
3)支持導航屬性條件
還有一個頗有必要支持的功能是對導航屬性的條件查詢,在關係數據庫中即爲關聯表條件查詢。
第一張代碼貼圖中其實已經有相關代碼展現了,我將該代碼行單獨剪切出來放在下圖,圖中代碼行定義一個"訂單.供應商.編碼"字段,後面的查詢示例中會有更詳細的代碼展現。
 
4)支持括號優先級
上面已經提到過支持括號優先級的必要性,下面的代碼將展現定義一個加法運算,並將這個算式用括號括起來,保證此算式優先計算。
 
2、查詢對象可序列化及反序列化
不管是DDD仍是傳統的三層架構都涉及到分層傳輸的問題,那麼咱們的通用查詢方案也必須提供序列化及反序列化的功能,經過下面的代碼貼圖能夠看到對XML格式文本的序列化支持,因爲時間所限我將在稍後提供對json格式的序列化支持。
 
3、定義好查詢後可同時編譯爲SQL或lambda表達式
若是前面的代碼你能讀懂(其實不難),那麼下面的代碼有很詳細的註釋,相信對你也沒什麼難度,爲節省時間,我就不費口舌了。
 1         [Fact]
 2         public void TestQuery3()
 3         {
 4             var helper = new TypeInfoHelper<Order>();
 5             //得到訂單集合中的第一張訂單,用於以後的單元測試驗證
 6             var findOrder = orders.First();
 7             //定義一個查詢,條件爲訂單主鍵等於前面獲取的訂單對象的主鍵值
 8             var query = new Query(typeof(Order))
 9             {
10                 RootExpression = helper.GetProperty(p => p.BillId).EqualTo(findOrder.BillId)
11             };
12             /*第一種編譯方式*/
13             //定義一個生成Lambda表達式的編譯器對象
14             var compiler = new LambdaExpressionCompiler<Order>(query);
15             //將查詢對象編譯爲Linq表達式
16             var expression = compiler.Compile();
17             //對訂單集合執行linq查詢
18             var items = orders.Where(expression.Compile()).ToList();
19             //驗證查詢結果中僅包含一張訂單,且訂單編號與參照訂單的編號相同
20             Assert.Equal(items.Count, 1);
21             Assert.Equal(items.First().BillNo, findOrder.BillNo);
22 
23             /*第二種編譯方式*/
24             //定義一個生成SQL腳本的編譯器對象
25             var sqlCompiler = new SqlWhereClauseCompiler(query) { GenSelectPart = true };
26             //將查詢對象編譯爲SQL腳本
27             var sql = sqlCompiler.Compile();
28             Console.WriteLine(sql);
29             //執行sql腳本
30             var reader = dbHelper.Read(sql, sqlCompiler.ParameterValues);
31             //驗證返回的結果中只有一條記錄,且訂單編號與參照訂單的編號相同
32             Assert.Equal(reader.Read(), true);
33             Assert.Equal(findOrder.BillNo, reader["BillNo"]);
34             Assert.Equal(reader.Read(), false);//驗證只能執行讀取一次操做
35         }

 

【用途】
1.適用於DDD中聚合根的查詢
2.適用於普通的面向數據表及視圖的查詢
3.能夠很方便的實現可重用的面向最終用戶自助查詢(條件組合徹底由用戶來指定)
受時間所限,這部份內容若有必要後續再補充吧,寫文章確實太費時間。
 
我不太擅長寫做,致使你們看完博文後,可能仍是很難明白此通用查詢具體能作什麼,補充一個稍複雜的應用示例吧。
下面的定義的查詢對象編譯後將生成SQL腳本(爲了加強閱讀性,我添加了換行和縮進):
SELECT * FROM xzcOrder AS o
  WHERE (o.BillNo LIKE 'PO%' OR  NOT o.BillDate >= '2014-1-1 0:00:00' )
  AND EXISTS(SELECT 1 FROM supplier WHERE supplier.Id=o.SupplierId AND supplier.Code LIKE '%X%')
  AND o.Invalid = 0
  AND EXISTS(SELECT 1 FROM Items AS d WHERE d.BillId=o.BillId AND EXISTS(SELECT 1 FROM Product AS p WHERE p.Id=d.ProductId AND p.Unit = ''))
  AND EXISTS(SELECT 1 FROM Items AS d WHERE d.BillId=o.BillId AND d.Qty < o.TotalQty)

相應的C#代碼html

 1             var helper = new TypeInfoHelper<Order>();
 2             var query = new Query(typeof(Order))
 3             {
 4                 RootExpression = helper.GetProperty(p => p.BillNo).StartsWith("PO") //單號以PO打頭
 5                     .Or(helper.GetProperty(p => p.BillDate).GreaterThanOrEqualTo(new DateTime(2014, 1, 1)).Not())//訂單日期 不 大於等於 2014-1-1
 6                     .Unitary() //上面兩個條件設爲獨立總體,即用()號包圍
 7                     .And(helper.GetProperty(p => p.Supplier.Code).Contains("X"))//供應商編碼中包含字符"X"
 8                     .And(helper.GetProperty(p => p.Invalid).EqualTo(false)) //訂單失效標誌爲否
 9                     .And(helper.GetProperty(p => p.Items.FirstOrDefault().Product.Unit).EqualTo("")) //訂購產品的計量單位爲"部"
10                     .And(
11                         //訂單明細中各項訂購數量小於訂單總訂購數量,僅爲測試,無實際意義
12                         helper.GetProperty(p => p.Items.FirstOrDefault().Qty)
13                             .LessThan(helper.GetProperty(p => p.TotalQty)))
14             };
15             List<Order> items;
16             var sqlCompiler = new SqlWhereClauseCompiler(query) { GenSelectPart = true };
17             var sql = sqlCompiler.Compile();
18             Console.WriteLine(sql);
 
【源碼下載】
不少人感興的可能都是有沒有提供源碼下載,特別是初學者;我想說的有源碼拿在手上當然是有一種踏實的感受,可是源碼以外的設計思路更重要。
這個通用查詢方案的源碼並無太多技術含量,設計思路也並不高大上,由於我一貫都是個實用主義者。因此你徹底有可能在瞭解個人思路以後,在此基 礎上提煉昇華,用更好的技術來實現你本身的通用查詢方案也絕非難事,我此舉開源僅爲拋磚引玉,聽取你們意見,博文開頭也已經做出說明了。
源碼我就不搞什麼回貼發郵件了,可是源碼還遠未成熟,還有一些代碼重構工做要作,另外也但願在獲得園子裏大牛們指點進一步完善代碼後再上傳到源碼託管服務器上,因此這裏暫時就用百度雲盤分享給你們吧。
連接: http://pan.baidu.com/s/1bn7y7oF 密碼:p2dg
最近更新下載
連接: http://pan.baidu.com/s/1o64u8UQ 密碼:qo6i
 
代碼已託管到開源中國,地址: http://git.oschina.net/xant77/Xant.Querier
 
【關於編譯項目】
開發環境:Visual Studio 2013
第三方庫:xunit、SQLite
將源碼解壓到磁盤上,打開解決方案,在"解決方案資源管理器"中顯示有兩個第三方庫存在衝突
 
試過啓用NuGet程序包還原,還原後編譯能經過,可是執行時會報告缺乏SQLite.Interop.dll文件,不知道有沒有更好的辦法, 一個可行的解決辦法是:在Tester項目上點右鍵,選擇"管理NuGet程序包...",將已安裝的2個包刪除,而後再聯機搜索這兩個包安裝上去。
注意搜索sqlite時會出現不少相關的包,請選擇下圖這個
 
以後即可以編譯經過了,Tester項目能夠編譯後直接運行或是在"測試資源管理器"中點擊"所有運行"查看測試方法執行結果。
 
若是看不到上圖的畫面,請先安裝 xUnit.net runner for Visual Studio 2012 and 2013
建議你先閱讀XunitTest.cs文件中的代碼,能夠對整個項目有一個直觀瞭解。
 
【寫在最後】
博文除了圖片之外絕大部分文字內容都是我在上下班乘坐地鐵或公交時在手機上完成的,寫做不昜,若是對你還有點用處或啓發的話,望不吝點 ,謝謝!
相關文章
相關標籤/搜索