データ サービス
SQL Server Data Services で堅牢かつスケーラブルなアプリケーションを開発する
David Robinson
この記事では、次の内容について説明します。
- SSDS データ モデル
- エンティティ、コンテナ、オーソリティを管理する
- サンプル Web アプリケーションを作成する
- クラスのシリアル化と逆シリアル化
|
この記事では、次のテクノロジを使用しています。
SQL Server
|
このコラムは、SQL Server Data Services のプレリリース版に基づいて書かれています。ここに記載されているすべての情報は、変更される場合があります。

目次
Microsoft データ プラットフォームは、構造化されていないバイナリ データから、それと対極にある高度に構造化されたオンライン分析処理 (OLAP) キューブまで、あらゆる種類のデータに対応する充実した製品とテクノロジで構成されています。これらのテクノロジはどのようなアプリケーション シナリオにも対応できますが、通常は、ソリューションのコーディングを始められるようになる前に、時間とハードウェア ソリューションの先行投資が伴います。
アプリケーションのライフサイクル全体を通して必要になるストレージと処理の要件を見積もることは、多くの場合、アプリケーション開発者にとって容易ではありません。特に、設立したばかりの企業の場合は、資金が限られ、ハードウェア投資を決定する基準になるデータが乏しいため、非常に困難なことがあります。このような先行投資に関する不安に対処するため、マイクロソフトは最近、既に堅牢なデータ プラットフォームに、SQL Server® Data Services (SSDS) という新しいテクノロジを追加しました。
SSDS は、従来の箱売りされているソフトウェアとしての製品ではなく、実績のある SQL Server テクノロジを内部的に使用し、業界標準の Web サービス インターフェイスを通して機能を公開する、堅牢なスケールフリーのデータ サービスです。SSDS は、オープンな業界標準のプロトコルを使用してアクセスできる、使いやすく柔軟なデータ モデルを提供します。
Microsoft® .NET Framework、Java、それ以外のテクノロジのいずれを使用して開発する場合でも、SSDS をデータ ストアとして使用できます。さらに、SSDS は、開発者が短時間でアカウントのプロビジョニングを行い、そのアカウントに対する開発をすぐに始められるように作られています。
SSDS は、使用可能なドライブ ベイやラック スペースによって制約されないデータ バック エンドを開発者に提供します。SSDS をデータ プラットフォームとして使用すると、アプリケーションは必要な量のデータを自由に使用できます。ストレージがギガバイトであってもペタバイトであっても、利用したリソース分だけのコストしか発生しません。
多くの Web サイトやアプリケーションのアクセス パターンは周期的であり、負荷のピークがたとえ短時間であっても、ピーク時の負荷に耐えられるだけの十分な処理能力が必要です。データ バック エンドとして SSDS を使用すると、アプリケーションに集中でき、データ プラットフォームの容量計画に気を遣う必要はありません。
この記事では、SSDS を中心とするデータ ソリューションの開発の基礎について紹介します。最初に、SSDS が採用しているデータ モデルについて説明します。次に、筆者が SSDS を使用して構築した簡単なオンライン広告システムをお見せしながら、開発の詳細について説明します (図 1 を参照)。
図 1 SSDS によってホストされる広告アプリケーションのサンプル (クリックすると拡大画像が表示されます)
SSDS データ モデル
SSDS は、柔軟なエンティティ ベースのデータ モデルを備えています。このモデルを構成するのは、オーソリティ、コンテナ、およびエンティティという 3 つの主要な要素であり、それらの要素は ACE 概念と呼ばれます (図 2 を参照)。SSDS のオーソリティは、リレーショナル世界のデータベースと関連付けることができます。オーソリティをプロビジョニングすると、オーソリティにアクセスするための DNS 名が SSDS によって作成されます。たとえば、ssdsdemo という名前のオーソリティをプロビジョニングした場合は、次の URI でアクセスします。
ssdsdemo.data.beta.mssds.com
図 2 SSDS の基本コンポーネント
オーソリティは、地理的な場所の単位でもあります。つまり、作成された DNS 名は、データがホストされる特定の Microsoft データセンターにマップされます。2 つのオーソリティ americasdemo.data.beta.mssds.com と europedemo.data.beta.mssds.com を作成した場合は、それぞれがユーザーに最も近い場所にある異なるデータセンターと関連付けられます。
オーソリティには、コンテナのコレクションが含まれます。コンテナは、リレーショナル データベースのテーブルと似ています。重要な違いは、データベースのテーブルにはスキーマを関連付けて、テーブル内のすべての行を同種にすることです。SSDS のコンテナにスキーマは必要なく、異種のエンティティをまとめて 1 か所に格納できるので便利です。コンテナは単なるエンティティのコレクションです。現在のリリースの SSDS では、すべてのクエリのスコープは単一のコンテナに設定されています。
もう 1 つ重要なこととして、各コンテナは SSDS クラスタ内の異なるノードに配置されます。SQL Server では、パフォーマンスを向上させるために、さまざまなテーブルを別々のスピンドルに配置し、読み取り/書き込み能力を最大化します。これは SSDS でも同様ですが、各コンテナを別々のマシンに配置する点が異なります。データを複数のコンテナに分割し、要求をマルチスレッド化すると、読み取りと書き込みが 1 つのコンピュータに制限されないので、パフォーマンスを向上できます。
コンテナに関する最後の注意点として、各コンテナが SSDS クラスタの別々のノードに配置されるだけでなく、障害復旧の目的でデータも他の複数のノードにレプリケートされます。コンテナが配置されたコンピュータで障害が発生した場合は、バックアップ レプリカの 1 つが自動的に昇格されるので、アプリケーションでデータやパフォーマンスの損失が発生することはありません。
エンティティは、リレーショナル データベースにおけるテーブルの行と対比できます。エンティティは単に、名前と値のペアからなるプロパティを収集したものです。名前/値ペアは、区別されたシステム プロパティと柔軟なプロパティという 2 つのカテゴリに分かれます。
区別されたシステム プロパティはすべてのエンティティに共通で、ID、Kind、Version などがあります。ID はエンティティを一意に識別します。ID は、対象のエンティティが存在するコンテナの内部では一意である必要がありますが、コンテナが違えば、異なるエンティティが同じ ID を持っていてもかまいません。Kind は、同種のエンティティを分類するために使用します。エンティティにスキーマはアタッチされないので、Kind が同じエンティティであっても同じ構造であるとは限りません。Version は、エンティティの現在のバージョンを示すために使用されます。この値は、操作のたびに更新されます。
柔軟なプロパティには、開発者がアプリケーション データを格納します。柔軟なプロパティは、string、decimal、bool、datetime、binary という単純な型をサポートします。柔軟なプロパティのそれぞれに先頭 256 バイトまでインデックスを作成できます。
広告システムを作成する
SSDS の機能を実演し、開発作業の概要を示すために、サンプルのオンライン広告システムである Contoso Classifieds の実装の流れを見ていきます。サンプルを構成するアプリケーションは、システムを管理するための Windows® フォーム アプリケーションと、ユーザーが訪れるメインの Contoso Classifieds サイトである ASP.NET アプリケーションの 2 つです。アプリケーションが SSDS にアクセスするには、Windows フォーム アプリケーションでは SOAP インターフェイスを使用し、ASP.NET アプリケーションでは Representational State Transfer (REST) インターフェイスを使用します。
SSDS アカウントにサインアップすると、ユーザー名とパスワードを受け取ります。そこから先は、必要に応じてオーソリティ、コンテナ、エンティティの作成を開始できます。Contoso Classifieds の場合は、contosoclassifieds というオーソリティを設定しました。SSDS のベータ リリースでのサービス URI エンドポイントは data.data.beta.mssds.com です。
contosoclassifieds オーソリティには、Categories と Cities という 2 つのコンテナが含まれます。Categories は、リスト カテゴリに関係のあるエンティティが含まれます。Cities には、システムで定義されている各都市のエンティティが含まれます。これらのエンティティは、都市固有のオーソリティを指すポインタにすぎません。都市固有の各オーソリティには、リスト カテゴリごとのコンテナがあります (図 3 を参照)。
図 3 Contoso Classifieds の要素 (クリックすると拡大画像が表示されます)
このような設計にしたのにはいくつかの理由があります。オーソリティを都市ごとに実装したのは、サービス提供の対象者に地理的に近いデータセンターでオーソリティをプロビジョニングできるようにするためです。コンテナをリスト カテゴリごとにしたのは、クエリ パターンを簡単にし、負荷をクラスタ内の複数のマシンに分散できるようにするためです (コンテナのスコープはバックエンド クラスタの特定のノードに設定されることを思い出してください)。図 4 は、Contoso Classifieds 管理クライアントのユーザー インターフェイスです。
図 4 Classifieds アプリケーションの管理クライアント (クリックすると拡大画像が表示されます)
既に説明したように、アプリケーションで使用するオーソリティ、コンテナ、およびエンティティは、開発者が作成します。サンプル アプリケーションでは、この作業はすべて、[Perform Initial Setup] ボタンのクリック イベントに含まれています (エラー処理を含む完全なコードは、この記事のコードのダウンロードに含まれています)。
SSDS のオーソリティのプロビジョニングは非常に簡単です。次のコード例では SitkaSoapServiceClient を使用しており、これには既に SSDS SOAP インターフェイスにサービス参照を追加してあります。
using (SSDSClient.SitkaSoapServiceClient ssdsProxy =
new SSDSClient.SitkaSoapServiceClient())
オーソリティについては既に説明しましたが、SSDS のスコープとはどのようなものでしょうか。SSDS でのスコープ オブジェクトは、REST サービスで URI が使用されるのと同じように、SOAP サービスにおいてオブジェクトをアドレス指定する手段を提供します。
まず最初にサービスのユーザー名とパスワードを設定します。
ssdsProxy.ClientCredentials.UserName.UserName =
txtUserName.Text;
ssdsProxy.ClientCredentials.UserName.Password =
txtPassword.Text;
ACE モデルの最上位レベルであるオーソリティを作成する必要があるので、最初に空のスコープを作成します。
SSDSClient.Scope serviceScope = new SSDSClient.Scope();
次に、Contoso Classifieds オーソリティを作成します。このオーソリティは、システムの一般的な構成情報を保持します。
SSDSClient.Authority contosoAuth =
new SSDSClient.Authority();
contosoAuth.Id = "contosoclassifieds";
それから、作成したスコープとオーソリティを SSDS に送信します。
ssdsProxy.Create(serviceScope, contosoAuth);
メインの Contoso Classifieds オーソリティを作成したので、そのオーソリティを指すようにスコープを設定し、広告を提供する都市を保持するためのコンテナを作成して、作成したコンテナを SSDS に送信します。
serviceScope.AuthorityId = contosoAuth.Id;
SSDSClient.Container citiesContainer = new SSDSClient.Container();
citiesContainer.Id = "Cities";
ssdsProxy.Create(serviceScope, citiesContainer);
コンテナを作成するプロセスは、オーソリティの作成プロセスと似ています。違いは、Authority オブジェクトの代わりに Container オブジェクトを作成し、空のスコープを作成する代わりに、コンテナを作成するオーソリティを指すようにスコープを更新することだけです。
アプリケーションでサポートするすべてのヘッダー カテゴリとリスト カテゴリを保持するコンテナを作成します。
SSDSClient.Container categoriesContainer =
new SSDSClient.Container();
categoriesContainer.Id = "Categories";
ssdsProxy.Create(serviceScope, categoriesContainer);
オーソリティを作成したら、Web ブラウザで HTTP または HTTPS を指定することで、REST インターフェイスを使用してコンテナの内容を表示できます。オーソリティを設定するときに SSDS が作成した新しい DNS 名を使用するだけです。この場合、URL は次のようになります。
http://contosoclassifieds.data.data.beta.mssds.com/v1
この URL をブラウザに入力すると、認証を求められます。認証が成功すると、ブラウザに次のような内容が表示されます。
<s:Authority xmlns:s="http://schemas.microsoft.com/sitka/2008/03/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:x="http://www.w3.org/2001/XMLSchema">
<s:Id>contosoclassifieds</s:Id>
<s:Version>11</s:Id>
</s:Authority>
オーソリティ名には小文字が使用されます。SSDS がオーソリティの DNS エントリを作成するので、DNS の命名規則に従う必要があるからです。
コンテナを作成した後、ブラウザの表示を更新して、URL に ?q="" の形式で空のクエリを追加すると、EntitySet で 1 つの Container オブジェクトが返されることがわかります。EntitySet は単に、クエリに対する応答として返されるエンティティのコレクションです。初期セットアップが完了すると、サンプル アプリケーションには 1 つのオーソリティ (contosoclassifieds) と 2 つのコンテナ (Categories と Cities) が作成されています。
<s:EntitySet xmlns:s="http://schemas.microsoft.com/sitka/2008/03/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:x="http://www.w3.org/2001/XMLSchema">
<s:Container>
<s:Id>Categories</s:Id>
<s:Version>1</s:Id>
</s:Container>
<s:Container>
<s:Id>Cities</s:Id>
<s:Version>1</s:Id>
</s:Container>
</s:EntitySet>
これで初期セットアップは完了です。先に進み、サービス対象都市の機能を追加します。
都市を追加する
既に説明したように、Contoso Classifieds は各都市の一覧を独自のオーソリティに格納します。これにより、その都市をホストするデータセンターを選択できます (現時点の SSDS のベータではこの機能はサポートされていませんが、製品がリリースされるまでにはサポートされるようになります)。
都市を追加するためのコードは、初期セットアップで使用したコードと似ています。SOAP プロキシを使用して資格情報の設定、空のスコープの作成、およびオーソリティ ID の設定を行い、Create を発行します。
SSDSClient.Authority cityAuth =
new SSDSClient.Authority();
cityAuth.Id = cityAuthorityName;
ssdsProxy.Create(serviceScope, cityAuth);
新しい都市オーソリティを作成したので、メインの contosoclassifieds オーソリティ内の Cities コンテナにポインタ エンティティを追加します。
serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Cities";
//Create the City Entity, and set its properties appropriately
SSDSClient.Entity cityEntity = new SSDSClient.Entity();
cityEntity.Id = cityAuthorityName;
cityEntity.Kind = "CityServed";
cityEntity.Properties = new Dictionary<string, object>();
cityEntity.Properties["AuthorityUri"] =
string.Format("{0}.data.data.beta.mssds.com/v1/", cityAuthorityName);
cityEntity.Properties["Name"] = txtCity.Text;
//Issue the create to SSDS
ssdsProxy.Create(serviceScope, cityEntity);
ここまで説明した内容をまとめてみましょう。システム全体のすべてのデータを保持するメインの contosoclassifieds オーソリティを作成しました。そのオーソリティ内に Cities コンテナがあり、そのコンテナ内には、アプリケーションがサービスを提供する各都市のエンティティが含まれます。そのエンティティには、都市の表示名と共に、すべての一覧を含むオーソリティへのポインタが含まれます。
カテゴリを追加する
ここまででオーソリティ、コンテナ、およびエンティティの追加が完了しました。次の手順はリスト カテゴリ機能の実装です。これを行うには、SSDS のクエリ、更新、および削除の機能を使用します。
Contoso Classifieds は、カテゴリ ヘッダーをサポートします。カテゴリ ヘッダーの下にはサブ カテゴリがあり、ユーザーからの実際の転記の内容を保持します。このデータにリレーショナル モデルを使用する場合、通常はデータのヘッダー/行パターンを使用します。しかし、SSDS が使用する柔軟なエンティティのデータ モデルでは、データの形態を自由に設定できます。Contoso Classifieds アプリケーションの場合は、単一のエンティティを使用して、各カテゴリ ヘッダーとそれに属するすべてのサブカテゴリを表すことにします (図 5 を参照)。
図 5 アプリケーションが使用するエンティティ データ モデル (クリックすると拡大画像が表示されます)
断っておきますが、このパターンを使用するのは、あくまでもエンティティの柔軟な性質を示すためであり、複数のエンティティが同じ Kind を持つことがあってもエンティティを同種にする必要がないことを説明するためです。エンティティの形態は自由に設定できます。
このシナリオに対するさらに優れたアプローチは、Category および Listing Category という 2 つの異なるエンティティの種類を作成し、すべてのエンティティに柔軟なプロパティ CategoryID を含めるというものです。このようにすると、次のようなクエリを発行できます。
from e in entities where e["CategoryID"] == "For Sale" select e
柔軟なプロパティに注意してください。既に説明したように、コンテナのエンティティは、区別されたシステム プロパティ ID の使用を制限する一意の ID を持つ必要がありました。また、発行しているクエリの構文にも注意してください。SSDS では LINQ に似たクエリ構文を使用します。その構文は、.NET Framework を使用しているほとんどの開発者にはなじみやすいものです。
図 4 の管理アプリケーションを見返すと、リスト カテゴリを表すツリー ビュー、およびカテゴリ ヘッダーとリスト カテゴリを追加するためのテキスト ボックスに気付くでしょう。カテゴリ ヘッダーを追加するときの内部の実装について説明します。
前のコードと同様に、SitkaSOAPServiceClient を使用して資格情報を設定し、contosoclassifieds オーソリティと Categories コンテナにスコープを設定します。次に行う必要があるのは、エンティティの作成だけです。
SSDSClient.Scope serviceScope = new SSDSClient.Scope();
serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Categories";
SSDSClient.Entity categoryHeaderEntity = new SSDSClient.Entity();
categoryHeaderEntity.Id = CategoryID;
categoryHeaderEntity.Kind = "Category";
カテゴリ名を格納する柔軟なプロパティを 1 つ追加することもできます。これにより柔軟なプロパティのコレクションには 1 つのプロパティが追加されるだけであることに注意してください。
categoryHeaderEntity.Properties =
new Dictionary<string, object>();
categoryHeaderEntity.Properties["CategoryName"] = CategoryName;
次の手順はリスト カテゴリの追加です。Contoso Classifieds は単一のエンティティを使用してカテゴリ ヘッダー内のリスト カテゴリのコレクション全体を表すので、カテゴリ エンティティを取得し、新しいリスト カテゴリを追加し (柔軟なプロパティとして追加されます)、更新を SSDS に送信する必要があります。
カテゴリのエンティティを取得するには、エンティティを直接指すようにスコープを設定して Get メソッドを呼び出します。このようにすると、クエリを発行しないで直接エンティティを取得できます。
SSDSClient.Scope serviceScope = new SSDSClient.Scope();
serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Categories";
serviceScope.EntityId = CategoryID;
//Retrieve the Category Entity
SSDSClient.Entity categoryEntity = ssdsProxy.Get(serviceScope);
次に新しいプロパティのインデックスを特定し、新しい柔軟なプロパティとして追加したら、Update を発行します (図 6 を参照)。

