您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
无责任Windows Azure SDK .NET开发入门(六):Table Storage服务
 
作者: 王豫翔 来源: CSDN 发布于:2015-10-15
  1864  次浏览      35
 

摘要:无责任Windows Azure SDK .NET开发入门篇,将带来一系列基础文章:从Windows Azure开发前准备工作、使用Azure AD 管理用户信息、创建管理“云”服务、到使用Blob Storage服务等,帮助读者轻易上手使用这套开发工具。

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

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

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

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

  • 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非常简洁,运行结果如图

二、创建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>

运行结果如下

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

三、Upload上传实体数据

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

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

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

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

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

在这个文件中,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查询结果:

按RowKey查询结果:

按非Key值查询结果:

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

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

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

五、Delete删除Table

代码非常简单:

public ActionResult Delete(string name)
{
tableClient.GetTableReference(name).Delete();
return RedirectToAction("Index");
}

作者简介

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

   
1864 次浏览       35
 
相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
 
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
 
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
最新活动计划
嵌入式软件架构设计 12-11[北京]
LLM大模型与智能体开发实战 12-18[北京]
嵌入式软件测试 12-25[北京]
AI原生应用的微服务架构 1-9[北京]
AI大模型编写高质量代码 1-14[北京]
需求分析与管理 1-22[北京]

使用decj简化Web前端开发
Web开发框架形成之旅
更有效率的使用Visual Studio
MVP+WCF+三层结构搭建框架
ASP.NET运行机制浅析【图解】
编写更好的C#代码
10个Visual Studio开发调试技巧
更多...   

.NET框架与分布式应用架构设计
.NET & WPF & WCF应用开发
UML&.Net架构设计
COM组件开发
.Net应用开发
InstallShield

日照港 .NET Framework & WCF应用开发
神华信息 .NET单元测试
北京 .Net应用软件系统架构
台达电子 .NET程序设计与开发
赛门铁克 C#与.NET架构设计
广东核电 .Net应用系统架构
更多...