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 的範例程式。;-)
刪除