図 6 柔軟なプロパティの追加
//Determine whether this is the first listing
//category being added to this header
if (categoryEntity.Properties.Count > 2) {
propCount = ((categoryEntity.Properties.Count - 1) / 2);
propCount++;
}
else {
propCount = 1;
}
//Create the FlexProperties for the new listing category
string listingIdPropName = string.Format ("ListingCategoryID{0}", propCount);
string listingNamePropName = string.Format ("ListingCategoryName{0}", propCount);
categoryEntity.Properties[listingIdPropName] = ListingCategoryID;
categoryEntity.Properties[listingNamePropName] = ListingCategoryName;
//Issue the update to SSDS
ssdsProxy.Update(serviceScope, categoryEntity);
エンティティを更新および削除する
リスト カテゴリの更新は、新しい柔軟なプロパティを追加するだけです。更新のたびに、エンティティを取得し、ローカルで更新を行って更新を送信し、更新したエンティティをスコープと共に Update メソッドに渡します。
まず、サービス スコープをメインの contosoclassifieds オーソリティに設定します。また、Categories コンテナおよび更新するカテゴリのエンティティを指すように設定します。
SSDSClient.Scope serviceScope = new SSDSClient.Scope();
serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Categories";
serviceScope.EntityId = currentHdrNode.Name;
更新するエンティティを取得します。
SSDSClient.Entity categoryEntity = ssdsProxy.Get(serviceScope);
これで、柔軟なプロパティのコレクションを再初期化し、リスト カテゴリに対するすべての柔軟なプロパティを再び追加できます。または、ループ処理で柔軟なプロパティを個別に更新することもできますが、このエンティティの構造は単純なので、図 7 で示すようにエンティティを再作成する方が簡単です。

