Skip to content

Instantly share code, notes, and snippets.

@NoelOConnell
Last active July 27, 2020 21:10

Revisions

  1. NoelOConnell revised this gist Jun 20, 2018. No changes.
  2. NoelOConnell revised this gist Jun 20, 2018. No changes.
  3. NoelOConnell created this gist Jun 20, 2018.
    4 changes: 4 additions & 0 deletions DataStoreItem.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,4 @@
    public class DataStoreItem: TableEntity
    {
    public string Value { get; set; }
    }
    107 changes: 107 additions & 0 deletions TableStorageDataStore.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,107 @@
    public class TableStorageDataStore : IDataStore
    {
    private CloudTable _table;
    public const string TABLE_NAME = "GoogleDataStore";
    public const string PARTITION_NAME = "OAuth2Responses";

    public TableStorageDataStore()
    {
    // Get TableStorage Connection String
    CloudStorageAccount storageAccount = CloudStorageAccount.Parse(ConfigurationManager.ConnectionStrings["StorageConnectionString"].ConnectionString);

    var tableClient = storageAccount.CreateCloudTableClient();
    _table = tableClient.GetTableReference(TABLE_NAME);
    _table.CreateIfNotExists();
    }

    public async Task ClearAsync()
    {
    // A batch operation may contain up to 100 individual table operations
    // https://docs.microsoft.com/en-us/dotnet/api/microsoft.windowsazure.storage.table.tablebatchoperation?view=azure-dotnet

    TableQuery<DataStoreItem> query = new TableQuery<DataStoreItem>();
    IList<DataStoreItem> items = _table.ExecuteQuery(query).ToList();

    int chunkSize = 99;

    var batchedItems = items.Select((x, i) => new { Index = i, Value = x })
    .GroupBy(x => x.Index / chunkSize)
    .Select(x => x.Select(v => v.Value).ToList())
    .ToList();

    foreach(List<DataStoreItem> batch in batchedItems)
    {
    TableBatchOperation batchDeleteOperation = new TableBatchOperation();

    foreach (DataStoreItem item in batch)
    batchDeleteOperation.Delete(item);

    await _table.ExecuteBatchAsync(batchDeleteOperation);
    }

    await Task.CompletedTask;
    }

    public async Task DeleteAsync<T>(string key)
    {
    if (string.IsNullOrEmpty(key))
    {
    throw new ArgumentException("Key MUST have a value");
    }

    DataStoreItem item = await GetAsync<DataStoreItem>(key);

    var deleteOperation = TableOperation.Delete(item);
    await _table.ExecuteAsync(deleteOperation);
    }

    public async Task<T> GetAsync<T>(string key)
    {
    if (string.IsNullOrEmpty(key))
    {
    throw new ArgumentException("Key MUST have a value");
    }

    string generatedStoreKey = GenerateStoredKey(key, typeof(T));

    TableOperation retrieveOperation = TableOperation.Retrieve<DataStoreItem>(PARTITION_NAME, generatedStoreKey);
    TableResult retrievedResult = await _table.ExecuteAsync(retrieveOperation);

    DataStoreItem item = (DataStoreItem)retrievedResult.Result;

    T value = item == null ? default(T) : JsonConvert.DeserializeObject<T>(item.Value);

    return value;
    }

    public async Task StoreAsync<T>(string key, T value)
    {
    if (string.IsNullOrEmpty(key))
    {
    throw new ArgumentException("Key MUST have a value");
    }

    string serializedValue = JsonConvert.SerializeObject(value);
    string generatedStoreKey = GenerateStoredKey(key, typeof(T));

    DataStoreItem item = new DataStoreItem() { PartitionKey = PARTITION_NAME, RowKey = generatedStoreKey, Value = serializedValue };

    TableOperation insertOperation = TableOperation.InsertOrMerge(item);
    TableResult result = await _table.ExecuteAsync(insertOperation);

    if (result.HttpStatusCode < (int)HttpStatusCode.OK || result.HttpStatusCode > (int)HttpStatusCode.MultipleChoices)
    {
    throw new Exception($"[{result.HttpStatusCode}] Failed to insert record at {TABLE_NAME} {item.RowKey}");
    }

    await Task.CompletedTask;
    }

    // <summary>Creates a unique stored key based on the key and the class type.</summary>
    /// <param name="key">The object key.</param>
    /// <param name="t">The type to store or retrieve.</param>
    public static string GenerateStoredKey(string key, Type t)
    {
    return $"{t.FullName}-{key}";
    }
    }