本文是在 《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 的实现来讲明如何利用现有的类型及方法来实现自定义查询的过滤条件。对于有相似需求的朋友,但愿能起到抛砖引玉的做用。