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

C# Interactive

How to enable Remote Desktop on Azure Cloud Service