Azure Table Storageの古いライブラリを使ってみました

**この記事は NEXTSCAPE Advent Calendar 2024 の11日目の記事です** 
https://qiita.com/advent-calendar/2024/nextscape

Azure Table Storageと.NETの記事です。
Azure Table Storage
https://azure.microsoft.com/ja-jp/products/storage/tables/
# やりたかったこと
Azure Table Storage を古いライブラリで使用する案件がありました。テーブルストレージはこれまでも頻繁に使っていたのですが、古いライブラリは資料が見つかりづらく、今後も増える事は無いでしょう。なので基本的な使い方について、2024年12月の今の時点でおさらいしました。
# 前提条件
- [VSCodeと.NET](http://VSCodeと.NET) 6 を使用し、コンソールアプリケーションを作成して確認します。
    - バージョンが推奨じゃないとか、細かい事は無視します。動作を確認するのが目的です。
- テーブルを確認するために Azure Storage Explorer を使用します。
    - https://azure.microsoft.com/ja-jp/products/storage/storage-explorer/
- githubにソースを公開しておきましたので、自分で試せると思います。細かい動作は自分で試してみましょう。多分、未来の私は忘れてるので聞かれても答えられるか自信はありません。
- ストレージのエミュレータは「Azurite」を使用してます。
    - https://learn.microsoft.com/ja-jp/azure/storage/common/storage-use-azurite?tabs=visual-studio%2Cblob-storage
    - いつの間にかAzureStorage Emulator は非推奨となっておりまして、自分が歳を取ったのだと感じます。軽く調べたら node.js で入れなさいとあったのですが、VSCodeの拡張機能でAzuriteそのものズバリなのがありました。ステータスバーでクリックすれば起動できるのでオススメです。
    - Visual Studio2022などを入れてれば勝手に入ってますが、VSCodeの方が手軽に使えてオススメです。
# 使用したライブラリ
## WindowsAzure.Storage 9.3.3
https://www.nuget.org/packages/WindowsAzure.Storage/9.3.3
非推奨のライブラリです。しかし2011年から2018年という長い間更新され続けたライブラリです。息が長いからか古いシステムではかなり使われているようです。自分が古い人間だからか、十分に使いやすく感じました。
## Microsoft.Azure.Cosmos.Table 1.0.8
https://www.nuget.org/packages/Microsoft.Azure.Cosmos.Table/1.0.8
非推奨のライブラリです。2018年から2019年頃まで更新されていたライブラリです。2年間という短い期間なのですが、意外と使われているようです。1.0.8のダウンロード数が2300万近く。なかなかの人気です。
WindowsAzure.Storage と比べて、同期処理を行えるようになってます。既存の処理はほぼ踏襲されており、WindowsAzure.Storageで作ったソースはほぼまるごと使えるはずです。
# 断念したライブラリ
## Microsoft.Azure.CosmosDB.Table 2.1.2
https://www.nuget.org/packages/Microsoft.Azure.CosmosDB.Table/2.1.2
非推奨のライブラリです。**極めて不遇なライブラリです**。Microsoft.Azure.Cosmos.Table と混同されがちで、CopilotにTable Storageのライブラリを質問するとかなりの頻度で出てきます。なのに使い方を質問すると **「Microsoft.Azure.CosmosDB.Table ライブラリは廃止されており、代わりに Azure.Data.Tables ライブラリが使用されるようになっています。こちらがAzure.Data.Tables ライブラリを使用した例です」** と言われる始末。ライブラリをダウンロードして中身を解き明かそうとしたのですが、不毛な気持ちと納期に勝てなくて断念しました。
# 細かいトラブル
大騒ぎするほどではないけれど、つまづいたり疑問を感じた所を書いておきます。こういうのあんまり共有されないんですよね。
### テーブルを削除した後、40秒以内に同じテーブルを作成すると例外が発生する
これはエミュレータでは発生しません。そもそもテーブルを削除したり作り直したりするのを頻繁に行うようなシステムは滅多にないので、ほとんどの人は遭遇するチャンスに恵まれないと思います。
以下はWindowsAzure.Storage 9.3.3 、 Microsoft.WindowsAzure.Storage.Table.CloudTableClient を使用してテーブルを作成しようとした所です。”Microsoft.WindowsAzure.Storage.StorageException : Conflict” という例外が見えます。
![40秒以内に同じテーブルを作成するとconflict.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217086/10ea1491-b7ce-49d5-eaee-652c2b7afdea.png)
この原因はAzure Storage Tableがテーブルの削除を完了するために少なくとも40秒はかかるためです。MSDNのドキュメントです。
https://learn.microsoft.com/en-us/rest/api/storageservices/Delete-Table?redirectedfrom=MSDN
Azure側の問題なので、どのライブラリでも同じ事が発生します。まあ実際には30秒くらいでも正常に作成できちゃう事があったのですが、目安として40秒は必要と思っておきましょう。
Azure側の問題なので、どのライブラリでも同じ事が発生します。
まあ実際には30秒くらいでも正常に作成できちゃう事があったのですが、目安として40秒は必要と思っておきましょう。
# 削除したはずの列がAzure Storage Explorerで見えてしまう
Azure Storage Explorerでテーブルを開いている様子です。右端の「FakeRegisterDate」がnullになっているのが解ります。
![削除したはずの列が見えてしまう01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217086/b456ddd8-085b-d36b-3b1e-6191988f3992.png)
Azure Table Storageはnullのデータを保持しません。しかし列が見えています。Azure Storage Explorerがキャッシュしてるのかな？と思いきや、接続をデタッチしてから接続しなおしても列が見えています。PowerShellのGet-AzTableRowで取得してみると、こちらは列が削除されています。
![削除したはずの列が見えてしまう02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217086/80ecf20e-21b5-0382-25fc-cd272f5d0960.png)
これが何故発生するのかは解りませんでした。この動作がトラブルの種になるような状況は無かろうとと思ってたら、どうもnullのカラムを検索する時に面倒なようです。海外の方が解決策を見つけてました。数値として比較した上で、notを加えて検索するのだそうです。
https://blog.atwork.at/post/Null-values-in-Azure-Table-Storage-Explorer
これが役に立つ状況ってそうそう無いと思うのですが、覚えておいて損は無さそうです。
## TableEntityのTimeStampのOffsetが一定ではない
TableStorageのライブラリは、InsertやRetrieveでテーブルを返却します。当然の動作なのですが、同じエンティティのはずなのにOffsetが異なります。画像はデバッグ途中のウォッチ式です。
Insertした結果のTimestampはOffsetが+00:00:00です。
しかしRetrieveした結果のTimestampはOffsetが+09:00:00です。
Tickは同じなので、表現している日時は一致しているはずです。
流石に重箱の隅であろうと思います。ただ恐らくライブラリの問題であろうと思われるので、ライブラリの完成度に不安を持ちます。Azure Table Storageで日時を扱うためにはDateTimeOffsetである必要があるという根拠の一つにはなるかもしれません。
![Timespanが一定でない.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217086/f8f8ee80-0345-50ae-9249-133a9f11e001.png)
# 実装上で気をつけた方が良いこと
### ETagは厳密に。”*”は控えた方が良い
エンティティを識別するためにはPartitionKeyとRowKeyがあれば良いのですが、これがいつのタイミングで更新されたオブジェクトなのかを識別するためにETagが必要です。
ETagに”*”というワイルドカードを与えて実装しているのを見かけましたが、これは控えた方が良いです。ETagは「このエンティティが正常であるか」の識別に使用できます。なんでもETagを”*”としてしまっては、誤った更新を行っても気がつくチャンスが失われます。潜在的なバグの温床になるでしょう。今回のサンプルのように複数のエンティティを扱うようなソースだと解りやすいと思います。
次の画像は、Mergeを実行した後のエンティティのETagを表示した物です。引数として与えたcustomerEntity と、戻り値のreplacedEntityのETagが一致しています。Mergeを実行した際に引数のETagも更新しているためです。これは引数のETagを”*”とした場合も同様で、引数のETagに新たなETagが入ります。
![ETagはワイルドカードしない方がいい01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217086/d12f598d-40ab-8025-9897-47d067a17a67.png)
ETagが無効な値（他のEntityのETagなど）である場合、例外が発生します。
> Exception has occurred: CLR/System.AggregateException
An unhandled exception of type 'System.AggregateException' occurred in System.Private.CoreLib.dll: 'One or more errors occurred.'
Inner exceptions found, see $exception in variables window for more details.
Innermost exception 	 Microsoft.WindowsAzure.Storage.StorageException : Precondition Failed
at Microsoft.WindowsAzure.Storage.Core.Executor.Executor.<ExecuteAsyncInternal>d__4`1.MoveNext()
![ETagはワイルドカードしない方がいい02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217086/43c58335-31f7-b69a-ae0b-0b4fe4925414.png)
# 実装したソース
https://github.com/currysita/advent_calendar_2024
こちらに公開しておきました。接続文字列はローカルのエミュレータに向いてますので、適宜書き替えて実行してみてください。
Microsoft.Azure.Cosmos.Table は同期処理で実装しています。それ以外はほぼ全て同じ実装をしています。
TableEntityを継承して自作のEntityクラスを作成しています。できれば DynamicTableEntity を使用したサンプルも作りたかったのですが、体力と納期に負けました。来年はもう少し健康になりたいです。世の中のほとんどのプロジェクトでは自作のエンティティを使ってるような気がしますので、まあこれでヨシとします。
# 終わりに
## 感想
生成AIに質問を投げながら作ったのですが、大変に便利です。誤ったソースを頻繁に出してくれるのですが、恐らくは他のライブラリでこういう使い方をしてたのだろうとか想像するのも楽しいです。ピンポイントで質問を投げて返してくれるのがありがたいですね。「非推奨なので他のライブラリを使いましょう」と言われるたびに、なんとも微妙な気持ちになりました。
## 他のネタの候補
他のネタの候補としては以下の通りです。もし希望があったら教えてください。大体、やっているうちに目的と手段が入れ替わって違う物が出来上がりますが、許して頂きたいです。
- Azure上でWebjobsアプリケーションのTimerトリガーが「動かない」状況を作る
- msbuildによるApp.configの置換の動作が、プロジェクトファイルの形式によっては正しく動作しない事を確認する
- Unity Containerの基本的な使い方と、今まで見かけた宜しく無さそうな実装