转载

无责任Windows Azure SDK .NET开发入门(六):使用Table Storage服务

Azure 表存储服务可存储大量结构化数据。该服务是一个 NoSQL 数据存储,接受来自 Azure 云内部和外部的通过验证的呼叫。Azure 表最适合存储结构化非关系型数据。表服务的常见用途包括:

  • 存储 TB 量级的结构化数据,能够为 Web 规模应用程序提供服务
  • 存储无需复杂联接、外键或存储过程,并且可以对其进行非规范化以实现快速访问的数据集
  • 使用聚集索引快速查询数据
  • 使用 OData 协议和 LINQ 查询以及 WCF 数据服务 .NET 库 访问数据

也就是说Azure 表存储服务适合存储数据量非常大的结构化非关系型数据。表服务包含以下组件:

无责任Windows Azure SDK .NET开发入门(六):使用Table Storage服务

在开发前你需要了解如下的概念:

  • URL 格式:代码使用此地址格式对帐户中的表 进行寻址: http://<storage account>.table.core.chinacloudapi.cn/<table>您可以直接使用此地址和 OData 协议来访问 Azure 表。
  • 存储帐户: 对 Azure 存储空间进行的所有访问都要 都要通过存储帐户完成。
  • 表:表是实体的集合。表不对实体强制实施架构,这意味着单个表可以包含具有不同属性集的实体。一个存储帐户可以包含的表数仅受存储帐户容量限制。
  • 实体:与数据库行类似,一个实体就是一组属性。一个实体的大小可达 1 MB。
  • 属性:属性是名称/值对。每个实体最多可包含 252 个用于存储数据的属性。每个实体还包含 3 个 系统属性,分别指定分区键、行键和时间戳。对具有相同分区键的实体的查询速度将更快,并且可以在原子操作中插入/更新这些实体。一个实体的行键是它在一个分区内的唯一标识符。

我们建立StorageTableController来测试Table Storage的服务,代码如下:

这个控制器有如下方法

  • Index
  • Create
  • Delete
  • Upload
  • List

一、列出当前存储中的Table

代码明确简单:

[Authorize] public class StorageTableController : Controller {  CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("Microsoft.WindowsAzure.Storage"));  CloudTableClient tableClient = null;  public StorageTableController()  {   tableClient = storageAccount.CreateCloudTableClient();  } } 

对应的View

@model IEnumerable<Microsoft.WindowsAzure.Storage.Table.CloudTable> @{ ViewBag.Title = "Index"; } <h2>Index</h2> <p> @Html.ActionLink("创建新的表格", "Create") </p> <table class="table"> <tr> <th>Name</th> <th>Uri</th> </tr> @foreach (var item in Model) { <tr> <td>@Html.ActionLink(@item.Name, "List", new { name = item.Name })</td> <td>@item.Uri</td> <td> @Html.ActionLink("上传数据", "Upload", new { name = item.Name }) | @Html.ActionLink("删除", "Delete", new { name = item.Name }) </td> </tr> } </table>
Table本身并没有太多的属性,所以Index非常简洁,运行结果如图

无责任Windows Azure SDK .NET开发入门(六):使用Table Storage服务

二、创建Table

代码如下

[HttpPost] public ActionResult Create(string name) {     CloudTable table = tableClient.GetTableReference(name);     table.CreateIfNotExists();      return RedirectToAction("Index"); }

对应的View代码

@{  ViewBag.Title = "Create"; } <h2>Create</h2> @using (Html.BeginForm()) {  @Html.AntiForgeryToken()  <div class="form-horizontal">   <h4>Storage Table</h4>   <hr />   @Html.ValidationSummary(true, "", new { @class = "text-danger" })   <div class="form-group">    <div class="col-md-offset-2 col-md-10">     @Html.Label("Table 名称")     @Html.TextBox("name")    </div>   </div>   <div class="form-group">    <div class="col-md-offset-2 col-md-10">     <input type="submit" value="Create" class="btn btn-default" />    </div>   </div>  </div> } <div>  @Html.ActionLink("Back to List", "Index") </div> 

运行结果如下

无责任Windows Azure SDK .NET开发入门(六):使用Table Storage服务

创建成功后会跳转到Index页面,我们可以看到创建成功的表

无责任Windows Azure SDK .NET开发入门(六):使用Table Storage服务

三、Upload上传实体数据

Storage Table存储的是结构化数据,所以我们需要建立一个模型,该模型必须从TableEntity继承,该基类实现了两个重要属性:PartitionKey和RowKey。

我上传的是我们业务的模拟数据,本来计划上传1000万行,实际测试太耗时间了,所以最终上传了300万行数据。

Table上传实体可以批量上传以提高效率,但有如下限制:

  • 每批次最多100个实体
  • 每批实体的PartitionKey必须一致

因为我们同事提供我数据的时候没有排序整理,所以我需要编写些逻辑进行适当处理。数据文件叫ARow1000W.txt,看看名字就知道我们的计划本来是很大的,下图看下这个文件的内容大致结构:

