Azure Functions: Table Input Bindings

Azure Functions provides powerful input and output bindings to integrate with various Azure services and other resources without complex custom code. The Table input binding allows you to easily retrieve data from an Azure Table Storage entity.

Understanding Table Input Bindings

A Table input binding lets you declare that your function needs to read a specific entity or a collection of entities from an Azure Table Storage table. The binding handles the retrieval of the data, making it available as a parameter in your function code.

How it Works

When you configure a Table input binding, you specify:

The binding then connects to your Azure Table Storage account (using a configured connection string) and fetches the requested data.

Defining a Table Input Binding

You define bindings in your function's function.json file (for JavaScript, C#, Python, etc.) or using attributes in your code (for C#). Here's an example for function.json:

{
  "bindings": [
    {
      "name": "inputEntity",
      "type": "table",
      "direction": "in",
      "tableName": "Products",
      "partitionKey": "{partitionKey}",
      "rowKey": "{rowKey}",
      "connection": "AzureWebJobsStorage"
    },
    {
      "name": "$return",
      "type": "http",
      "direction": "out"
    }
  ],
  "disabled": false
}

Binding Parameters

Example Usage in JavaScript

If your function.json is configured as above, your JavaScript function might look like this:

module.exports = async function (context, req, inputEntity) {
    context.log('JavaScript HTTP trigger function processed a request.');

    if (inputEntity) {
        context.res = {
            status: 200,
            body: inputEntity
        };
    } else {
        context.res = {
            status: 404,
            body: "Product not found."
        };
    }
};

In this example, inputEntity will contain the data for the specified partition and row key, or null if the entity is not found.

Example Usage in C#

For C#, you would use attributes:

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

public static class GetProduct
{
    [FunctionName("GetProduct")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", Route = "products/{partitionKey}/{rowKey}")] HttpRequest req,
        string partitionKey,
        string rowKey,
        [Table("Products", "{partitionKey}", "{rowKey}", Connection = "AzureWebJobsStorage")] ProductEntity inputEntity,
        ILogger log)
    {
        log.LogInformation("C# HTTP trigger function processed a request.");

        if (inputEntity == null)
        {
            return new NotFoundResult();
        }

        return new OkObjectResult(inputEntity);
    }
}

public class ProductEntity
{
    public string PartitionKey { get; set; }
    public string RowKey { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Notes on C# Example

Azure Functions supports various data types for table entities, including primitive types and complex objects. Ensure your entity class matches the schema of your table.

Querying Multiple Entities

To retrieve multiple entities, you can use the filter parameter or bind to a collection type. For instance, to get all products in a specific partition:

Using filter in function.json

{
  "bindings": [
    {
      "name": "products",
      "type": "table",
      "direction": "in",
      "tableName": "Products",
      "partitionKey": "{partitionKey}",
      "filter": "Category eq 'Electronics'",
      "connection": "AzureWebJobsStorage"
    },
    {
      "name": "$return",
      "type": "http",
      "direction": "out"
    }
  ]
}

Your function would then receive an array or list of products.

Using $filter query parameter in HTTP Trigger

You can also pass filter conditions via query parameters in an HTTP trigger and use them to construct the filter for the table binding. This is commonly done in C# by passing the query parameter to the filter property:

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
using System.Collections.Generic;

public static class GetProductsByCategory
{
    [FunctionName("GetProductsByCategory")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", Route = "products")] HttpRequest req,
        string category,
        [Table("Products", Connection = "AzureWebJobsStorage")] IEnumerable<ProductEntity> allProducts, // Bind all products
        ILogger log)
    {
        log.LogInformation("C# HTTP trigger function processed a request.");

        string categoryFilter = req.Query["category"];

        if (string.IsNullOrEmpty(categoryFilter))
        {
            return new OkObjectResult(allProducts); // Return all if no category specified
        }

        // Note: For larger tables, filtering on the server-side using TableQuery is more efficient.
        // This example shows a simple in-memory filter after retrieving all items.
        var filteredProducts = new List<ProductEntity>();
        foreach (var product in allProducts)
        {
            // Assume ProductEntity has a 'Category' property
            if (product.Category.Equals(categoryFilter, StringComparison.OrdinalIgnoreCase))
            {
                filteredProducts.Add(product);
            }
        }

        return new OkObjectResult(filteredProducts);
    }
}

Note: For more efficient querying of large tables, consider using a Table Output binding to construct a query dynamically or performing the filtering on the Azure Table Storage side using query expressions directly.

Connection Strings

Ensure your Azure Table Storage connection string is correctly configured in your Azure Functions app settings under the name specified in the connection property (e.g., AzureWebJobsStorage).

Best Practices

By leveraging Table input bindings, you can significantly simplify your Azure Functions code for interacting with Azure Table Storage.