Azure Table Storage API 泛型設計
最近有機會使用到Azure Table Storage服務,Table Storage是早期Azure儲存體一種NoSQL儲存方式,程式開發上花點時間看看官網的文件,跟著範例做一下,大致上不會有太大問題。不過等真的要使用時會發現,如果按照範例程式的寫法,基本上是一整個寫死,沒有彈性可言。
建立Entity(實體)
Table Storage 的 SDK 設計是你在呼叫 Table API 之前必須傳入要儲存的 Entity(實體),如果讀者熟悉 Entity Framework 的話,可以想成 Entity Framework 的 Entity,如官網範例的 CustomerEntity
:
public class CustomerEntity : TableEntity { public CustomerEntity(string lastName, string firstName) { this.PartitionKey = lastName; this.RowKey = firstName; } public CustomerEntity() { } public string Email { get; set; } public string PhoneNumber { get; set; } }
然後進行CRUD操作時都是以 Entity 為單位,例如查詢:
// Create a new customer entity. CustomerEntity customer1 = new CustomerEntity("Harp", "Walter"); customer1.Email = "Walter@contoso.com"; customer1.PhoneNumber = "425-555-0101"; // Create the TableOperation object that inserts the customer entity. TableOperation insertOperation = TableOperation.Insert(customer1); // Execute the insert operation. table.Execute(insertOperation);
在正式使用碰到的問題就是,我們通常針對這類操作的 API 會先抽離出來給大家共用,但問題是,此 Table Storage API 並不會事前知道你所要操作的 Entity 格式,Entity 格式應是留給使用端去定義,這樣才能發揮 NoSQL 的長處,如果一開始定義寫死了,那就和關連式資料庫沒有差別了。
針對上述問題,我參考官網的範例寫了個泛型版本的 Table Storage API 版本,透過泛型就能輕鬆解決 Entity 型別的問題。
/// <summary> /// Table Storage /// </summary> public class TableStorage<T> where T : TableEntity, new() { private string tableConn; private string tableName; private CloudTable table; /// <summary> /// 初始化 Table 物件。 /// </summary> /// <param name="tableConn">連線字串</param> /// <param name="tableName">存取表格名稱</param> public TableStorage(string tableConn, string tableName) { this.tableConn = tableConn; this.tableName = tableName; CloudStorageAccount storageAccount = CloudStorageAccount.Parse(tableConn); CloudTableClient tableClient = storageAccount.CreateCloudTableClient(); table = tableClient.GetTableReference(tableName); table.CreateIfNotExists(); } /// <summary> /// 新增資料至Table儲存區。 /// 注意,不能存放重覆key值資料。 /// </summary> /// <param name="entity"></param> /// <returns></returns> public string Insert(T entity) { TableOperation insertOperation = TableOperation.Insert(entity); try { table.Execute(insertOperation); } catch (Exception ex) { return $"Error:{ex.Message}"; } return "OK"; } /// <summary> /// 新增或覆寫資料至Table儲存區。 /// </summary> /// <param name="entity"></param> /// <returns></returns> public string InsertOrReplace(T entity) { try { TableResult retrievedResult = GetServerTableResult(entity); T updateEntity = (T)retrievedResult.Result; if (updateEntity != null) { // Replace updateEntity = entity; TableOperation insertOrReplaceOperation = TableOperation.InsertOrReplace(updateEntity); try { table.Execute(insertOrReplaceOperation); return "OK"; } catch (Exception ex) { return $"Error:{ex.Message}"; } } else { // Insert TableOperation insertOrReplaceOperation = TableOperation.InsertOrReplace(entity); try { table.Execute(insertOrReplaceOperation); return "OK"; } catch (Exception ex) { return $"Error:{ex.Message}"; } } } catch (Exception ex) { return $"Error:{ex.Message}"; } } /// <summary> /// 批次新增資料至Table儲存區。 /// </summary> /// <param name="entities">欲快取的集合</param> /// <returns></returns> public string BatchInsert(IEnumerable<T> entities) { TableBatchOperation batchOperation = new TableBatchOperation(); foreach (var cache in entities) { batchOperation.Insert(cache); } try { table.ExecuteBatch(batchOperation); } catch (Exception ex) { return $"Error:{ex.Message}"; } return "OK"; } /// <summary> /// 取得 table 所有集合。 /// </summary> /// <returns></returns> public IEnumerable<T> GetAll() { TableQuery<T> query = new TableQuery<T>().Where( TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, tableName)); return table.ExecuteQuery(query); } /// <summary> /// 以非同步取得大量(1000個以上)實體。 /// </summary> /// <returns></returns> public async Task<IEnumerable<T>> GetAllAsync() { TableQuery<T> tableQuery = new TableQuery<T>(); TableContinuationToken continuationToken = null; do { // 接收一個段落資料(1000以上實體) TableQuerySegment<T> tableQueryResult = await table.ExecuteQuerySegmentedAsync(tableQuery, continuationToken); continuationToken = tableQueryResult.ContinuationToken; return tableQueryResult.Results; } while (continuationToken != null); } /// <summary> /// 以 rowkey 為關鍵字,取得相關集合。 /// </summary> /// <param name="rowkey"></param> /// <returns></returns> public IEnumerable<T> GetRange(string rowkey) { TableQuery<T> rangeQuery = new TableQuery<T>().Where( TableQuery.CombineFilters( TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, tableName), TableOperators.And, TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.LessThan, rowkey))); return table.ExecuteQuery(rangeQuery); } /// <summary> /// 取得單一實體 /// </summary> /// <param name="entity"></param> /// <returns></returns> public T GetSingle(T entity) { TableResult retrievedResult = GetServerTableResult(entity); T singleData = (T)retrievedResult.Result; if (singleData != null) { return singleData; } else { return null; } } /// <summary> /// 刪除實體 /// </summary> /// <param name="entity"></param> /// <returns></returns> public string Delete(T entity) { TableResult retrievedResult = GetServerTableResult(entity); T deleteEntity = (T)retrievedResult.Result; if (deleteEntity != null) { TableOperation deleteOperation = TableOperation.Delete(deleteEntity); table.Execute(deleteOperation); return "OK"; } else { return "Error: deleteEntity is null."; } } /// <summary> /// 刪除資料表。 /// 在刪除後一段時間內,將無法重新建立同名表格。 /// </summary> /// <returns></returns> public string DeleteTable() { try { table.DeleteIfExists(); return "OK"; } catch (Exception ex) { return $"Error:{ex.Message}"; } } /// <summary> /// 取回伺服器上的Table結果集 /// </summary> /// <param name="tableData"></param> /// <returns></returns> private TableResult GetServerTableResult(T tableData) { TableOperation retrieveOperation = TableOperation.Retrieve<T>(tableName, tableData.RowKey.ToString()); try { TableResult retrievedResult = table.Execute(retrieveOperation); return retrievedResult; } catch (Exception) { return null; } } }
以上程式碼有誤,最新版請參考 GenericsAzureTableStorageAPI 專案。原程式碼不再修正。
批量新增的部分 一次最高僅能100筆 否則會報錯 這是表的限制...
回覆刪除感謝提醒,我找個時間 Update 一下 GitHub。
刪除@Alan Liu 我已經更新 GitHub 的範例程式。;-)
刪除