本文是在 《Azure 基礎:Table storage》 一文的基礎上介紹如何自定義 Azure Table storage 的查詢過濾條件。若是您還不太清楚 Azure Table storage 的基本用法,請先移步前文。html
讓咱們回到前文中提到的一個問題,如何過濾出 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 的查詢過濾條件。this
TableQuery 是本文的主角,它表明了某個表上的一個查詢。基本用法是使用查詢條件構建一個 TableQuery 類的實例,而後把這個實例做爲參數傳遞給 CloudTable 的ExecuteQuery 方法:spa
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 過濾條件的實現過程。code
如何從一些字符串中找出以某個子串開頭的字符串呢?咱們能夠從字符串的比較入手。
好比字符串具備下面的關係:htm
「abc」 == 「abc」 < 「abd」 「abc」 < 「abca」 < 「abd」 「abc」 < 「abcz」 < 「abd」
由上面的大小關係咱們能夠得出結論:以"abc"開頭的字符串一定大於或等於"abc"且小於"abd"。OK,這就是咱們構建 StartsWith 過濾條件的理論基礎。blog
接下來咱們經過 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 的邏輯封裝到 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); }
如今查詢 PartitionKey 爲"201607",RowKey 以"16"開頭的記錄能夠這麼寫:
StartsWithByRowKey myStartsWithQuery = new StartsWithByRowKey("201607", "16"); List<MyLogEntity> result = myStartsWithQuery.Execute(logTable);
代碼簡潔了不少,讀起來也更清晰了(您還能夠動手給 PartitionKey 也添加一樣的功能)!
本文簡單的介紹了 TableQuery 類型,而後比較詳細的說明了 StartsWith 過濾條件的思路及實現。主要是想經過 StartsWith 的實現來講明如何利用現有的類型及方法來實現自定義查詢的過濾條件。對於有相似需求的朋友,但願能起到拋磚引玉的做用。