図 7 プロパティの更新
categoryEntity.Properties = new Dictionary<string, object>();
categoryEntity.Properties["CategoryName"] = currentHdrNode.Text;
int propCount = 1;
if (e.Node.Parent != null) {
//Loop through each node and add its category ID and
//category name flex property.
foreach (TreeNode node in e.Node.Parent.Nodes) {
string listingIdPropName = string.Format ("ListingCategoryID{0}", propCount);
string listingNamePropName = string.Format ("ListingCategoryName{0}", propCount);
categoryEntity.Properties[listingIdPropName] = node.Name;
categoryEntity.Properties[listingNamePropName] = node.Text;
//if we are re-adding the updated category, the tree view
//will still have its old value, so set the flex property
//to the updated value
if (node.Name == e.Node.Name) {
categoryEntity.Properties[listingIdPropName] = m_OldSelectNode.Name;
categoryEntity.Properties[listingNamePropName] = e.Label;
}
propCount++;
}
}
//Submit the update to SSDS
ssdsProxy.Update(serviceScope, categoryEntity);
エンティティまたはコンテナを削除するには、ServiceScope が削除するアイテムを指すように設定し、SitkaSoapServiceClient の Delete メソッドを呼び出します。Header の場合は、エンティティ全体を削除します。
if (m_OldSelectNode.Parent == null) {
ssdsProxy.Delete(serviceScope);
}
他のエンティティの場合は、更新で行ったのと同様に、柔軟なプロパティのコレクションを再作成して再構築し、削除したノード以外のすべてのノードの追加を実行します。
SSDSClient.Entity categoryEntity = ssdsProxy.Get(serviceScope);
categoryEntity.Properties = new Dictionary<string, object>();
categoryEntity.Properties["CategoryName"] = CategoryName;
int propCount = 1;
foreach (TreeNode node in entityNode.Nodes) {
string listingIdPropName = string.Format ("ListingCategoryID{0}", propCount);
string listingNamePropName = string.Format ("ListingCategoryName{0}", propCount);
if (node.Text != m_OldSelectNode.Text) {
categoryEntity.Properties[listingIdPropName] = node.Name;
categoryEntity.Properties[listingNamePropName] = node.Text;
propCount++;
}
}
最後に、更新を SSDS に発行し、ツリー ビューからノードを削除します。
ssdsProxy.Update(serviceScope, categoryEntity);
tvCategories.Nodes.Remove(m_OldSelectNode);
リスト スキーマを追加および削除する
SSDS の現在のベータは、スキーマをサポートしていません。将来的にはスキーマのサポートを追加する計画がありますが、SSDS で独自にスキーマを追加するのは簡単です。ただ 1 つ注意が必要なのは、現時点では SSDS がスキーマをサポートしないので、アプリケーションでスキーマを管理する必要があることです。
Contoso Classifieds の場合は、特定のリスト カテゴリに対して管理者がカスタム スキーマを定義しておくのは有効だと言えます。管理クライアントの内部でスキーマを定義し、後で Web サイトを実装するときにそれを使用できます。
これまでに説明した手順では、同じコンテナの中に異種のエンティティは含まれていませんでした。各エンティティには Kind プロパティがあるので、これを使用して異なる種類のエンティティを同じコンテナに格納でき、特定の種類のエンティティ (つまりこの場合は Kind) を照会することもできます。Kind に関連付けられたスキーマ コントラクトはありませんが、このメカニズムを使用して同種のデータを簡単にグループ化できます。ここでは、Kind の新しいエンティティである ListingSchema を Categories コンテナに追加します。
リスト スキーマを追加するためのダイアログは非常に簡単です (図 8 を参照)。このダイアログで管理者はリストに関連付ける新しいフィールドを定義し、特定のフィールドを必須として指定できます。詳細については、後で実際の Web サイトの実装方法を示すときに説明します。
図 8 リストのスキーマのカスタマイズ (クリックすると拡大画像が表示されます)
ListingSchema エンティティを追加するプロセスは既に説明したプロセスと同じですが、今度は異なるエンティティ (Kind) を使用します。ListingSchema を追加するコードを 図 9 に示します。For Sale Cars カテゴリのリスト スキーマを追加した後、Categories コンテナに異なる 2 種類のエンティティが含まれていることを確認します。まったく異なるデータが同じコンテナに格納されています。

