Introduction

The Azure Functions table output binding allows your function to easily write data to an Azure Table Storage table. This binding simplifies the process of persisting structured data without needing to directly interact with the Azure Storage SDK.

Azure Table Storage is a NoSQL key-attribute store. It's ideal for storing large amounts of structured, non-relational data. Each table is a collection of entities, and each entity is a collection of properties.

Overview

The table output binding operates by defining an output binding of type Table in your function's configuration (e.g., function.json or via attributes in C#). When your function executes and a value is returned or assigned to this output binding, Azure Functions runtime automatically handles the insertion or update of that data into the specified table.

Key features include:

  • Automatic serialization of input data into table entities.
  • Support for single entity insertion/update.
  • Integration with Azure Storage emulator for local development.

Configuration

Using Attributes (C#)

In C# Azure Functions, you define output bindings using attributes. The [Table] attribute is used for table output bindings.

The attribute requires the following parameters:

  • tableName: The name of the Azure Table Storage table to write to.
  • connection: The name of the application setting that contains the Azure Storage connection string.

Here's an example of how to declare a table output binding:

[Table("MyTable", Connection = "AzureWebJobsStorage")] public static void WriteToTable(string input, [Table("MyTable", Connection = "AzureWebJobsStorage")] out MyEntity entity)
{
    // ... function logic ...
}

Using function.json

For C# isolated process functions or other language runtimes, you configure bindings in the function.json file.

{
  "scriptFile": "__init__.py",
  "bindings": [
    {
      "name": "input",
      "type": "httpTrigger",
      "direction": "in",
      "authLevel": "function",
      "methods": [
        "get",
        "post"
      ]
    },
    {
      "name": "outputTable",
      "type": "table",
      "direction": "out",
      "tableName": "MyTable",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

In this configuration, outputTable is the name of the output binding, and it points to the "MyTable" table using the connection string found in the "AzureWebJobsStorage" application setting.

Example Usage

Let's create a simple HTTP-triggered function that takes data from the request body and writes it to Azure Table Storage.

C# Function Code

Define a simple entity class that maps to your table structure.

public class Product
{
    public string PartitionKey { get; set; }
    public string RowKey { get; set; }
    public string Name { get; set; }
    public double Price { get; set; }
}

Here's the function code:

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

public static class AddProduct
{
    [FunctionName("AddProduct")]
    public static async Task Run(
        [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
        [Table("Products", Connection = "AzureWebJobsStorage")] out dynamic[] productEntities,
        ILogger log)
    {
        log.LogInformation("C# HTTP trigger function processed a request to add a product.");

        string requestBody = await new System.IO.StreamReader(req.Body).ReadToEndAsync();
        var productData = JsonConvert.DeserializeObject(requestBody);

        if (productData == null)
        {
            productEntities = null; // Indicate no output
            log.LogError("Invalid product data received.");
            return;
        }

        // Ensure PartitionKey and RowKey are set for a valid Table entity
        // Typically, PartitionKey could be a category, and RowKey a unique ID or SKU.
        if (string.IsNullOrEmpty(productData.PartitionKey) || string.IsNullOrEmpty(productData.RowKey))
        {
            productData.PartitionKey = "Default";
            productData.RowKey = System.Guid.NewGuid().ToString();
            log.LogWarning($"PartitionKey or RowKey was missing. Assigned default values: PartitionKey='{productData.PartitionKey}', RowKey='{productData.RowKey}'");
        }

        // Create a dynamic object that matches the table schema
        // Azure Functions SDK expects a list of dynamic objects for multiple inserts/updates
        // For a single insert, you can pass a single dynamic object or an array with one element.
        // Using dynamic[] for flexibility.
        productEntities = new dynamic[]
        {
            new {
                PartitionKey = productData.PartitionKey,
                RowKey = productData.RowKey,
                Name = productData.Name,
                Price = productData.Price
            }
        };

        log.LogInformation($"Product '{productData.Name}' added to table.");
    }
}
Note on Entities: When using the Table output binding, the runtime expects a structure that can be mapped to an Azure Table entity. This typically involves properties named PartitionKey and RowKey, which are mandatory for Azure Table Storage. If your input object doesn't have these, you'll need to add them before assigning to the output binding. The example uses dynamic[] for simplicity, but strongly typed entities implementing ITableEntity are also a good practice.

Making a Request

To test this function, send a POST request to its endpoint with a JSON payload:

POST /api/AddProduct HTTP/1.1
Host: your-function-app.azurewebsites.net
Content-Type: application/json

{
    "PartitionKey": "Electronics",
    "RowKey": "SKU12345",
    "Name": "Wireless Mouse",
    "Price": 25.99
}

After the function runs successfully, you will find a new entity in the "Products" table in your Azure Storage account.

Advanced Scenarios

Batch Operations

The Table output binding can accept an array of objects to perform batch insertions or updates. This is more efficient than calling the function multiple times for each entity.

using Microsoft.Azure.WebJobs;
using System.Collections.Generic;

public static class BulkAddProducts
{
    [FunctionName("BulkAddProducts")]
    public static void Run(
        [QueueTrigger("product-queue")] string[] productQueueItems,
        [Table("Products", Connection = "AzureWebJobsStorage")] out dynamic[] productEntities)
    {
        var entities = new List();
        foreach (var item in productQueueItems)
        {
            // Assume item is JSON string that needs deserialization
            var productData = JsonConvert.DeserializeObject(item);
            // ... logic to set PartitionKey and RowKey ...
            entities.Add(new {
                PartitionKey = productData.PartitionKey,
                RowKey = productData.RowKey,
                Name = productData.Name,
                Price = productData.Price
            });
        }
        productEntities = entities.ToArray();
    }
}

Entity Types

While dynamic is convenient, using strongly typed C# classes that implement Microsoft.Azure.Cosmos.Table.ITableEntity offers better compile-time checks and structure.

using Microsoft.Azure.Cosmos.Table;

public class ProductEntity : ITableEntity
{
    public string PartitionKey { get; set; }
    public string RowKey { get; set; }
    public DateTimeOffset Timestamp { get; set; }
    public string ETag { get; set; }

    public string Name { get; set; }
    public double Price { get; set; }
}

Then, your output binding would expect an array of ProductEntity:

[Table("Products", Connection = "AzureWebJobsStorage")] out ProductEntity[] productEntities) { ... }

Delete Operations

To delete entities, you typically retrieve them first using an input binding and then use the SDK to delete them. However, for simple cases, you might construct entities with just PartitionKey and RowKey and rely on a trigger mechanism that indicates deletion (this is less direct with the output binding alone).

Warning: The Table output binding primarily supports inserting and updating entities. For deleting entities, it's generally recommended to use a Table input binding to retrieve the entity and then use the Azure Storage SDK (e.g., CloudTable.ExecuteAsync(TableOperation.Delete(entity))) within your function code for explicit delete operations.

Troubleshooting

  • Connection String Issues: Ensure the connection string specified in your binding configuration (function.json or attribute) correctly points to a valid Azure Storage account connection string in your application settings (e.g., local.settings.json for local development, or Application Settings in Azure portal).
  • Missing PartitionKey/RowKey: Every entity in Azure Table Storage must have a PartitionKey and a RowKey. If your input data doesn't provide these, your function will fail. Make sure to assign them, either from your input or by generating them (e.g., using GUIDs).
  • Data Format: Verify that the data being passed to the output binding can be serialized into a valid table entity. Ensure data types are compatible.
  • Table Creation: The table specified in the binding will be created automatically if it doesn't exist. However, ensure the storage account is accessible and configured correctly.
  • Logging: Use ILogger extensively to trace the flow of your function and identify where data might be missing or malformed.