无责任Windows Azure SDK .NET开发入门(六):使用Table Storage服务

在这个文件中,PK是有重复值的,但RK没有重复值。

对应这个文件我们编写的模型如下代码:

public class BizEntity : TableEntity {  public string TARMAGIC { set; get; }  public string CONTSEQ { set; get; }  public string CONTDATE { set; get; }  public string CONTCODE { set; get; }  public string CONTNOTE1 { set; get; }  public string CONTNOTE2 { set; get; }  public string CONTNOTE3 { set; get; }  public string FOLLDATE { set; get; }  public string FOLLCODE {set;get; }  public string CONTWHO { set; get; }  public string CONTTEMP { set; get; } } 

控制器代码如下:

public ActionResult Upload(string name) {  CloudTable table = tableClient.GetTableReference(name);  var path = @"../ARow1000W.txt";  var file = new System.IO.FileInfo(path);  using (var reader = new System.IO.StreamReader(file.OpenRead()))  {   String str = String.Empty;   TableBatchOperation batchOperation = new TableBatchOperation();   int row = 0;   string LastPartitionKey = string.Empty;   while ((str = reader.ReadLine()) != null)   {    row++;    if (row == 1)//第一行是标题不作为数据行    {     continue;    }    var datas = str.Split(',');    if (datas.Length < 11)    {     continue;    }    var BizEntity = new AzureWebSdkApp.Models.BizEntity()    {     PartitionKey = datas[1].Trim(),     RowKey = datas[0].Trim(),     TARMAGIC = datas[2].Trim(),     CONTSEQ = datas[3].Trim(),     CONTDATE = datas[4].Trim(),     CONTCODE = datas[5].Trim(),     CONTNOTE1 = datas[6].Trim(),     CONTNOTE2 = datas[7].Trim(),     CONTNOTE3 = datas[8].Trim(),     FOLLDATE = datas[9].Trim(),     FOLLCODE = datas[10].Trim(),    };    if (BizEntity.PartitionKey == "")    {     continue;    }    //同一批的实体的PartitionKey必须一致    if ((!string.IsNullOrEmpty(LastPartitionKey) && BizEntity.PartitionKey != LastPartitionKey))    {     //将之前的批处理数据执行完毕     if (batchOperation.Count > 0)     {      table.ExecuteBatch(batchOperation);      batchOperation.Clear();     }     LastPartitionKey = BizEntity.PartitionKey;    }    //批中的实体最多100个    if (batchOperation.Count < 100)    {     batchOperation.InsertOrReplace(BizEntity);     LastPartitionKey = BizEntity.PartitionKey;    }    if (batchOperation.Count == 100)    {     table.ExecuteBatch(batchOperation);     batchOperation.Clear();    }   }   if (batchOperation.Count > 0)   {    table.ExecuteBatch(batchOperation);    batchOperation.Clear();   }  }  return RedirectToAction("Index"); } 

上面的代码主要的工作就是读取含有数据的文本文件,将每行文字转为实体对象并满足批次上传的要求。我的逻辑没有优化,你可以优化更有效的提高上传性能。

四、 List查询数据

这是一个非常令人激动的测试,测试的结果只有一个字:快!300万数据中,无论是查询RowKey,还是PartitionKey,还是PartitionKey和RowKey,还是其他非Key属性,或者Key和其他非Key属性混合查询,得到的体验就是快!

Table的查询由TableQuery实例化实体查询对象,由Table的ExecuteQuery方法具体执行。对查询的条件是通过一个查询表达字符串在TableQuery的Where中描述。为了简化Where中的查询表达式,SDK提供了TableQuery的一系列方法帮助我们构造查询字符串

  • CombineFilters
  • GenerateFilterCondition
  • GenerateFilterConditionForBinary
  • GenerateFilterConditionForBool
  • GenerateFilterConditionForDate
  • GenerateFilterConditionForDouble
  • GenerateFilterConditionForGuid
  • GenerateFilterConditionForInt
  • GenerateFilterConditionForLong
但是,你要记住,这些方法返回的都是字符串,并不是一个查询对象,这非常重要。另一个有趣的事情是TableQuery没有提供我们多条件并列的方法,可是我们却很容易来处理这个需求,看下代码你就明白了。
<div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;">[HttpPost]</span></div><div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;">public ActionResult List(string name, FormCollection values)</span></div>{  ViewBag.TableName = name; <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;"> ViewBag.RowKey = values["rowkey"];</span></div> ViewBag.PartitionKey = values["partitionkey"]; <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;"> ViewBag.CONTCODE = values["contcode"];</span></div> ViewBag.CONTSEQ = values["contseq"]; <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;"> IEnumerable<AzureWebSdkApp.Models.BizEntity> result = null;</span></div> CloudTable table = tableClient.GetTableReference(name); <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;"> var filters = new List<string>();</span></div> var query = new TableQuery<AzureWebSdkApp.Models.BizEntity>(); <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;">  filters.Add(TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, values["partitionkey"]));</span></div> if (!string.IsNullOrWhiteSpace(values["partitionkey"]))  {  }  if (!string.IsNullOrWhiteSpace(values["rowkey"])) <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;"> if (!string.IsNullOrWhiteSpace(values["contseq"]))</span></div> {   filters.Add(TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, values["rowkey"]));  }  { <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;"> if (!string.IsNullOrWhiteSpace(values["contcode"]))</span></div>  filters.Add(TableQuery.GenerateFilterCondition("CONTSEQ", QueryComparisons.Equal, values["contseq"]));  }  {   filters.Add(TableQuery.GenerateFilterCondition("CONTCODE", QueryComparisons.Equal, values["contcode"])); <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;">  result = table.ExecuteQuery(query);</span></div> }  if (filters.Count > 0)  {   var filter = filters.Aggregate((item, next) => string.Format("({0}) and ({1})", item, next));   result = table.ExecuteQuery(query.Where(filter));  }  else  {  } <div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;">}</span></div><div style="text-align: left;"><span style="font-family: Menlo, Monaco, monospace, sans-serif; font-size: 12.6000003814697px;"> return View(result);</span></div> 

上述代码说明当需要多条件我们可以自己构造Where需要的查询字符串。

对应的View代码如下,其中你可以了解到上述代码中ViewBag是用来保存用户上次选择的内容以提供比较良好的体验。

@model IEnumerable<AzureWebSdkApp.Models.BizEntity> @{  ViewBag.Title = "List"; } <h2>List</h2> @using (Html.BeginRouteForm("default",new { name = @ViewBag.TableName },FormMethod.Post,new {@class="form-inline"})) {  <div class="form-group">   <label for="">PartitionKey</label>   <input type="text" class="form-control" name="partitionkey" value="@ViewBag.PartitionKey">  </div>  <div class="form-group">   <label for="">RowKey</label>   <input type="text" class="form-control" name="rowkey" value="@ViewBag.RowKey">  </div>  <div class="form-group">   <label for="">CONTSEQ</label>   <input type="text" class="form-control" name="contseq" value="@ViewBag.CONTSEQ">  </div>  <div class="form-group">   <label for="">CONTCODE</label>   <input type="text" class="form-control" name="contcode" value="@ViewBag.CONTCODE">  </div>  <input type="submit" value="submit" class="btn btn-primary" /> } <ul class="nav nav-pills" role="tablist">  <li role="presentation" class="active"><span>查询耗时 <span class="badge">42</span></span></li> </ul> <table class="table col-md-12">  <tr>   <th>PartitionKey</th>   <th>RowKey</th>   <th>TARMAGIC</th>   <th>CONTSEQ</th>   <th>CONTDATE</th>   <th>CONTCODE</th>   <th>CONTNOTE1</th>   <th>CONTNOTE2</th>   <th>CONTNOTE3</th>   <th>FOLLDATE</th>   <th>FOLLCODE</th>   <th>CONTWHO</th>   <th>CONTTEMP</th>  </tr>  @if (Model != null)  {   foreach (var item in Model)   {    <tr>     <td>@item.PartitionKey</td>     <td>@item.RowKey</td>     <td>@item.TARMAGIC</td>     <td>@item.CONTSEQ</td>     <td>@item.CONTDATE</td>     <td>@item.CONTCODE</td>     <td>@item.CONTNOTE1</td>     <td>@item.CONTNOTE2</td>     <td>@item.CONTNOTE3</td>     <td>@item.FOLLDATE</td>     <td>@item.FOLLCODE</td>     <td>@item.CONTWHO</td>     <td>@item.CONTTEMP</td>    </tr>   }  } </table> 

运行中我们按不同的查询来观察性能,总数据行为300多万行。

按PartitionKey查询结果:

无责任Windows Azure SDK .NET开发入门(六):使用Table Storage服务

按RowKey查询结果:

无责任Windows Azure SDK .NET开发入门(六):使用Table Storage服务

按非Key值查询结果:

无责任Windows Azure SDK .NET开发入门(六):使用Table Storage服务

按PartitionKey组合非Key值查询结果:

无责任Windows Azure SDK .NET开发入门(六):使用Table Storage服务

按RowKey组合非Key值查询结果:

无责任Windows Azure SDK .NET开发入门(六):使用Table Storage服务

现在瓶颈在于呈现而不是查询阶段。如何提升呈现效率,我之后的章节会有描述。

五、Delete删除Table

代码非常简单:

public ActionResult Delete(string name)

{

tableClient.GetTableReference(name).Delete();

return RedirectToAction("Index");

}

作者简介

王豫翔,上海致胜信息技术有限公司开发部经理,微软最有价值专家(Microsoft MVP)。曾在各种类型企业做编程技术工作,从代码工人到架构设计,从CS到BS,从静态语言到动态语言,从企业应用到移动互联网。最近3年主持实施了多个大型BI项目和Azure项目。

正文到此结束
Loading...