Storage Table - Easy data access layer

You can simply use the below code structure when you want to write a data access layer for Azure Storage Table. 

Use blow Interface as the skeleton of your data access class. 

    public interface ITableStorage<T>
    {
        Task<List<T>> GetAllAsync(string partitionKey);

        Task<List<T>> RetrieveEntityAsync(string partitionKey, string rowPrefix);

        Task<T> GetSingleAsync(T entity);

        Task<T> GetSingleAsync(string partitionKey, string rowKey);

        Task InsertEntityAsync(T entity, bool forInsert = true);

        Task DeleteEntityAsync(T entity);

        Task DeleteSingleAsync(string partitionKey, string rowKey);

        Task<List<T>> RetrieveAsync(string partitionKey);
    }

Here is the generic concrete class for the main interactions you do with the Storage Table. Add-Edit-Get-Delete.

     public class TableStorage<T> : ITableStorage<T> where T : TableEntity, new()
    {
        private readonly CloudStorageAccount _cloudStorageAccount;
        private readonly CloudTable _table;

        public TableStorage(string connectionString)
        {
            _cloudStorageAccount = CloudStorageAccount.Parse(connectionString);

            CloudTableClient tableClient = _cloudStorageAccount.CreateCloudTableClient();
            _table = tableClient.GetTableReference(typeof(T).Name);
            _table.CreateIfNotExistsAsync();
        }

        public async Task DeleteEntityAsync(T entity)
        {
            try
            {
                var DeleteOperation = TableOperation.Delete(entity);
                await _table.ExecuteAsync(DeleteOperation);
            }
            catch (Exception ExceptionObj)
            {
                throw ExceptionObj;
            }
        }

        public async Task DeleteSingleAsync(string partitionKey, string rowKey)
        {
            DynamicTableEntity dynamicTableEntity = new DynamicTableEntity(partitionKey, rowKey);
            TableResult tableResult = await _table.ExecuteAsync(TableOperation.Retrieve<T>(partitionKey, rowKey, (List<string>)null), null, null);

            if(tableResult == null || tableResult.Result == null)
            {
                return;
            }

            dynamicTableEntity.ETag = "*";
            await _table.ExecuteAsync(TableOperation.Delete(dynamicTableEntity), null, null);
        }

        public async Task<List<T>> GetAllAsync(string partitionKey)
        {
            try
            {
                TableContinuationToken token = null;
                TableQuerySegment<T> dataList = await _table.ExecuteQuerySegmentedAsync(new TableQuery<T>()
                    .Where(TableQuery.GenerateFilterCondition("PartitionKey", "eq", partitionKey)),
                    token, null, null);

                List<T> returnDataList = new List<T>();
                returnDataList.AddRange(dataList);
                return returnDataList;
            }
            catch (Exception ExceptionObj)
            {
                throw ExceptionObj;
            }
        }

        public async Task<T> GetSingleAsync(T entity)
        {
            return await GetSingleAsync(entity.PartitionKey, entity.RowKey);
        }

        public async Task<T> GetSingleAsync(string partitionKey, string rowKey)
        {
            TableResult tableResult = await _table.ExecuteAsync(TableOperation.Retrieve<T>(partitionKey, rowKey, (List<string>)null), null, null);

            if(tableResult == null)
            {
                return default(T);
            }

            return tableResult.HttpStatusCode == 404 ? default(T) : (T)tableResult.Result;
        }

        public async Task InsertEntityAsync(T entity, bool forInsert = true)
        {
            try
            {
                if (forInsert)
                {
                    var insertOperation = TableOperation.Insert(entity);
                    await _table.ExecuteAsync(insertOperation);
                }
                else
                {
                    var insertOrMergeOperation = TableOperation.InsertOrReplace(entity);
                    await _table.ExecuteAsync(insertOrMergeOperation);
                }
            }
            catch (Exception ExceptionObj)
            {
                throw ExceptionObj;
            }
        }

        public async Task<List<T>> RetrieveEntityAsync(string partitionKey, string rowPrefix)
        {
            try
            {
                int length = rowPrefix.Length - 1;
                char ch = (char)((uint)rowPrefix[length] + 1U);
                string givenValue = rowPrefix.Substring(0, length) + ch.ToString();
                string filterX = TableQuery.CombineFilters(TableQuery.GenerateFilterCondition("RowKey", "ge", rowPrefix),
                    "and", TableQuery.GenerateFilterCondition("RowKey", "lt", givenValue));
                string filter = TableQuery.CombineFilters(TableQuery.GenerateFilterCondition("PartitionKey", "eq", partitionKey),
                    "and", filterX);

                TableContinuationToken token = null;
                TableQuerySegment<T> dataList = await _table.ExecuteQuerySegmentedAsync(new TableQuery<T>().Where(filter),
                    token, null, null);

                List<T> returnDataList = new List<T>();
                returnDataList.AddRange(dataList);
                return returnDataList;
            }
            catch (Exception ExceptionObj)
            {
                throw ExceptionObj;
            }
        }

        public async Task<List<T>> RetrieveAsync(string partitionKey)
        {
            try
            {
                TableQuery<T> partitionScanQuery = new TableQuery<T>().Where
                 (TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, partitionKey));

                TableContinuationToken token = null;
                TableQuerySegment<T> dataList = await _table.ExecuteQuerySegmentedAsync(partitionScanQuery, token);
                List<T> returnDataList = new List<T>();
                returnDataList.AddRange(dataList);
                return returnDataList;
            }
            catch(Exception ex)
            {
                throw ex;
            }
        }
    } 

This is all you need for your data access layer.
Here is how you need to use these components in your code. 

Assume you need to write a code to store Student information in the Storage table. You can write your Student model calls like below.

    public class Student : TableEntity
    {
        public Guid StudentId => Guid.Parse(RowKey);
        public string SchoolName => PartitionKey;
        public DateTime DOB { get; set; }
        public string Email { get; set; }
        public string Address { get; set; }
        public string Phone { get; set; }

        public Student() { }
        public Student(string partitionKey, string rowKey) : base(partitionKey, rowKey) { }
    }

Note: When you inherit your models with the TableEntity and write code to access Storage table client you must install below NuGet packages
- WindowsAzure.Storage
- Microsoft.Azure.Storage.Common
- Microsoft.Azure.Cosmos.Table

When you inject your services to read Student's data you can inject it as below

    string storageConnectionString = "THE CONNECTION STRING FOR STORAGE TABLE";

     services.AddSingleton<ITableStorage<Student>>(new TableStorage<Student>(storageConnectionString));

You can write your service class as below.

    public interface IStudentService
    {
        Task<Guid> AddStudentAsync(Student student);   
        // ......
    }

    public class StudentService : IStudentService
    {
        private readonly ITableStorage<Student> _tableStorage;

        public StudentService(ITableStorage<Student> tableStorage)
        {
            _tableStorage = tableStorage;
        }

        public async Task<Guid> AddStudentAsync(Student student)
        {
            await _tableStorage.InsertEntityAsync(student, true);
            return student.StudentId;
        }
        
        // ......
    }


Hope this will help you to speed up your dev work. 
Cheers!! 

Comments

Popular posts from this blog

Dependency Injection for Azure Function

How to enable Remote Desktop on Azure Cloud Service