特集
Windows Azureストレージ開発入門(前編)

初めてのWindows Azureテーブル・ストレージ開発

野村総合研究所 勇 大地
2009/12/22
Page1 Page2

3. Windows Azureテーブル・ストレージでの開発

テーブル・ストレージの階層構造

 すでに述べたように、テーブル・ストレージは、構造化されたデータを格納するためのストレージである。リレーショナル・データベースと異なる分散Key-Valueストアのアーキテクチャを採用しているため、高いスケーラビリティを提供している。

 テーブル・ストレージは、ストレージ・アカウント、テーブル、エンティティ、プロパティの階層構造を提供する。

テーブル・ストレージの階層構造

ストレージ・アカウント

 RDBのデータベースに対応する。テーブル・ストレージにアクセスするための名前空間のうち最も上のレベルである。

テーブル

 RDBのテーブルに対応する。ストレージ・アカウントの下に作成され、一連のエンティティが格納される。1つのストレージ・アカウントに複数のテーブルを格納できる。

エンティティ

 RDBのレコードに対応する。固定のスキーマを持たず、エンティティごとにプロパティの数や名前が一致しなくてもよい点が、RDBの「テーブル」とは異なる。必須のプロパティ(PartitionKey、RowKey、Timestamp)を含む最大255個のプロパティを持つことができる。

プロパティ

 RDBのカラムに対応する。エンティティ内の1つの値を表し、プロパティ名は大文字と小文字が区別される。プロパティは、次の表で述べる型をサポートしている。テーブル・ストレージは、SQL Serverのテーブルとはサポートしているプロパティの型が異なるので注意してほしい。

プロパティの型 詳細
Binary バイトの配列(最大サイズは64KByte)
Bool ブール値
DateTime UTC時刻として表現された64bit値(サポートされている値の範囲は1/1/1600〜12/31/9999)
Double 64bitの浮動小数点
GUID 124bitのグローバル一意識別子
Int 32bitの整数
Int64 64bitの整数
String UTF-16でエンコードされた値(サイズは最大64KByte)
エンティティのプロパティに含むことのできる型

必須プロパティ(PartitionKey/RowKey/Timestamp)とは

 次に必須プロパティであるPartitionKey、および、RowKey、Timestampについて解説する。

PartitionKeyプロパティとRowKeyプロパティ

 テーブル内の各エンティティは、PartitionKeyプロパティとRowKeyプロパティを用いて一意に識別される。また、これらのプロパティは、各エンティティがテーブルに格納される順序を制御するのにも使用される。

 なお、Windows Azureのテーブル・ストレージでは、パーティションを多数のストレージ・ノードに分散させることによって高いスケーラビリティを提供している。そのパーティションは、PartitionKeyプロパティの値によって決まる。つまり、同じPartitionKeyプロパティ値を持つエンティティは、すべて1つのストレージ・ノードに格納されることが保証されている。

 従って、テーブル・ストレージで高いスケーラビリティを実現するためには、データのパーティショニング(=分割)を検討し、それぞれのパーティションに対してPartitionKeyプロパティの値を適切に割り振る必要がある。

Timestampプロパティ

 読み取り専用のプロパティであり、Windows Azureストレージのシステムによって保持されるバージョンとして取り扱われる。

テーブル・ストレージを利用したアプリケーションの作成手順

 以降では、テーブル・ストレージを利用して、発言を保存するアプリケーションを作成する。

テーブル・ストレージを利用したアプリケーションの動作イメージ

 テーブル・ストレージを利用したアプリケーションの作成手順は、以下の流れとなる。

ASP.NET Webロールの作成
System.Data.Services.Clientアセンブリへの参照を追加
エンティティを定義
コンテキストを作成
Webロール側のWebフォーム画面を作成
Webロール側の処理ロジックを作成

ASP.NET Webロールの作成

 前述の「Windows Azureクラウド・サービスのソリューションの作成手順」を参考に、新しい「Cloud Service」のプロジェクトとソリューションを(以下の例では「TableConfirmCloudService」という名前で)作成し、ASP.NET Webロールを(以下の例では「WebRole1」という名前で)追加する。そして「Development Storageにアクセスするための基本設定」も行う。

