前言
之前專案中做Elasticsearch相關開發的時候,雖然藉助了第三方的元件PlainElastic.Net,但是由於當時不熟悉用法,而選擇了自己拼接查詢語句。例如:
string queryGroup = "{\"query\": {\"match\": { \"roomid\": \"FRIEND_12686_10035\" }}}"; //關鍵字查詢 string queryKeyWord = "{ \"query\": {\"match_phrase\": {\"content\": {\"query\": \"" + keyword + "\",\"slop\": 0} } }}"; //是否圖片 查詢 string queryImg = "{ \"term\": {\"isimg\": true }}"; //是否包含檔案查詢 string queryFile = "{ \"term\": {\"isfile\": true }}"; //大於小於某個時間段查詢 string queryTimeRange = "{\"range\": {\"addtime\": { \"gt\": \"" + st + "\",\"lt\": \"" + et + "\" }} }"; //大於某個時間 string queryTimeRangeGt = "{\"range\": {\"addtime\": { \"gt\": \"" + st + "\"}} }"; //小於某個時間 string queryTimeRangeLt = "{\"range\": {\"addtime\": { \"lt\": \"" + et + "\" }} }";
後來慢慢看了下該元件的原始碼,想自己簡單實現一下,看看到底是什麼原理。
分析
先來一個簡單的小例子:PlainElastic中的demo示例:
string query = new QueryBuilder<Tweet>() // This will generate: .Query(q => q // { "query": { "term": { "User": "somebody" } } } .Term(t => t .Field(tweet=> tweet.User).Value("somebody") ) ).Build();
可以看到,構造查詢語句的時候很靈活,直接用表示式的形式,最後通過Build方法,生成相應的查詢語句,於是乎,照著葫蘆畫瓢,開始吧。其實,不管如何寫語句,最終都是對字串的拼接,生成最終的查詢語句。那我們就從最簡單的term查詢開始。比如一條查詢語句就是 {"query":{"term":{"name":"zhangsan"}}},這條語句的的意思,就是查詢 name 為zhangsan的資料。(需要讀者懂elasticsearch查詢語法)
先不考慮封裝,直接新建一個類,就叫 TermFilter,內部實現了 Term 的語言構造。由於需要鏈式呼叫,所以裡面的方法一般都返回 this 。
private Dictionary<string, object> _terms;
public TermFilter() { _terms = new Dictionary<string, object>(); } public TermFilter KeyValue(string key, object value) { _terms.Add(key, value); return this; }
如上述程式碼所示,當我們呼叫KeyValue方法時,傳入key和value,新增到內部的Dictionary中。然後重寫 ToString 方法,構造Term語句
private void Build() { StringBuilder str = new StringBuilder(); int i = 0; foreach (KeyValuePair<string, object> kv in _terms) { str.Append("{\"term\":{\"" + kv.Key + "\":" + kv.Value + "}}"); if (i >= 0 && i < _terms.Count - 1) { str.Append(","); } i++; } _condition = str.ToString(); } public override string ToString() { Build(); return base.ToString(); }
遍歷Dictionary,構造term語句,Term構造完之後,我們需要在外層加一個Query,由於Query是通用的,所以也需要提取出。於是乎,又多了一個類,叫做Filter,這個是查詢的入口,裡面有兩個方法,一個Bool方法,一個Query方法:
public Filter() { } // public Filter Bool(Func<BoolFilter, BoolFilter> boolFunc) { string boolFuncResult = boolFunc(new BoolFilter()).ToString(); _condition = "{\"query\":{\"filtered\":{\"filter\":{" + boolFuncResult + "}}}"; return this; } public Filter Query(Func<BoolFilter, BoolFilter> boolFunc) { string boolFuncResult = boolFunc(new BoolFilter()).ToString(); _condition = "{\"query\":" + boolFuncResult; return this; }
直接看Query方法,裡面的引數為 Func<BoolFilter,BoolFilter> boolFunc,好吧,這裡的Boss終於出場了,就是核心類,BoolFilter,它內部實現了,Must,Shoud,MustNot,And,Or,等方法。當然還有Term。我們直接看Term方法。
public BoolFilter Term(Func<TermFilter, TermFilter> termFunc) { PrapareCondition(); _condition += termFunc(new TermFilter()); return this; }
同理,因為鏈式呼叫,還是返回this,上述程式碼中由於termFunc 返回的是一個TermFilter物件,然後toString之後,就相當於追加 相應的Term語句。ToString方法最終也是返回 _condition欄位的值。好吧,我猜你越來越暈了,沒關係,我們在看最後一個類,就可以實戰了。
public QueryCreator Filter(Func<Filter, Filter> filter) { _condition += filter(new Filter()); return this; }
好了,到這裡,程式碼基本結束了。重新梳理一遍:
首先,最外層程式碼呼叫Filter方法,Filter實現了Query方法,Query內部傳入了BoolFilter引數,在呼叫Term方法,最終由TermFilter實現語句的構造,所以,外部最終程式碼呼叫起來是這樣的。
var result = creator.Filter(f => //Filter內部呼叫Query方法, f.Query(q => //Query呼叫BoolFilter的Term方法 q.Term(t => //BoolFilter又呼叫TermFilter的KeyValue方法 t.KeyValue("name", "zhangsan")))) .BuildBeautiful();//最後構造出我們想要的結果
如上圖,from和size是預設的。下面我們來個稍微複雜點的。比如在一個使用者表,想要查詢 使用者型別為 3 的且地區為 北京 的 並且滿足 年齡是 20 歲或者 工作經驗為1年 的使用者。並且根據 姓名倒敘排序,分頁取第3頁的20條資料。
首先分析一下,這裡我們要使用 and 查詢,and 裡面還包括 or 查詢。 構造語句如下:
var result = creator.Filter(f => f.Bool(b => //bool查詢 b.Must(m => //must,必須符合條件 m.And(a => //and查詢 a.Term(t => //構造查詢條件 t.KeyValue("type", 3).KeyValue("area", "北京")). Or(o => o.Term(t1 => t1.KeyValue("age", 20).KeyValue("experience", 1))))))).//or查詢,構造查詢條件 Page(3).//頁碼 Size(20).//每頁大小 OrderByDesc("name").//姓名倒敘排序 BuildBeautiful();//根據之前的條件建立查詢語句
哈哈,是不是有點繞啊,其實要想用這個,還是得會點ES查詢語法的,就好比SQL語句一樣,你不理解,是查不出東東的。用這種方式我們就能夠避免手動寫ES查詢語句了,只要經過程式碼簡單配製就好嘍,不過我還是乖乖用第三方元件吧,自己寫的太渣了。。。
看看構造成的語句:
好了就到這裡吧。當做自己研究的總結了。