図 9 スキーマの追加
listingSchemaEntity.Kind = "ListingSchema";
listingSchemaEntity.Properties = new Dictionary<string, object>();
listingSchemaEntity.Properties["ListingID"] = m_ListingID;
listingSchemaEntity.Properties["ListingName"] = m_ListingName;
int propCount = 0;
bool required = false;
foreach (DataGridViewRow row in dataGridView1.Rows) {
if (row.IsNewRow) continue;
propCount++;
listingSchemaEntity.Properties[string.Format ("Property{0}Name",propCount)] =
row.Cells["colFieldName"].Value.ToString();
listingSchemaEntity.Properties[string.Format ("Property{0}DataType", propCount)] =
row.Cells["colDataType"].Value.ToString();
if (row.Cells["colRequired"].Value == null) {
required = false;
}
else {
required = Convert.ToBoolean(row.Cells ["colRequired"].Value.ToString());
}
listingSchemaEntity.Properties[string.Format ("Property{0}Required", propCount)] = required;
}
if (propCount > 0) {
using (SSDSClient.SitkaSoapServiceClient ssdsProxy = new SSDSClient.SitkaSoapServiceClient()) {
//Set username and password for the service
...
//Set service scope to the main contoso classifieds
//authority. Point it to the categories container
...
//If this entity doesn't exist, create it
if (listingSchemaID == "" || listingSchemaID == null) {
ssdsProxy.Create(serviceScope, listingSchemaEntity);
}
//Otherwise update it
else {
serviceScope.EntityId = listingSchemaID;
ssdsProxy.Update(serviceScope, listingSchemaEntity);
}
}
MessageBox.Show("Custom Schema Saved", "Custom Schema Saved",
MessageBoxButtons.OK, MessageBoxIcon.Information);
this.Hide();
}
管理クライアントの最後の部分では、リスト カテゴリが削除されたときにカスタム リスト スキーマを削除します。エンティティとコンテナの削除については既に説明しましたが、そのときは削除するエンティティまたはコンテナの ID がわかっていました。スキーマを削除するには、最初に LINQ クエリを発行して、リスト カテゴリに関連付けられているすべてのリスト スキーマを取得し、それらを削除します。クエリは次のようになります。
string.Format(@"from e in entities
where e.Id == ""{0}"" select e", CategoryID);
クエリから ListingSchema エンティティの一覧が返されます。ここで必要なのは返されたエンティティを削除することだけです。DeleteListingSchema メソッドの全体を図 10 に示します。

図 10 DeleteListingSchema
//Retrieve the custom schema entity for this listing
string linqQuery = string.Format(
@"from e in entities where e.Id == ""{0}"" select e", CategoryID);
List<SSDSClient.Entity> entities =
ssdsProxy.Query(serviceScope, linqQuery);
foreach (SSDSClient.Entity entity in entities) {
//If more than 1 flex property on the entity, the
//header has listing categories attached to it.
if (entity.Properties.Count > 1) {
//Calculate the number of Listing Categories
int propCount = ((entity.Properties.Count - 1) / 2);
for (int x = 1; x < propCount + 1; x++) {
//Delete the listing schema for each listing
//in this category
DeleteListingSchema(entity.Properties["ListingCategoryID" +
x.ToString()].ToString());
}
}
}
Contoso Classifieds 管理クライアントは以上で完成です。次に、SSDS の REST インターフェイスを使用した Web サイトの実装に進みます。
Classifieds Web アプリケーション
既に説明したように、SSDS には REST インターフェイスと SOAP インターフェイス (Contoso Classifieds 管理クライアントで使用しました) を使用してアクセスできます。どちらのインターフェイスを選択しても概念と機能は同じですが、REST では HTTP (または HTTPS) の呼び出しを直接行います。
最初に、図 1 で示した Web アプリケーションのメイン インターフェイスを見直してください。リスト カテゴリを表すツリー ビュー、メインのコンテンツ領域、アプリケーションのサポート対象として現在構成されている全都市のデータリストがあります。それぞれについて、SSDS に対する ASP.NET アプリケーションを簡単に開発できることを説明します。
ツリー ビューの ASPX コードを見ると、それが非常に単純であることがわかります。
<h3>Groups</h3>
<asp:TreeView ID="TreeView1" runat="server"
onselectednodechanged="TreeView1_SelectedNodeChanged">
</asp:TreeView>
処理は、図 11 で示す分離コードで行われています。まず最初に関係するのは URI の作成であり、URI は Categories コンテナへのポインタです。データは都市に固有ではないので、メインの contosoclassifieds オーソリティの Categories コンテナに格納することにしました。

図 11 ツリー ビューの作成
TreeView1.Nodes.Clear();
appDataUri = string.Format(@"http://{0}.{1}{2}",
conSSDSAuthName, conSSDSUri, "Categories");
query = @"from e in entities where e.Kind == ""Category"" select e";
UriBuilder newUri = new UriBuilder(appDataUri);
newUri.Query = String.Format("q='{0}'", Uri.EscapeDataString(query));
string xmlResults =
HTTPHelper.GetHTTPWebRequest(newUri.Uri.ToString(),
new System.Net.NetworkCredential(conSSDSUsername, conSSDSPassword));
XmlDocument categoriesDoc = new XmlDocument();
categoriesDoc.LoadXml(xmlResults);
XmlNodeList nodeList = categoriesDoc.SelectNodes("//Category");
int nodeIndex = 0;
foreach (XmlNode node in nodeList) {
if (node.ChildNodes.Count > 3) {
int propCount = ((node.ChildNodes.Count - 1) / 2);
TreeNode tn = new TreeNode(node.ChildNodes[2].InnerText,
node.ChildNodes[0].InnerText);
TreeView1.Nodes.Add(tn);
for(int x=1;x<propCount;x++) {
tn = new TreeNode(node.ChildNodes[(x*2)+2].InnerText,
node.ChildNodes[(x*2)+1].InnerText);
TreeView1.Nodes[nodeIndex].ChildNodes.Add(tn);
}
}
else {
TreeNode tn = new TreeNode(node.ChildNodes[2].InnerText, node.ChildNodes[0].InnerText);
TreeView1.Nodes.Add(tn);
}
nodeIndex++;
}
次に、LINQ クエリを使用して Kind カテゴリのすべてのエンティティを取得し、UriBuilder オブジェクトを使用して集約し、必要な HTTP エスケープを追加しています。それから、GetHTTPWebRequest メソッドを呼び出し、エスケープした URI および SSDS のユーザー名とパスワードを含む NetworkCredential オブジェクトを渡します。GetHTTPWebRequest は、クエリの述部を満たすすべてのエンティティを含む EntitySet の文字列表現を返します。
GetHTTPWebRequest は、HTTP の細部の一部を隠す静的ヘルパ メソッドです。
WebRequest request =
HttpWebRequest.Create(Uri.EscapeUriString(serviceUri));
request.Credentials = requestCredential;
request.Method = "GET";
request.ContentType = XmlContentType;
// Get the response and read it in to a string.
using (HttpWebResponse response =
(HttpWebResponse)request.GetResponse()) {
return ReadResponse(response);
}
次に行う必要があるのは、新しい WebRequest オブジェクトの作成、渡された Credentials の設定、適切な HTTP 動詞の設定、ContentType の XML への設定、および GetResponse メソッドの呼び出しです。次に、ReadResponse を呼び出します。これはもう 1 つの静的ヘルパ メソッドであり、ストリーム リーダーを使用して HTTPResponse を読み取り、呼び出し元に返します。その後、分離コードは返された XML を取得して XmlDocument に読み込み、それを使用してツリー ビューを読み込みます。
クラスの逆シリアル化
これまでは、コードで単純に XML を手動で操作してきましたが、簡単にエンティティを取得してクラスに逆シリアル化できます。その場合、アプリケーションでは CityServed クラスを使用します。
Entity 基本クラスは、ごく基本的なクラスです。このクラスには、すべてのエンティティに共通である ID と Version の両方に使用するプロパティが含まれます。また、クラスをシリアル化可能にするために必要な属性も含まれます。
図 12 に示すように、CityServed は基本クラスの Entity を継承します。ジェネリック メソッドの Query は、CityServed 型のエンティティのリストを返します。
foreach (CityServed i in HTTPHelper.Query<CityServed>(
appDataUri, query, new System.Net.NetworkCredential(
conSSDSUsername, conSSDSPassword)))

図 12 CityServed
[XmlRoot(ElementName = "CityServed", Namespace = "")]
public class CityServed : Entity {
[XmlElement(ElementName = "AuthorityUri")]
public object AuthorityUriField;
[XmlElement(ElementName = "Name")]
public object NameField;
public override string ToString() {
return Name;
}
[XmlIgnore]
public string AuthorityUri {
get { return (string)AuthorityUriField; }
set { AuthorityUriField = value; }
}
[XmlIgnore]
public string Name {
get { return (string)NameField; }
set { NameField = value; }
}
}
この記事のコード サンプルに含まれる Query メソッドは単なるジェネリックな静的ヘルパ メソッドであり、前に使用したのと同じ GetHTTPWebRequest メソッドを呼び出します。Serialize メソッドと Deserialize メソッドを図 13 に示します。Query メソッドと Serialize および Deserialize メソッドを組み合わせて、厳密に型指定された .NET オブジェクトを使用したり SSDS に保存したりできます。

図 13 エンティティのシリアル化と逆シリアル化
private static T Deserialize<T>(Stream stm, string xmlPayload) {
XmlSerializer ser = new XmlSerializer(typeof(T));
T flex = (T)ser.Deserialize(stm);
XmlDocument xDom = new XmlDocument();
xDom.LoadXml(xmlPayload);
return flex;
}
public static T Deserialize<T>(String xmlPayload) {
using (MemoryStream stm = new MemoryStream()) {
Encoding encoding = new UTF8Encoding(false);
stm.Write(encoding.GetBytes(xmlPayload), 0, encoding.GetByteCount(xmlPayload));
stm.Position = 0;
return Deserialize<T>(stm, xmlPayload);
}
}
public static string Serialize<T>(T flex) {
using (MemoryStream stm = new MemoryStream()) {
Serialize(stm, flex);
stm.Position = 0;
using (StreamReader reader = new StreamReader(stm)) {
return reader.ReadToEnd();
}
}
}
private static void Serialize<T>(Stream stm, T flex) {
XmlSerializer ser = new XmlSerializer(typeof(T));
Encoding encoding = new UTF8Encoding(false);
XmlWriterSettings settings = new XmlWriterSettings();
settings.CloseOutput = false;
settings.ConformanceLevel = ConformanceLevel.Document;
settings.Encoding = encoding;
settings.Indent = true;
settings.OmitXmlDeclaration = true;
using (XmlWriter writer = XmlWriter.Create(stm, settings)) {
ser.Serialize(writer, flex);
}
}
カスタム リスト スキーマを使用する
既に説明したように、SSDS のエンティティは柔軟で、必要に応じて各エンティティの形態を自由に決めることができます。たとえば、Contoso Classifieds 管理クライアントでは、リスト カテゴリにスキーマを追加できました。また、SSDS からカスタム スキーマを取得し、システムへのリストの追加に使用できる入力フォームを動的に作成することもできます。
まず最初に、Field、Value、DataType、および Required という 4 つの列を含む DataTable を作成します。このテーブルは、管理者がリストに対して定義したフィールドを保持します。次に、クエリを発行し、リストに対してカスタム スキーマが定義されているかどうかを確認します。
string appDataUri = string.Format(@"http://{0}.{1}{2}",
conSSDSAuthName, conSSDSUri, "Categories");
string query = string.Format(
@"from e in entities where e[""ListingID""] == ""{0}"" select e",
listingCategoryID);
UriBuilder newUri = new UriBuilder(appDataUri);
newUri.Query = String.Format("q='{0}'", Uri.EscapeDataString(query));
string xmlResults = HTTPHelper.GetHTTPWebRequest(newUri.Uri.ToString(),
new System.Net.NetworkCredential(conSSDSUsername, conSSDSPassword));
XmlDocument categoriesDoc = new XmlDocument();
categoriesDoc.LoadXml(xmlResults);
XmlNodeList nodeList = categoriesDoc.SelectNodes("//ListingSchema");
返された XML を DataTable に読み込んで、DataList にバインドできます。
REST インターフェイスでは、エンティティの XML 表現を作成し、エンティティを挿入するコンテナに対して HTTP POST を発行する必要があります。エンティティを作成するのは簡単です。定型のエンティティを使用して各フィールドをループ処理し、XML ドキュメントにノードとして追加するだけです。
foreach (DataListItem fieldItem in dlAddFields.Items) {
Label lblField = (Label)fieldItem.FindControl("lblAddField");
TextBox tbItem = (TextBox)fieldItem.FindControl("txtAddValue");
Label lblFieldType = (Label)fieldItem.FindControl("lblAddFieldType");
entity = entity + String.Format(
@" <{0} xsi:type='x:{1}'>{2}</{0}>", lblField.Text.Replace(" ", ""),
lblFieldType.Text, tbItem.Text);
}
最後に、POST の対象となるコンテナを指す URI を作成して、POST を発行します。
string serviceUri = string.Format(@"http://{0}.{1}{2}",
postingAuthority, conSSDSUri, postingContainer);
HTTPHelper.PostHTTPWebRequest(serviceUri, entity,
new System.Net.NetworkCredential(
conSSDSUsername, conSSDSPassword));
今後の展望
SSDS 用の開発は簡単です。繰り返しておきたいのは、SSDS のベースになっているのは実績のある SQL Server と Windows Server® のテクノロジであるということです。SSDS の最初のベータ リリースで公開された機能はごく限られたものですが、今後は多数追加されていきます。現時点で使用できる機能のサブセットでも、ユーザーが対応する必要がある多くのシナリオに役立ちます。
さらに、SQL Server スイートの他の製品やツールにも SSDS のサポートが追加される予定です。そのため、開発に使用する言語が C#、Visual Basic
®、Java、Ruby、Microsoft Office Access
® のいずれであっても、SSDS とそのオープン プロトコルのサポートは、アプリケーションのデータを格納するための理想的な場所になります。詳細については、SSDS のサイト (
microsoft.com/sql/dataservices) にアクセスし、ベータ版にサインアップしてください。製品への追加を希望する機能がある場合は、
david.robinson@microsoft.com までご連絡ください。