項目演化系列--深入查詢


前言

  對於數據層的所有操作而言,查詢是最常用的,之前的文章中只開有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接口的時候,獲取的數據就會經過過濾,這里也可以加上列數據的過濾,只要在結果集的基礎上再對列進行過濾,沒有權限的列可以直接過濾掉。

結束語

  以上列舉了幾種數據層查詢的擴展方案,於是優點便體現出來了,數據層開發人員可以對不同的表使用不同的數據優化策略,不會影響到開發人員的業務開發,而業務開發人員只需要將所有的精力投入到業務中去,無需關注數據的處理。

  文章就到這里,如果有什么疑問、建議、錯誤的話,請給我留言,謝謝。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM