/ azure functions

Azure Functions + Table Storage - CRUD

Imagine having all your CRUD operations in a single Azure Function? In this post, I'm going to use Azure Table Storage to demonstrate all CRUD operation in a single function. Keep in mind this is for demonstration purposes only, for scalability sake you would probably have separate functions for reads and writes.

The source code covered in this post is available here

To be able to access my Azure Table Storage, I'll have to set two bindings for my function. You can achieve the same in the portal by going to the integrate tab and adding Azure Table Storage as both Input and Output. You will also need to configure your connection string.

{
  "type": "table",
  "name": "inputTable",
  "tableName": "Customers",
  "take": 50,
  "connection": "MY_STORAGE",
  "direction": "in",
  "partitionKey": "Customer"
},
{
  "type": "table",
  "name": "outputTable",
  "tableName": "Customers",
  "connection": "MY_STORAGE",
  "direction": "out"
}

Notice that the table name is Customers, this will change for each function as well as the partition key (For more information about Partition Key click here)

This is how my function looks like.

#r "Microsoft.WindowsAzure.Storage"
#load "../shared/crud.csx"
#load "../shared/model/customer.csx"
using System.Net;
using Microsoft.WindowsAzure.Storage.Table;

public static async Task<HttpResponseMessage> Run(
    HttpRequestMessage req, 
    IQueryable<Customer> inputTable, 
    CloudTable outputTable, 
    TraceWriter log)
{
    return await Crud(req, inputTable, outputTable, log, "Customer", Customer.Project);
}

I've got my two extra bindings as my inputTable and outputTable parameters, I also created a shared crud method so I can reuse across multiple functions and a model for each function together with its projection.

My model is a simple class inheriting from TableEntity, I'm not going to paste it all here, and my projection is a Func getting a dynamic and returing a Customer.

//Using dynamic here because it's the data I get from my body request
#r "Microsoft.WindowsAzure.Storage"
using Microsoft.WindowsAzure.Storage.Table;

public class Customer : TableEntity
{
 //......
}

public static Func<dynamic,Customer> Project
{
    get{
        return data => {
            return new Customer{
                PartitionKey = "Customer",
                RowKey = data?.RowKey,
                CompanyName = data?.CompanyName,
                //....
                //This is necessary when creating executing a table operation
                ETag = "*"
            };
        };
    }
}

Now let's have a look at my crud method. It's very similar to the function itself, but I'm adding an extra 2 parameters: partitionKey and projection

#r "Microsoft.WindowsAzure.Storage"
using System.Net;
using Microsoft.WindowsAzure.Storage.Table;

public static async Task<HttpResponseMessage> Crud(
    HttpRequestMessage req, 
    IQueryable<Microsoft.WindowsAzure.Storage.Table.ITableEntity> inputTable, 
    CloudTable outputTable, 
    TraceWriter log,
    string partitionKey,
    Func<dynamic,Microsoft.WindowsAzure.Storage.Table.ITableEntity> projection)
{
    //todo...
}

Okay, now let's put together all CRUD methods in my shared crud.

Get by id
string key = req.GetQueryNameValuePairs()
        .FirstOrDefault(q => string.Compare(q.Key, "rowKey", true) == 0).Value;
if(req.Method == HttpMethod.Get && !string.IsNullOrEmpty(key)) {
    var list = inputTable.Where(i=>i.RowKey == key && i.PartitionKey == partitionKey).ToList();
    if(list.Count()>0) return req.CreateResponse(HttpStatusCode.OK, list.First(),"application/json");
    return req.CreateResponse(HttpStatusCode.NotFound, "Entity Not Found");
}
Get All
if(req.Method == HttpMethod.Get && string.IsNullOrEmpty(key)) 
   return req.CreateResponse(HttpStatusCode.OK, inputTable.ToList(),"application/json"); 
Delete
if(req.Method == HttpMethod.Delete && !string.IsNullOrEmpty(key)){
    var item = new TableEntity
    {
        PartitionKey = partitionKey,
        RowKey = key,
        //this is required
        ETag = "*"
    };
    var operation = TableOperation.Delete(item);
    await outputTable.ExecuteAsync(operation);
    return req.CreateResponse(HttpStatusCode.NoContent);
}
Create and Update
if(req.Method == HttpMethod.Post){    
    var data = await req.Content.ReadAsAsync<object>();
    //that's where I'm using my projection
    var entity = projection(data); 

    //Create
    if(entity.RowKey == "0")
    {
        var newKey = Guid.NewGuid().ToString();
        entity.RowKey = newKey;
        var operation = TableOperation.Insert(entity);
        await outputTable.ExecuteAsync(operation);
        return req.CreateResponse(HttpStatusCode.OK, entity,"application/json");
    }
    //Update
    else{
        var operation = TableOperation.Replace(entity);
        await outputTable.ExecuteAsync(operation);
        return req.CreateResponse(HttpStatusCode.OK, entity,"application/json"); 
    }
}

That's it.

Cheers.

Thiago Passos

Thiago Passos

I'm Thiago Passos, a Solution Architect working for SSW sharing what I've been working with and learning. Love technology, dancing and I get unfriendly when I'm hungry.

Read More