Azure Mobile Services .NET バックエンドで Table Storage によるページングのサポートを強化
このポストは、10 月 10 日に投稿した Better support for paging with Table Storage in Azure Mobile Services .NET backend の翻訳です。
Azure Mobile Services で .NET バックエンドがリリースされてから、サービス開始以来使用されていた SQL Azure ストレージに加えて、Azure Table Storage および MongoDB データベースにもデータを格納できるようになりました。しかし、Table Storage には Table に存在するアイテム数が多い場合に弱点がありました。Table Storage では SQL クエリのようにアイテムの「スキップ」ができないため、アプリケーションが Table 内のすべてのアイテムを移動する場合にページングを使用することができませんでした。今回の更新により、スキップ/取得の組み合わせを使用する代わりに、Table Storage で「後続リンク (Continuation link)」(HTTP の Link ヘッダー (英語) として公開) を使用してページングを実行できるようになりました。後続リンクにはクライアントがアイテムを追加取得する際に使用するリンクが含まれていて、要求の中にすべてのアイテムが含まれていない場合の応答に使用されます。この後続リンクはクライアントには公開されていなかったため、これまでは大規模な Table を使用することはできませんでした。
.NET バックエンド パッケージの最新リリース (バージョン 1.0.405、英語) およびクライアント SDK の更新版 (現時点では Managed SDK バージョン 1.2.5 (英語)、近日中に更新予定) を使用すると、クライアントで後続リンクを取得しフォローできるため、Table Storage を利用しているデータのページングを適切に実行できます。ここからは具体的な使用例をご紹介します。
データの取得とページング
サーバー側ではコードを変更する必要はありません。まず Azure Mobile Services .NET Backend Azure Storage Extension の NuGet パッケージを使用して更新します。その後、戻されたアイテムよりも多くのアイテムが要求に含まれる可能性がある場合は後続リンクを取得します。サービスのセットアップが完了していない場合は、この記事の追加資料の項目をご覧ください。サービスのセットアップが完了したら、次はクライアントのコードについて説明します。先に述べたとおり、現時点で完全にサポートされているのは Managed SDK のみであるため、今回の説明ではこれを使用します。このシナリオでは、クライアント内でクラスを使用してユーザー リスト (非常に小規模な連絡先リスト) を戻す単純なコントローラーを使用します。その定義は下記のとおりです。
public class Person
{
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("age")]
public int Age { get; set; }
}
既定では、Table コントローラーの読み込み操作では最大 50 個のアイテムが戻されます。Table Storage に格納されているアイテム数がそれよりも多い場合、クライアントは ToListAsync メソッドまたは ToEnumerableAsync メソッドの結果を IQueryResultEnumerable<T> インターフェイスにキャストして、追加を要求する必要があります。次のコードは Table のすべての要素を取り扱う場合の例です。
public async Task<double> CalculateAverageAge()
{
var client = new MobileServiceClient(AppUrl, AppKey);
var table = client.GetTable<Person>();
var sum = 0.0;
var count = 0;
var items = await table.Take(10).ToEnumerableAsync();
while (items != null && items.Count() != 0)
{
count += items.Count();
sum += Enumerable.Sum(items, i => i.Age);
var queryResult = items as IQueryResultEnumerable<Person>;
if (queryResult != null && queryResult.NextLink != null)
{
items = await table.ReadAsync<Person>(queryResult.NextLink);
}
else
{
items = null;
}
}
return sum / count;
}
JSON の Table を使用する場合 (つまり、Person などの型ではなく JToken ファミリを使用する場合)、ReadAsync メソッドの wrapResult パラメーターに true を渡すと、応答をオブジェクトにラップするように要求できます。その場合、結果は次の 2 つのプロパティを持つオブジェクトにラップされます。その 1 つが results で、サービスから戻された実際の結果の配列が含まれます。もう 1 つが nextLink で、HTTP 応答に継続トークンを含む Link ヘッダーが存在する場合に存在します。この継続トークンは ReadAsync メソッドに渡され、Table から次のエントリのセットを取得する際に使用されます。
public async Task<double> CalculateAverageAge2()
{
var client = new MobileServiceClient(AppUrl, AppKey);
var table = client.GetTable("person");
var sum = 0.0;
var count = 0;
var response = await table.ReadAsync("$top=10", null, wrapResult: true);
while (response != null)
{
var items = (JArray)response["results"];
var nextLink = (string)response["nextLink"];
count += items.Count();
sum += Enumerable.Sum(items, i => (int)i["age"]);
if (nextLink != null)
{
response = await table.ReadAsync(nextLink, null, true);
}
else
{
response = null;
}
}
return sum / count;
}
後続リンクを使用する場合、これを Table のオブジェクトの read メソッドに渡すことで、クライアントは Azure Storage 内の Table に存在するすべてのアイテムを確認できます。
追加資料: Azure Storage の Table を使用してサービスをセットアップする
Table Storage をバックエンドに使用するコントローラーでのサービスのセットアップがまだ完了していない場合、次の手順に従って実施します。
Storage アカウントをセットアップする
Table Storage で使用する Azure Storage アカウントのセットアップが完了していない場合、まずこれを行います。データを Table に保存したり Table から取得したりするには、Azure Storage アカウントが必要です。アカウントの作成方法については、Storage アカウントの作成方法に関するチュートリアルを参照してください。
アカウントのセットアップが完了したら、Mobile Services と Storage アカウントの通信方法を指定するために、アカウント名とアクセス キーを取得する必要があります。このキーを取得するには、従来のポータルの場合は [Quickstart] または [DASHBOARD] タブに移動し、[MANAGE ACCESS KEYS] オプションを選択します。
新しいポータルの場合は下記のとおりです。
アカウント名とキーを取得したら、次はお客様の Mobile Services に移動します。
サービスをセットアップする
お客様の Mobile Services のデータ保存に Azure Storage を使用するには、Azure Mobile Services .NET Backend Azure Storage Extension を使用する必要があります。サービスのプロジェクトを右クリックして [Manage NuGet Packages…] を選択して、「mobileservices.backend」で検索します。前述 (および下図) のパッケージを選択し、[Install] をクリックします。
パッケージのインストールが完了したら、次にサービスを作成します。この例では、下記のように、友人の情報を格納する非常に簡単な Table を作成し、Person クラスを定義します。EntityData 基本クラス (通常は Entity Framework または SQL をベースとするプロジェクトで使用する) ではなく、StorageData 基本クラスを使用することにご注目ください。これは、パーティション キーや行キーを Azure Table Storage で定義する際に使用されます。
public class Person : StorageData
{
public string Name { get; set; }
public int Age { get; set; }
}
次に、Storage アカウントの接続文字列をサービスに追加します。ここでは web.config ファイルか、ポータルの [configure] タブの接続文字列のいずれかを設定できます。前者は安全性の面で劣りますが、取り扱いが簡単で開発に適していてローカルでのデバッグが可能です。後者は、一般ユーザーがソース コードにアクセスして見ることができないため安全性は高いですが、Azure でデプロイされたサービス以外では使用できません。ここでは、説明を簡単にするために私の Web.config を使用して設定します。
<connectionStrings>
<add name="MS_TableConnectionString"
connectionString="<the actual value>"
providerName="System.Data.SqlClient" />
<add name="My_StorageConnectionString"
connectionString="DefaultEndpointsProtocol=https;AccountName=<the account>;AccountKey=<the key>;"/>
</connectionStrings>
最後に、Storage アカウント内の Table のデータを Mobile Services の Table として公開するためのコントローラーのクラスを作成します。この実装は、ポータルの [Quickstart] ページからのダウンロード リンクで生成されたものや Entity Framework をベースとするデータの Visual Studio 用テンプレートの場合とほぼ同じですが、下記の点が異なります。
- ドメイン マネージャーの型は StorageDomainManager<T> になります。これは、Mobile Services の Table とバックエンドのデータ ストア (Azure Storage) の間をマッピングするものです。
- Azure Storage の Table ではクエリ機能の一部がサポートされておらず、SQL Database では IQueryable<T> が戻されますが、これは使用できません。しかし、基本データ型の TableController<T> には、Storage Table で使用可能な QueryAsync メソッド (複数のアイテムのクエリに使用) と LookupAsync メソッド (単一のアイテムのクエリに使用) が存在します。
この例で使用するコントローラーのクラスの完全なコードは、次のようになります。
public class PersonController : TableController<Person>
{
protected override void Initialize(HttpControllerContext controllerContext)
{
base.Initialize(controllerContext);
var tableName = controllerContext.ControllerDescriptor.ControllerName.ToLowerInvariant();
var connStringName = "My_StorageConnectionString";
DomainManager = new StorageDomainManager<Person>(connStringName, tableName, Request, Services);
}
// GET tables/Person
public Task<IEnumerable<Person>> GetAllPerson(ODataQueryOptions queryOptions)
{
return base.QueryAsync(queryOptions);
}
// GET tables/Person/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<SingleResult<Person>> GetPerson(string id)
{
return base.LookupAsync(id);
}
// PATCH tables/Person/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task<Person> PatchPerson(string id, Delta<Person> patch)
{
return UpdateAsync(id, patch);
}
// POST tables/Person/48D68C86-6EA6-4C25-AA33-223FC9A27959
public async Task<IHttpActionResult> PostPerson(Person item)
{
Person current = await InsertAsync(item);
return CreatedAtRoute("Tables", new { id = current.Id }, current);
}
// DELETE tables/Person/48D68C86-6EA6-4C25-AA33-223FC9A27959
public Task DeletePerson(string id)
{
return DeleteAsync(id);
}
}
ここで、lookup、delete、update の各メソッドは、クライアント SDK とのコントラクトの一部として String 型の ID を取得することにご注意ください。Table に格納されているアイテムの ID は、パーティション キーと行キーの 2 つの部分から構成されています。この 2 つをマージするには、ID が <パーティション キー>,<行キー> という形であることを示すマッピングを定義します (必要に応じて、このキーを一重引用符で囲むこともできます)。上記のコードでサービスを実行する場合、下記のような要求を含むアイテムを挿入することができます。
POST /tables/person HTTP/1.1
X-ZUMO-APPLICATION: <the app key>
Content-Type: application/json; charset=utf-8
Host: mobile-service-name.azure-mobile.net
Content-Length: 75
{
"id": "partition,row1082",
"name": "dibika lyzufu",
"age": 64
}
まとめ
説明は以上です。後続リンクの取得は、現時点では管理されたクライアント プラットフォームでのみサポートされていますが、今後他のプラットフォームでもご利用いただけるようになる予定です。この記事で使用したコードは、NetBackendWithTableStorage ディレクトリ (英語) の mobile-services-samples リポジトリ (英語) からダウンロードできます。
いつものお願いではありますが、ご意見やご要望がございましたらこの記事のコメント欄までお寄せください。また Twitter (@AzureMobile) や MSDN のフォーラムでも受け付けております。