System.Data.Services.Clientアセンブリへの参照を追加

 テーブル・ストレージは実際にはADO.NET Data Services(WCF Data Servicesに名称変更される)によって提供されているため、(REST形式ではなく)ADO.NET形式を採用する場合、ADO.NET Data ServicesのASP.NET/Windowsフォーム/WPF(Windows Presentation Foundation)向けクライアント・ライブラリを活用する必要がある。このクライアント・ライブラリはSystem.Data.Services.Clientアセンブリとして提供されている。

 そこで、System.Data.Services.Clientアセンブリへの参照を追加する。

 まず[ソリューション エクスプローラー]で、Webロールのプロジェクト項目の右クリック・メニューから[参照の追加]を選択する。

[ソリューション エクスプローラー]におけるアセンブリへの参照の追加(C#の例)

 [参照の追加]ダイアログから[.NET]タブを選択し、[コンポーネント名]から「System.Data.Services.Client」を選択して、[OK]ボタンをクリックする。

[参照の追加]ダイアログによるSystem.Data.Services.Clientへの参照の追加

エンティティを定義

 テーブルのエンティティの定義を作成する。今回は発言者の名前(Name)と発言内容(Body)を保存するため、以下のプロパティを定義する。

プロパティ名 格納データ
Name 文字列 発言者の名前
Body 文字列 発言内容
テーブル設計

 エンティティとなるクラスを実装するためのソース・ファイルとして「Comment.cs」もしくは「Comment.vb」を新規作成し、そこに以下のコードを記述する。このコードでは、Commentエンティティ・クラス内に、発言者の名前を表すNameプロパティと発言内容を表すBodyプロパティが定義されている。

using System;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;

namespace WebRole1.Models
{
  public class Comment : TableServiceEntity
  {
    public Comment()
    {
      RowKey = string.Format("{0:10}_{1}",
        DateTime.MaxValue.Ticks - DateTime.UtcNow.Ticks,
        Guid.NewGuid());
      PartitionKey = "position";
    }
    public string Name { get; set; }
    public string Body { get; set; }
  }
}
Imports Microsoft.WindowsAzure
Imports Microsoft.WindowsAzure.StorageClient

Namespace Models

  Public Class Comment
    Inherits TableServiceEntity

    Public Sub New()
      RowKey = String.Format("{0:10}_{1}", _
        DateTime.MaxValue.Ticks - DateTime.UtcNow.Ticks, _
        Guid.NewGuid())
      PartitionKey = "position"
    End Sub

    Private _name As String
    Public Property Name() As String
      Get
        Return _name
      End Get
      Set(ByVal value As String)
        _name = value
      End Set
    End Property

    Private _body As String
    Public Property Body() As String
      Get
        Return _body
      End Get
      Set(ByVal value As String)
        _body = value
      End Set
    End Property

  End Class

End Namespace
エンティティのコード例(上:Comment.cs、下:Comment.vb)

 PartitionKey、RowKey、Timestampといった必須プロパティが定義されていないが、Commentクラスが継承しているTableServiceEntityクラス内で定義されているため、自分で定義する必要はない。

 今回PartitionKeyプロパティは常に同じ文字列(=「position」という文字列)を指定し、RowKeyプロパティには日付情報とGuidから生成した文字列を割り当てている。エンティティはPartitionKeyプロパティとRowKeyプロパティに従って辞書順に並び替えられるため、日付の情報をゼロ詰め10けたで入力することで最新のエンティティを最初に取得することができる。

コンテキストを作成

 次に、コンテキストを作成する。コンテキストとは、ADO.NET Data Servicesの仕組みにおいて実行時の状態を保持・管理するDataServiceContextクラスの派生オブジェクトのことで、Windows AzureストレージのADO.NET形式ではTableServiceContextクラスの派生オブジェクトとして提供される。

 つまりここでは、テーブル・ストレージのコンテキストとして、TableServiceContextを継承したクラス(下記の例では「CommentServiceContext」)を定義する。このCommentServiceContextクラスを利用して、ADO.NET形式でテーブル・ストレージにアクセスできる。

 CommentServiceContextクラスを実装するためのソース・ファイルとして「CommentServiceContext.cs」もしくは「CommentServiceContext.vb」を新規作成し、そこに以下のコードを記述する。

using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;

namespace WebRole1.Models
{
  public class CommentServiceContext : TableServiceContext
  {
    public CommentServiceContext(string baseAddress, StorageCredentials credentials)
      : base(baseAddress, credentials)
    {
    }

    public void AddComment(string name, string body)
    {
      this.AddObject("comments", new Comment {
                                   Name = name, Body = body });
    }
  }
}
Imports Microsoft.WindowsAzure
Imports Microsoft.WindowsAzure.StorageClient

Namespace Models

  Public Class CommentServiceContext
    Inherits TableServiceContext

    Public Sub New(ByVal baseAddress As String, ByVal credentials As StorageCredentials)
      MyBase.New(baseAddress, credentials)
    End Sub

    Public Sub AddComment(ByVal name As String, ByVal body As String)
      Me.AddObject("comments", New Comment With { _
                                 .Name = name, .Body = body})
    End Sub

  End Class

End Namespace
コンテキストのコード例(上:CommentServiceContext.cs、下:CommentServiceContext.vb)

 このコードでは、CommentServiceContextコンテキスト・クラス内にAddCommentメソッドが定義されている。AddCommentメソッドでは、発言者の名前と発言内容から、「comments」テーブルのエンティティを作成し、テーブルに追加する処理を行う。

Webロール側のWebフォーム画面を作成

 次に、Webロール側のWebフォーム画面(Default.aspx)を作成する。サービス利用者の名前と発言内容を入力するテキストボックス、発言内容の一覧を表示するリストビュー、発言をWebロール側に通知するボタンを定義する。

 具体的には、次のようなコードになる(C#)。

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebRole1._Default" %>

<!doctype html public "-//w3c//dtd xhtml 1.0 transitional//en" "http://www.w3.org/tr/xhtml1/dtd/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>ASP.NET WEBロール</title>
</head>
<body>
  <form id="form1" runat="server">
     <h1>データのリスト(最初の5件を表示)</h1>
     <dl>
    <dt>名前</dt>
    <dd><asp:TextBox ID="NameTextBox"
       runat="server" Text="名無し" />
    </dd>
    <dt>発言</dt>
    <dd><asp:TextBox ID="BodyTextBox"
       runat="server" Text="空発言" />
    </dd>
     </dl>
     <asp:Button ID="CommentAddButton" runat="server" Text="発言を追加"
     OnClick="CommentAddButton_Click" />
     <asp:listview ID="Listview1" runat="server">
      <LayoutTemplate>
      <table runat="server" id="table1" width="640px" border="1">
        <tr runat="server">
        <th runat="server">お名前</th>
        <th runat="server">発言内容</th>
        <th runat="server">発言時間</th>
        </tr>
        <tr runat="server" id="itemPlaceholder"></tr>
      </table>
      </LayoutTemplate>
      <ItemTemplate>
       <tr runat="server">
        <td><%# Eval("Name") %></td>
        <td><%# Eval("Body") %></td>
        <td><%# Eval("Timestamp") %></td>
       </tr>
      </ItemTemplate>
    </asp:listview>
  </form>
</body>
</html>
Webフォーム画面のコード例(Default.aspx)

 なおVBの場合は、先頭行が、

<%@ Page Language="vb" AutoEventWireup="false" CodeBehind="Default.aspx.vb" Inherits="WebRole1._Default" %>

となる。

Webロール側の処理ロジックを作成

 最後に、Webロール側のコード・ビハインド・ファイル(Default.aspx.cs/Default.aspx.vb)に、発言を保存するロジック(Default.aspx.cs)を作成する。具体的には、以下のようなコードになる。

using System;
using System.Linq;
using System.Text;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using WebRole1.Models;

namespace WebRole1
{
  public partial class _Default : System.Web.UI.Page
  {
    CommentServiceContext context;

    protected void Page_Load(object sender, EventArgs e)
    {
      // *.cscfg設定ファイルからストレージ・アクセスの情報を取得
      var account = CloudStorageAccount.FromConfigurationSetting(
          "DataConnectionString");

      // テーブルクライアントを作成して、テーブルを作成
      CloudTableClient client = account.CreateCloudTableClient();
      client.CreateTableIfNotExist("comments");

      // サービス・コンテキストを定義
      context = new CommentServiceContext(
                  account.TableEndpoint.ToString(),
                  account.Credentials);

      // テーブル・バインド
      UpdateDataSource();
    }

    // コメントを追加するメソッド
    protected void CommentAddButton_Click(object sender, EventArgs e)
    {
      string name = NameTextBox.Text;
      string body = BodyTextBox.Text;

      // コメントを追加して保存
      // ※日本語を入力すると例外が発生するので、
      //  コメント追加時にBase64エンコードで対策
      context.AddComment(
        Convert.ToBase64String(Encoding.UTF8.GetBytes(name)),
        Convert.ToBase64String(Encoding.UTF8.GetBytes(body)));
      context.SaveChanges();

      // テーブル・バインド
      UpdateDataSource();
    }

    // テーブル・バインドを行うメソッド
    protected void UpdateDataSource()
    {
      // RowKeyプロパティによって整列済みのデータから、
      // 5件のデータを抜き出す
      var query = context.CreateQuery<Comment>(
                    "comments").Take(5).ToList();

      // 取得したCommentデータのNameとBodyは、
      // base64データとして格納されているのでデコードする
      this.Listview1.DataSource = query.Select(cmnt =>
      {
        cmnt.Name = Encoding.UTF8.GetString(
          Convert.FromBase64String(cmnt.Name));
        cmnt.Body = Encoding.UTF8.GetString(
          Convert.FromBase64String(cmnt.Body));
        return cmnt;
      });

      this.Listview1.DataBind();
    }
  }
}
Imports System
Imports System.Linq
Imports System.Text
Imports Microsoft.WindowsAzure
Imports Microsoft.WindowsAzure.StorageClient
Imports WebRole1.Models

Partial Public Class _Default
  Inherits System.Web.UI.Page

  Dim context As CommentServiceContext

  Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

    ' *.cscfg 設定ファイルからストレージ・アクセスの情報を取得
    Dim account = CloudStorageAccount.FromConfigurationSetting( _
      "DataConnectionString")

    ' テーブルクライアントを作成して、テーブルを作成
    Dim client = account.CreateCloudTableClient()
    client.CreateTableIfNotExist("comments")

    ' サービス・コンテキストを定義
    context = New CommentServiceContext( _
        account.TableEndpoint.ToString(), _
        account.Credentials)

    ' テーブル・バインド
    UpdateDataSource()

  End Sub

  ' コメントを追加するメソッド
  Protected Sub CommentAddButton_Click(ByVal sender As Object, ByVal e As EventArgs) Handles CommentAddButton.Click

    Dim name = NameTextBox.Text
    Dim body = BodyTextBox.Text

    ' コメントを追加して保存
    ' ※日本語を入力すると例外が発生するので、
    '  コメント追加時にBase64エンコードで対策
    context.AddComment( _
      Convert.ToBase64String(Encoding.UTF8.GetBytes(name)), _
      Convert.ToBase64String(Encoding.UTF8.GetBytes(body)))
    context.SaveChanges()

    ' テーブル・バインド
    UpdateDataSource()

  End Sub

  ' テーブル・バインドを行うメソッド
  Protected Sub UpdateDataSource()

    ' RowKeyプロパティによって整列済みのデータから、
    ' 5件のデータを抜き出す
    Dim query = context.CreateQuery(Of Comment)( _
            "comments").Take(5).ToList()

    ' 取得したCommentデータをDecodeBase64メソッドで処理
    Me.Listview1.DataSource = query.Select(AddressOf DecodeBase64)

    Me.Listview1.DataBind()

  End Sub

  ' 取得したCommentデータのNameとBodyは、
  ' base64データとして格納されているのでデコードする
  Function DecodeBase64(ByVal cmnt As Comment) As Comment

    cmnt.Name = Encoding.UTF8.GetString( _
      Convert.FromBase64String(cmnt.Name))
    cmnt.Body = Encoding.UTF8.GetString( _
      Convert.FromBase64String(cmnt.Body))
    Return cmnt

  End Function

End Class
フォーム画面の処理ロジックのコード例(上:Default.aspx.cs、下:Default.aspx.vb)

 Webロール側のテーブル・アクセス処理のロジックとして、以下のメソッドを定義した。

  • Page_Loadメソッド:同じ名前のテーブルが存在しなかった場合、テーブルを新規作成する処理を行う
  • CommentButton_Clickメソッド:サービス利用者が画面から入力した「名前」と「発言内容」を読み取ってテーブルに追加する。日本語の文字列を格納したエンティティをストレージに格納する際に、Base64エンコードする必要があることに注意する
  • UpdateDataSourceメソッド:最初の5件のデータをテーブルから取得してBase64のデコード処理を行ってリストビューにデータをバインドする

 以上で、テーブル・ストレージを利用したアプリケーションの作成は完了である。Visual Studioで[F5]キーを押して、作成したアプリケーションを実行し、動作確認していただきたい。

 また、本稿では取り扱わなかったが、テーブル・ストレージに対して発行したクエリで返されるエンティティの数は、以下のいずれかの理由で制限される。

(1)クエリで返されるエンティティの数がアプリケーション側で指定されている
(2)クエリで返されるエンティティの数が最大数(現在は1000個)を超えている
(3)クエリの実行時間が5秒を超えている

 クエリで返されるエンティティの数が(2)か(3)の理由で制限された場合、アプリケーション側で意図しない動作を行う可能性がある。この問題を解決するために、テーブル・ストレージに対するリクエストの応答には、以下で記述する継続トークンがREST APIのHTTP応答ヘッダとして含まれる(ADO.NET形式も内部でREST形式と同じAPIが呼び出されている)。

  • x-ms-continuation-NextTableName
  • x-ms-continuation-NextPartitionKey
  • x-ms-continuation-NextRowKey

 本稿では詳細に取り扱わないが、詳しい内容を知りたい方は以下のMSDNのページを参照してほしい。

 なお、テーブル・ストレージに対するクエリでは、IQueryableインターフェイスのメソッドの一部(例えばMaxメソッドやAverageメソッド)がサポートされていない。このため、すべてのクエリ機能は利用できないので注意してほしい。

 次回後編ではWindows Azureストレージのブロブ、キューの実装方法と注意点について解説する。End of Article


 INDEX
  特集:Windows Azureストレージ開発入門(前編)
  初めてのWindows Azureテーブル・ストレージ開発
    1.Windows Azureストレージの概要
  2.Windows Azureテーブル・ストレージでの開発
 
  特集:Windows Azureストレージ開発入門(後編)
  初めてのブロブ&キュー・ストレージ開発
    3.Windows Azureブロブ・ストレージでの開発
    4.Windows Azureキュー・ストレージでの開発
Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

キャリアアップ

.NET未来展望台

未来展望台コーナースポンサーからのお知らせ


- PR -
- PR -
ソリューションFLASH

「ITmedia マーケティング」新着記事

米大統領選挙後にXユーザー「大量流出」? うわさの真相は……
ユーザーのX離れXがリアルタイムの情報発信において他の追随を許さない存在であることは...

「ECプラットフォーム」売れ筋TOP10(2024年11月)
今週は、ECプラットフォーム製品(ECサイト構築ツール)の国内売れ筋TOP10を紹介します。

情報収集先としてのショート動画 就職・転職先探しで3人に1人が利用
ラクスルは、商品購入やサービスの利用、就職や転職における情報収集先に関するアンケー...