[Windows Azure][IT鐵人賽系列] Day 15 - Storage Service (2): Table Storage

Table Storage是一個模擬關聯式資料庫的結構化資料(structured data)存取服務,它就像是在雲端中的表格一樣,允許應用程式可以在Table儲存體中宣告並存取自己的資料結構。而在Table儲存體的內部,則是橫跨多個伺服器與磁碟儲存區的基礎架構,微軟的Windows Azure開發小組將核心內的所有作業都隱藏起來,只顯露出一個REST API供外部應用程式存取,而且都是透過相同的URL來呼叫,因此Table基本上並不是儲存在應用程式所在的VM,而是在Windows Azure Platform內部自動規範的儲存區域中。

Table Storage是一個模擬關聯式資料庫的結構化資料(structured data)存取服務,它就像是在雲端中的表格一樣,允許應用程式可以在Table儲存體中宣告並存取自己的資料結構。而在Table儲存體的內部,則是橫跨多個伺服器與磁碟儲存區的基礎架構,微軟的Windows Azure開發小組將核心內的所有作業都隱藏起來,只顯露出一個REST API供外部應用程式存取,而且都是透過相同的URL來呼叫,因此Table基本上並不是儲存在應用程式所在的VM,而是在Windows Azure Platform內部自動規範的儲存區域中。但是它雖然可以保存結構化資料,然而實際上它是以XML的方式儲存,架構上是以NoSQL的概念為主,所以不要將它和一般的DBMS的表格聯想在一起,它沒有辦法與DBMS一樣執行彙總與表格聯結(JOIN),當然也不相容於SQL指令。

與BLOB相似,Table Storage也有容器的概念,不過在Table Storage中的容器稱為表格,每一筆資料都是以表格為容器保存的,稱為Entity,每一筆Entity因為都是XML(如下列示之XML資料),所以必須要符合XML的規範,否則會在系統自行序列化時發生錯誤,簡單的說,只要使用最基本的.NET資料型別,多半都可以順利執行。

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<entry xmlns:d="
http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom">
  <title />
  <updated>2008-09-18T23:46:19.3857256Z<updated/>
  <author>
    <name />
  </author>
  <id />
  <content type="application/xml">
    <m:properties>
      <d:Address>Mountain View</d:Address>
      <d:Age m:type="Edm.Int32">23</d:Age>
      <d:AmountDue m:type="Edm.Double">200.23</d:AmountDue>
      <d:BinaryData m:type="Edm.Binary" m:null="true" />
      <d:CustomerCode m:type="Edm.Guid">c9da6455-213d-42c9-9a79-3e9149a57833</d:CustomerCode>
      <d:CustomerSince m:type="Edm.DateTime">2008-07-10T00:00:00</d:CustomerSince>
      <d:IsActive m:type="Edm.Boolean">true</d:IsActive>
      <d:NumOfOrders m:type="Edm.Int64">255</d:NumOfOrders>
      <d:PartitionKey>mypartitionkey</d:PartitionKey>
      <d:RowKey>myrowkey1</d:RowKey>
      <d:Timestamp m:type="Edm.DateTime">0001-01-01T00:00:00</d:Timestamp>
    </m:properties>
  </content>
</entry>

 

 

每一筆Entity除了自己本身的資料外,固定要保存PartitionKey, RowKey與Timestamp三筆資料:

  • PartitionKey:識別分割區的資料,由Table Storage伺服器依需要進行Entity的群組化,以加快存取時間。
  • RowKey:每個Entity的唯一識別碼,在同一個Table內要是唯一的。
  • Timestamp:每個Entity的最後存取時間。

 

Table Storage與BLOB Storage一樣,會提供REST API,不過特別的是Table的REST API是以OData(Open Data Protocol)為標準開放的,它是源自於WCF Data Service(前身為ADO.NET Data Service),所以.NET開發人員只要利用.NET Framework內建的Data Service Client(System.Data.Service.Client)就能順利存取它,更可以直接使用LINQ。

在Table Storage的程式設計上,開發人員會使用到CloudTableClient物件,這是Table Storage用戶端的入口,另外會使用到TableServiceEntity以及TableServiceContext兩個類別,TableServiceEntity是用來宣告Entity的內容的,因為Table Storage不可能知道Entity內的資料有哪些,應用程式必須告訴它,所以程式中一定要有一個資料類別是繼承自TableServiceEntity,內含必要的資料型別,如下列程式碼:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.Services;
using System.Data.Services.Client;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;

namespace ContactManagerWeb
{
    public class Contact : TableServiceEntity
    {
        public string Name { get; set; }
        public string Address { get; set; }
        public string Phone { get; set; }
        public string Cellphone { get; set; }

