しばやん雑記

Azure Serverless とメイドさんが大好きなフリーランスのプログラマーのブログ

新しい Azure Table SDK がリリースされていたので試した

Cosmos DB の Premium Table な SDK を使えと言われ続けてきた Table Storage ですが、予告されていた通りに新しい Azure SDK の仕様に則った専用の SDK が公開されました。

Blobs や Queues と同じような API になっているので、特にドキュメントを読まずとも IntelliSense だけを見て利用できました。機能は今のところ最低限という感じです。

ドキュメントとサンプルコードは Azure SDK の repo に用意されていますが、用意されている API が分かりやすいので自分は読む必要なかったです。

Azure Functions 周りが v11 SDK ベースになっているのであまり移行する気にはならないですが、将来的には Storage Extension も新しい Azure SDK ベースになるはずなので軽く触っておきました。

基本となる Table へのエンティティ追加を行うコードを書きました。特に説明は必要ないでしょう。

class Program
{
    static async Task Main(string[] args)
    {
        var tableServiceClient = new TableServiceClient("UseDevelopmentStorage=true");
        var tableClient = tableServiceClient.GetTableClient("sample");

        await tableClient.CreateIfNotExistsAsync();

        for (int i = 0; i < 100; i++)
        {
            await tableClient.AddEntityAsync(new TableEntity("sample", Guid.NewGuid().ToString())
            {
                { "IntValue", i },
                { "StrValue", "buchizo" }
            });
        }
    }
}

古い SDK にも TableEntity クラスは存在していましたが、所詮 PartitionKeyRowKey などの必須プロパティが実装されているだけのクラスでした。

しかし新しい SDK では IDictionary<string, object> が実装されているので、専用のエンティティクラスを用意する必要なくカジュアルに扱えるようになっています。

上のコードを実行すると、Table Storage にエンティティが書き込まれることが確認できます。

f:id:shiba-yan:20200912171747p:plain

古の WindowsAzure.Storage の Table SDK よりは使いやすいなと思いました。TableEntity を使えば割と雑にデータの読み書きが出来るのは結構好きな感じです。

今回は追加を行いましたが、基本的な CRUD な API は用意されているので必要に応じて使えば良いです。

f:id:shiba-yan:20200912175114p:plain

後は Table Storage からのデータ読みこみを行っておきます。PartitionKeyRowKey で読み込む方法ではなく、任意のフィルタリングを行って読み込む方法を試しておきます。

とりあえずシンプルに PartitionKey を指定して取得するコードを書きました。

型引数は必須なので TableEntity を最低でも指定する必要がありますが、Indexer を使ってカラム名でアクセス出来るので便利です。最近の SDK らしく非同期版は IAsyncEnumerable<T> を返します。

class Program
{
    static async Task Main(string[] args)
    {
        var tableServiceClient = new TableServiceClient("UseDevelopmentStorage=true");
        var tableClient = tableServiceClient.GetTableClient("sample");

        await tableClient.CreateIfNotExistsAsync();

        var result = tableClient.QueryAsync<TableEntity>("PartitionKey eq 'sample'");

        await foreach (var entity in result)
        {
            Console.WriteLine($"{entity.PartitionKey},{entity.RowKey},{entity["IntValue"]},{entity["StrValue"]}");
        }
    }
}

実行すると先ほど追加したエンティティが取得されて、コンソール画面に表示されます。簡単ですね。

f:id:shiba-yan:20200912171758p:plain

使用できるフィルタ式はドキュメントに書いてありますが、OData で書く必要があるので正直面倒です。

クライアントサイドでのフィルタリングは行われないようなので、安心して使えます。一応 LINQ も使えますが、エンティティクラスを定義すると使いやすいです。

エンティティクラスを定義して使う

古の SDK のようにエンティティクラスを定義すると、タイプセーフに値を扱えます。この時 TableEntity を継承するのではなく ITableEntity インターフェースを実装すればよいです。

public class SampleEntity : ITableEntity
{
    public string PartitionKey { get; set; }
    public string RowKey { get; set; }
    public DateTimeOffset? Timestamp { get; set; }
    public ETag ETag { get; set; }
    public int IntValue { get; set; }
    public string StrValue { get; set; }
}

データの追加は TableEntity の時と同様に行えます。こちらの方が型が決まっているので安全に扱えます。

class Program
{
    static async Task Main(string[] args)
    {
        var tableServiceClient = new TableServiceClient("UseDevelopmentStorage=true");
        var tableClient = tableServiceClient.GetTableClient("sample");

        await tableClient.CreateIfNotExistsAsync();

        for (int i = 0; i < 100; i++)
        {
            await tableClient.AddEntityAsync(new SampleEntity
            {
                PartitionKey = "sample",
                RowKey = Guid.NewGuid().ToString(),
                IntValue = i,
                StrValue = "kazuakix"
            });
        }
    }
}

エンティティクラスを定義しておくと、LINQ が使いやすくなります。TableEntity の時も Indexer でカラムを参照できますが、キャストが必要になるので冗長になります。

最初のサンプルのように PartitionKey で絞り込むクエリは以下のように書けます。対応していないクエリを書いた場合には例外になります。

class Program
{
    static async Task Main(string[] args)
    {
        var tableServiceClient = new TableServiceClient("UseDevelopmentStorage=true");
        var tableClient = tableServiceClient.GetTableClient("sample");

        await tableClient.CreateIfNotExistsAsync();

        var result = tableClient.QueryAsync<SampleEntity>(x => x.PartitionKey == "sample");

        await foreach (var entity in result)
        {
            Console.WriteLine($"{entity.PartitionKey},{entity.RowKey},{entity.IntValue},{entity.StrValue}");
        }
    }
}

まだ Batch や Entity Group Transaction は実装されていないですが、最近はこの辺りの機能が必要なら素直に Cosmos DB を使った方が良いと思うので、個人的にはあまり重要視していません。

Durable Functions が古い SDK に依存せざるを得ない状態になっているので、新しい Table SDK によって依存関係が整理されることを期待しています。