本文是在Azure Table storage 基本用法一文的基礎上,介紹如何自定義 Azure Table storage 的查詢過濾條件。如果您還不太清楚 Azure Table storage 的基本用法,請先移步前文。
讓我們回到前文中提到的一個問題,如何過濾出 MyLogTable 表中某一天產生的所有日志?在進入細節之前,我們先來回顧一下 MyLogTable 類的設計:
internal class MyLogEntity : TableEntity { public MyLogEntity() { } public MyLogEntity(string pkey, string rkey) { this.PartitionKey = pkey; this.RowKey = rkey; } //… }
其中,PartitionKey 用來存放產生日志的年份和月份(例如201607表示2016年7月),RowKey 用來存放產生日志的天和時分秒毫秒(例如160934248492表示16號9點34分…)。
在我們設計的 MyLogTable 中,天信息保存在 RowKey 的前兩位。我們要做的就是過濾 RowKey 的前兩位,也就是找到所有 RowKey 以”xx”開頭的記錄。這在字符串操作中稱為 StartsWith。遺憾的是現有 Table storage 的接口中沒有提供這種功能的方法,因此我們需要我們自己實現它(還好 TableQuery 的實現支持我們去擴展它)。
本文將通過實現 StartsWith 過濾條件說明如何自定義 Azure Table storage 的查詢過濾條件。
TableQuery類
TableQuery 是本文的主角,它代表了 Table 上的一個查詢。基本用法是使用查詢條件構建一個 TableQuery 類的實例,然后把這個實例作為參數傳遞給 CloudTable 的 ExecuteQuery 方法:
TableQuery<MyLogEntity> query = new TableQuery<MyLogEntity>().Where( TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "201607")); var queryResult = logTable.ExecuteQuery(query);
我們還可以使用 TableQuery 的靜態方法 CombineFilters 構建自定義的查詢條件。
比如我們要查詢 PartitionKey 等于 "201607" 并且 RowKey 等于"161148372454"的記錄:
TableQuery.CombineFilters( TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "201607"), TableOperators.And, TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, "161148372454"));
此時函數的返回結果為: ” (PartitionKey eq '201607') and (RowKey eq '161148372454')”。
然后把這個過濾字符串送給 query.Where 函數做參數,或者設置給 query.FilterString 屬性,就可以完成過濾功能了。
CombineFilters 方法可愛的地方在于我們可以不斷的使用它來合并查詢條件,直到滿意為止!
接下來我們一起看看 StartsWith 過濾條件的實現過程。
比較字符串
如何從一些字符串中找出以某個子串開頭的字符串呢?我們可以從字符串的比較入手。
比如字符串具有下面的關系:
“abc” == “abc” < “abd”
“abc” < “abca” < “abd”
“abc” < “abcz” < “abd”
由上面的大小關系我們可以得出結論:以”abc”開頭的字符串必定大于或等于”abc”且小于”abd”。OK,這就是我們構建 StartsWith 過濾條件的理論基礎。
構建StartsWith過濾條件
接下來我們通過 TableQuery.CombineFilters 方法構建 StartsWith 過濾條件:
string startsWithCondition = TableQuery.CombineFilters( TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThanOrEqual, "abc"), TableOperators.And, TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.LessThan, "abd") );
TableQuery.CombineFilters 方法的返回值是一個字符串。運行上面的代碼我們會得到字符串:
“(RowKey ge 'abc') and (RowKey lt 'abd')”
我們完全可以手動拼出這樣的字符串,但我相信沒有程序員愿意這么做。
那么,我們需要繼續完善上面的方法:
string startStr = "abc"; int endIndex = startStr.Length - 1; Char lastChar = startStr[endIndex]; //找到比字符'c'大的那個字符。 Char afterLastChar = (char)(lastChar + 1); //拼出字符串 "abd" string endStr = startStr.Substring(0, endIndex) + afterLastChar; string startsWithCondition = TableQuery.CombineFilters( TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThanOrEqual, startStr), TableOperators.And, TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.LessThan, endStr) );
組合更多過濾條件
在前面構建 StartsWith 過濾條件時,我們已經使用 TableQuery.CombineFilters 方法組合了不同的過濾條件。遺憾的是 TableQuery.CombineFilters 方法只有兩個參數的重載,我們不能添加更多的 TableOperators 操作。
但我們可以繼續調用 TableQuery.CombineFilters 方法來組合上一個結果和新的條件。比如我們要把 Startswith 過濾條件和 PartitionKey 過濾條件組合起來就可以這么做:
string filterCondition = TableQuery.CombineFilters( TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "201607"), TableOperators.And, "(RowKey ge 'abc') and (RowKey lt 'abd')" );
運行上面的代碼,生成的結果為:
(PartitionKey eq '201607') and ((RowKey ge 'abc') and (RowKey lt 'abd'))
到這來就很清楚了,TableQuery.CombineFilters 方法的主要工作,就是把過濾條件組織成查詢引擎能夠識別的字符串。
因而我們可以通過不斷的疊加,來生成很復雜的過濾條件。
封裝StartsWith過濾條件
下面我們把 StartsWith 的邏輯封裝到 StartsWithByRowKey 類型中,以下是完整的代碼:
public class MyLogEntity : TableEntity { public MyLogEntity() { } public MyLogEntity(string pkey, string rkey) { this.PartitionKey = pkey; this.RowKey = rkey; } public DateTime LogDate { get; set; } public string LogMessage { get; set; } public string ErrorType { get; set; } } public class StartsWithByRowKey : IQuery<CloudTable, List<MyLogEntity>> { private readonly string partitionKey; private readonly string startsWithString; internal StartsWithByRowKey(string partitionKey, string startsWithString) { this.partitionKey = partitionKey; this.startsWithString = startsWithString; } public List<MyLogEntity> Execute(CloudTable coludTable) { var query = new TableQuery<MyLogEntity>(); int endIndex = startsWithString.Length - 1; Char lastChar = startsWithString[endIndex]; Char afterLastChar = (char)(lastChar + 1); string endStr = startsWithString.Substring(0, endIndex) + afterLastChar; string startsWithCondition = TableQuery.CombineFilters( TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.GreaterThanOrEqual, startsWithString), TableOperators.And, TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.LessThan, endStr) ); string filterCondition = TableQuery.CombineFilters( TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey), TableOperators.And, startsWithCondition ); var entities = coludTable.ExecuteQuery(query.Where(filterCondition)); return entities.ToList(); } } public interface IQuery<in TModel, out TResult> { TResult Execute(TModel model); }
應用StartsWith的實例
現在查詢 PartitionKey 為”201607”,RowKey 以”16”開頭的記錄可以這么寫:
StartsWithByRowKey myStartsWithQuery = new StartsWithByRowKey("201607", "16"); List<MyLogEntity> result = myStartsWithQuery.Execute(logTable);
代碼簡潔了很多,讀起來也更清晰了(您還可以動手給 PartitionKey 添加同樣的功能)!
小結一下,本文簡單的介紹了 TableQuery 類型,然后比較詳細的說明了 StartsWith 過濾條件的思路及實現。主要是想通過 StartsWith 的實現來說明如何利用現有的類型和方法來實現自定義查詢的過濾條件。對于有類似需求的朋友,希望能起到拋磚引玉的作用。
相關閱讀:
Azure Blob Storage 基本用法 -- Azure Storage 之 Blob
Azure Queue Storage 基本用法 -- Azure Storage 之 Queue
Azure File Storage 基本用法 -- Azure Storage 之 File
Azure Table storage 基本用法 -- Azure Storage 之 Table
文章列表