        public Contact()
        {
            base.PartitionKey = "Contact";
            base.RowKey = Guid.NewGuid().ToString();
        }
        public Contact(string Name, string Address, string Phone, string Cellphone)
        {
            base.PartitionKey = "Contact";
            base.RowKey = Guid.NewGuid().ToString();
            this.Name = Name;
            this.Address = Address;
            this.Phone = Phone;
            this.Cellphone = Cellphone;
        }
    }
}

 

而TableServiceContext則可以當做遠端的資料來源,所有針對Table Storage的OData查詢都是由這個類別發動,它所屬的CreateQuery<T>()方法可以建立對Table Storage的查詢,再利用LINQ指令,即可輕鬆的對Table Storage進行資料的查詢與回傳的工作。

DataServiceQuery<Contact> queryProvider = context.CreateQuery<Contact>("Contacts");
   this.gvContactViewer.DataSource = from contacts in queryProvider
   select contacts;

對Table Storage的資料增修刪當然也不是問題,只要透過類別操作(),最後呼叫TableServiceContext.SaveChanges()即可。

protected void gvContactViewer_RowCommand(object sender, GridViewCommandEventArgs e)
{
    CloudStorageAccount storageAccount = CloudStorageAccount.FromConfigurationSetting("DataSource");
    CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
    TableServiceContext context = tableClient.GetDataServiceContext();
    Contact contact = null;
    switch (e.CommandName)
    {
        case "EditContact":

            contact = new Contact(e.CommandArgument.ToString());

            this.txtName.Text = contact.Name;
            this.txtPhone.Text = contact.Phone;
            this.txtCellphone.Text = contact.Cellphone;
            this.txtAddress.Text = contact.Address;

            ViewState["EditState"] = EditState.Update;
            ViewState["EditContactID"] = e.CommandArgument.ToString();

            this.mvContactManager.SetActiveView(this.vContactForm);

            break;

        case "DeleteContact":

            var queryItem = from contacts in context.CreateQuery<Contact>("Contacts")
                              where contacts.RowKey == e.CommandArgument.ToString()
                              select contacts;

            context.DeleteObject(queryItem.First<Contact>());
            context.SaveChanges(SaveChangesOptions.Batch);

            // re-bind gridview.
            this.gvContactViewer.DataSource = from contacts in context.CreateQuery<Contact>("Contacts")
                                                 select contacts;
            this.gvContactViewer.DataBind();

            break;
    }

    context = null;
    storageAccount = null;
    tableClient = null;
}

 

protected void cmdOK_Click(object sender, EventArgs e)
{
    CloudStorageAccount storageAccount = CloudStorageAccount.FromConfigurationSetting("DataSource");
    CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
    TableServiceContext context = tableClient.GetDataServiceContext();

    if (EditState.Add == ((EditState)ViewState["EditState"]))
    {
        context.AddObject("Contacts",
            new Contact(this.txtName.Text, this.txtAddress.Text, this.txtPhone.Text, this.txtCellphone.Text));
    }
    else
    {
        var queryItem = from contacts in context.CreateQuery<Contact>("Contacts")
                         where contacts.RowKey == ViewState["EditContactID"].ToString()
                         select contacts;

        Contact contact = queryItem.First<Contact>();

        contact.Address = this.txtAddress.Text;
        contact.Name = this.txtName.Text;
        contact.Phone = this.txtPhone.Text;
        contact.Cellphone = this.txtCellphone.Text;

        context.UpdateObject(contact);
    }
    context.SaveChangesWithRetries(SaveChangesOptions.Batch);

    this.gvContactViewer.DataSource = from contacts in context.CreateQuery<Contact>("Contacts")
                                         select contacts;
    this.gvContactViewer.DataBind();

    this.mvContactManager.SetActiveView(this.vContactViewer);

    ViewState["EditState"] = EditState.None;
    ViewState["EditContactID"] = null;

    context = null;
    storageAccount = null;
    tableClient = null;
}

 

 

最後要提的是,Table Storage在使用上有些限制:

  • 欄位名稱要依照C#的identifier(識別子)的規範來命名,最長255字,若XML規範未支援的C#規範也不可以使用。
  • 欄位數最多252個(不含系統必要的3個),且整個資料列的資料長度不可以超過1MB。
  • 系統欄位Timestamp不可以修改(若在POST和UPDATE時入Timestamp的話,系統會略過)。
  • 不可以使用『\』、『/』、『#』以及『?』四種字元。
  • 每次的新增、修改與刪除作業,都必須要指定Partition Key以及Row Key。
  • Partition Key和Row Key長度最大1KB,同時在相同的Partition Key中,Row Key對於每個資料列都必須是唯一的值。
  • 所有欄位的資料型別都必須要符合EDM(Entity Data Model)的資料型別規範。

 

Reference:

http://msdn.microsoft.com/en-us/windowsazure/wazplatformtrainingcourse_exploringwindowsazurestoragevs2010_topic2#_Toc303848586