[Azure] Table Storage にデータを保存

多量のデータを安価に取り扱うとしてたらAzure Table Strage一択になります。本日はレシートデータ（これから多量に保存されるだろうと思われるもの）を保存しながら利用方法を紹介します。

# TableStorage?

Table Storageとは何ぞや、というのはこちらを参照ください。

https://docs.microsoft.com/ja-jp/azure/storage/tables/table-storage-overview

# 設計のワンポイント

ポイントとしては、`RowKey`（ID）と`PartitionKey`という2軸があり、`PartitionKey`である程度データをグルーピングしておくこで取込パフォーマンスは著しくあがります。今回のようにレシートデータを保存する場合は`StoreId`を`PartiotionKey`とすることでStore単位である程度データをまとめて取得することができるようにしています。

# SDK

.NET用にライブラリーがありますのでそちらを利用します。NuGetはこちら。

https://www.nuget.org/packages/Azure.Data.Tables/

# Tableの作成

まずは一番上位にあるテーブルを作成します。`CreateTableIfNotExists`というこで、なければ新しく作成されることなく該当のテーブルを参照することができます。`TableItem`を返すファンクションにしておくことによって毎度これを呼べるようにしておきます。

```csharp
private TableItem CreateTable(string tableName)
{
   var serviceClient = new TableServiceClient(new Uri(_storageUri),
   new TableSharedKeyCredential(_storageAccountName, _storageAccountKey));
            
   TableItem table = serviceClient.CreateTableIfNotExists(tableName);

   return table;
}
```

# TableClientの作成

実際にテーブルに対してなにか操作したい場合は`TableClient`が必要となります。なので、こちらも`TableClient`を返すファンクションをここで書いておきます。

```csharp
private TableClient GetTableClient(string tableName)
{
    var tableClient = new TableClient(
    new Uri(_storageUri),
    tableName,
    new TableSharedKeyCredential(_storageAccountName, _storageAccountKey));

    // Create the table in the service.
    tableClient.Create();

    return tableClient;
}
```

# データの挿入

次はテーブルにデータを挿入します。データモデルは`ITableEntity`を継承します。

少し長いですが、これが実際につかったモデルになります。

```csharp
/// <summary>
    /// Receipt
    /// </summary>
    public class ReceiptModel : ITableEntity
    {
        public ReceiptModel(string id,
            string transactionType, string terminalTransactionId, DateTime terminalTransactionDateTime,
            string storeId, string terminalId, string staffId,
            decimal subTotal,
            decimal grandTotal
            )
        {
            RowKey = id;
            TransactionType = transactionType;
            TerminalTransactionId = terminalTransactionId;
            TerminalTransactionDateTime = terminalTransactionDateTime;
            PartitionKey = storeId;
            TerminalId = terminalId;
            StaffId = staffId;
            SubTotal = subTotal;
            GrandTotal = grandTotal;
        }

        // Id and Type
        public string RowKey { get; set; }
        public string TransactionType { get; set; }
        public string? CancelType { get; set; }
        public string TerminalTransactionId { get; set; }
        public DateTime TerminalTransactionDateTime { get; set; }
        public DateTimeOffset? Timestamp { get; set; }
        public ETag ETag { get; set; }

        // Store, Terminal, Staff
        public string PartitionKey { get; set; } // StoreId
        public string TerminalId { get; set; }
        public string StaffId { get; set; }

        // Customer
        public string? CustomerId { get; set; }
        public string? CustomerCode { get; set; }
        public string? CustomerGroup { get; set; }
        public DateTime TimeOfEnter { get; set; }
        public int NumberOfParty { get; set; }
        public string? CustomerMemo { get; set; }

        // Sub Total
        public decimal SubTotal { get; set; }
        public decimal SubTotalDiscountAmount { get; set; }
        public int SubTotalDiscountRate { get; set; }

        // Points
        public string? NameOfPoint { get; set; }
        public int PointsUsed { get; set; }
        public int PointsAdded { get; set; }
        public int TotalPointsAfetr { get; set; }
        public int TotalPointsBefore { get; set; }
        public PointAsTypes PointAs { get; set; }

        // Miles
        public string? NameOfMile { get; set; }
        public int MilesUsed { get; set; }
        public int MilesAdded { get; set; }
        public int MilesPointsAfetr { get; set; }
        public int MilesPointsBefore { get; set; }
        public string? MilesNote { get; set; }

        // Tax
        public int TaxPercentageIfInclude { get; set; }
        public int TaxPercentageIfExclude { get; set; }

        // Round or Not
        public int RoundingPosition { get; set; }
        public int RoundingMethod { get; set; }

        // Otehr Fee
        public decimal ShippingFee { get; set; }
        public decimal ServiceFee { get; set; }
        public decimal OtherFee { get; set; }

        // Grand Total
        public decimal GrandTotal { get; set; }

        // Deposit and Change
        public DepositMethodTypes DespoitMethod { get; set; }
        public decimal Deposit { get; set; }
        public decimal Change { get; set; }

        // Closing
        public bool IsClosed { get; set; }
        public DateTime ClosingDate { get; set; }

        // Memo
        public string? Memo { get; set; }
        public string? Tags { get; set; }
        public string? Barcode { get; set; }

        // Designs
        public string? BackgroundColor { get; set; }
        public string? ForeColor { get; set; }
        public string? HeaderContentHtml { get; set; }
        public string? AfterItemsContentHtml { get; set; }
        public string? AfterGrandTotalContentHtml { get; set; }
        public string? AfterPaymentMethodContentHtml { get; set; }
        public string? FooterContentHtml { get; set; }
    }
```

大元のクラスは下記です。`AddEntity`よりも追加と更新を適えた`UpsertEntity`を使っています。

```csharp
 public async Task<ReceiptModel?> UpsertReceipt(ReceiptModel receipt)
{

    // Create Table.
    var tableItem = CreateTable("ReceiptByStores");

    // Get Table Client.
    var tableClient = GetTableClient("ReceiptByStores");

    // Insert.
    var result = await tableClient.UpsertEntityAsync(receipt);

    if (result.IsError)
    {
        // If error, I ll return null.
        return null;
    }

    // Return receipt data that was inserted.
    return receipt;

}
```

# サンプルコード

MSからのサンプルはこちらにありますのでご参照ください。

https://github.com/Azure/azure-sdk-for-net/tree/main/sdk/tables/Azure.Data.Tables/samples

# メンバー募集中

一緒に開発するメンバー募集しています。副業でもOK（むしろ歓迎）、フルリモートです。