前言
對於數據層的所有操作而言,查詢是最常用的,之前的文章中只開有Find的查詢接口,接口如下:
public DbResult Find(Dictionary<string, object> query);
由於只開放了一個Find接口,因此在業務開發過程當中,會出現如下缺點:
1、業務靠多個表數據組合展示的時候,需要業務開發人員多次使用Find方法查找不同的表來組合數據,偽代碼如下:
var orders = orderDb.FindByUserNo("no001"); var orderProdutcs = orderProductDb.FindByOrderIds( orders.Select(o => o.Id).ToArray()); //組合數據
2、一些相似的業務需要重復編寫代碼(其實只是多次相同的Find,但是結果數據可能不同),例如:pc端需要顯示訂單中5個產品的基礎信息,而移動端僅僅只顯示圖片而已
3、開發人員需要關注多表間的關系(其實使用Orm自動映射仍然要關注這些關系)
接下來對Find進行擴展,雖然上面的問題無法得到解決,但是好處卻更多。
普通查詢
對於某一個Find而言,比如:根據用戶號查詢用戶的訂單(FindByUserNo),請求數據結構如下:
var qs = new Dictionary<string, object> { { "userNo", "no001" } };
除了no001會發生變化以外,查詢數據的結構是不會發生變化的,不管no001查詢多少次,除非有編輯(CUD)的行為產生,不然結果集是不會有變化的。
因此可以將查詢請求數據為key,結果集為value,緩存起來,那么下次進行相同查詢的時候,便可以直接使用緩存中的數據了,Find代碼大致如下:
public DbResult Find(Dictionary<string, object> query) { var qsKey = JsonConvert.SerializeObject(query); if (cache.Exists(qsKey)) { //從緩存中獲取並返回 } var qsResult = this.FindByQuery(query); cache.Set( qsKey, JsonConvert.SerializeObject(query)); return new DbResult { Error = false, Data = qsResult }; }
每個相應的表都有相對應的緩存,那么只要在涉及相應表的方法中清除該表的所有緩存即可。
從以上的代碼看出,功能上還有可以優化的空間,可以將緩存的內容調整一下,只緩存結果集中的主鍵,代碼如下:
var qsKey = JsonConvert.SerializeObject(query); var isCached = cache.Exists(qsKey); if (isCached) { //從緩存中取出 string[] ids = null; query = new Dictionary<string, object>{ { "id", new Dictionary<string, object>{ { "$in", ids } } } }; } var qsResult = this.FindByQuery(query); if (!isCached) { PropertyInfo idProp = null; var index = 0; var ids = new string[(qsResult as ICollection).Count]; var enumator = (qsResult as IEnumerable).GetEnumerator(); while (enumator.MoveNext()) { if (idProp == null) idProp = enumator.Current.GetType().GetProperty("Id"); ids[index++] = idProp.GetValue(enumator.Current, null).ToString(); } cache.Set( qsKey, JsonConvert.SerializeObject(ids)); }
這樣可以減少緩存占用的空間,並且可以利用數據庫的索引查詢(如果使用緩存服務,那么索引指向的就是緩存中的Key),加速數據的獲取。
這里還可以再深入下去,那就是對不同表的緩存設定不同的緩存時間,對於訪問比較頻繁的表,提供較短的緩存時間,而較少訪問的則設定較長的時間,於是就可在項目內增加緩存的配置管理,后期可增加算法自行管理(提供基礎緩存時間,隨着訪問的頻繁遞增或遞減緩存時間)。
條件查詢
當條件查詢只涉及單個表的時候是非常簡單的,不需要做任何處理,直接使用Find即可,但是關聯到多個表的時候就會比較復雜了,例如:查詢訂單中會員名稱包含“李”且商品名包含“互聯網”的訂單數據。
一種方案是創建一個額外的表,然后將關聯到的字段存儲到該張額外表中去,那么Find的時候,將轉移調用該表方法,並且在相關編輯方法中對該表進行維護,也可以使用數據庫觸發器來進行維護,偽代碼如下:
public class Order : IDb { public DbResult Find(Dictionary<string, object> query) { IDb queryDb; if (查詢我的訂單) queryDb = new MyOrderQuery(); else queryDB = new ShopOrderQuery(); return queryDb.Find(query); } }
另一種方案是使用ElasticSearch或其他Lucene搜索服務來實現,跟上面的原理差不多,先提前將需要搜索的數據存儲到服務中去(編輯時同步維護),然后利用搜索引擎來查詢。
前者依賴於數據庫,后期數據庫只作為存儲介質,而不用來查詢時需要重構相關的代碼解除依賴。
數據權限
項目中存在數據權限的情況下,只要根據權限系統獲取操作用戶的權限條件,並添加到query上即可,比如:區域管理員只能獲取該區域的訂單數據,偽代碼如下:
public DbResult Find(Dictionary<string, object> query) { query = new Dictionary<string, object>{ { "$and", query } }; query.Add("$and", new Dictionary<string, object> { { "areaId", "廈門市" } }); return this.FindByQuery(query); }
那么開發人員調用Find接口的時候,獲取的數據就會經過過濾,這里也可以加上列數據的過濾,只要在結果集的基礎上再對列進行過濾,沒有權限的列可以直接過濾掉。
結束語
以上列舉了幾種數據層查詢的擴展方案,於是優點便體現出來了,數據層開發人員可以對不同的表使用不同的數據優化策略,不會影響到開發人員的業務開發,而業務開發人員只需要將所有的精力投入到業務中去,無需關注數據的處理。
文章就到這里,如果有什么疑問、建議、錯誤的話,請給我留言,謝謝。