Azure – CRUD operations with Cosmos DB for Table

By | 23/04/2025

In this post, we will see how to implements all CRUD operations with Cosmos DB for Table, using an Azure Function.
But first of all, what is Cosmos DB for Table?
“The Cosmos DB Table API is a schema-less, key-value storage solution designed to work like Azure Table Storage but with enhanced features. It allows us to store and retrieve entities using PartitionKey and RowKey, providing a simple and scalable way to manage data.
It offers global distribution, SLA-backed performance, automatic indexing, and advanced consistency models, making it a powerful choice for applications that need high availability and low-latency access to table-structured data.”

The Cosmos DB Table API is the better choice when we are familiar with Azure Table Storage or already have applications built around a key-value schema. It simplifies migration and allows us to reuse existing skills and code with minimal changes.
On the other hand, the Core API is more suitable for applications that require advanced querying, rich relationships, or a highly flexible schema. If our project doesn’t need these additional features, the Table API keeps things simpler and more cost-effective, avoiding the potential complexity of the Core API.

Let’s see how to manage an entity called User using an Azure Function.

Let’s start by creating creating a Resource Group where we will add a Cosmos DB for Table:


Then, we create an Azure Function project where we will add the ‘Cosmos connection string’ and the ‘Table name’ in the settings file:

{
    "IsEncrypted": false,
    "Values": {
        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
        "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
        "AzureCosmosDBConnection": "DefaultEndpointsProtocol=https;AccountName=testingcosmosdbtables;AccountKey=ZFeOBS2ieC7wIG2QZwz7o9cLHyUjzODHSpczUG5kEHEZfOf5jwiQrreMoPsJzGmJvyK70Ppie0frACDbV6fRbQ==;TableEndpoint=https://testingcosmosdbtables.table.cosmos.azure.com:443/;",
        "TableName": "User"
    }
}


Now, we define the Class User and the Class ManageUser where we will add the method ‘CheckTableExists’:
[USER.CS]

using Microsoft.Azure.Cosmos.Table;

namespace Test.CosmosDbTable;

public class User : TableEntity
{
    // PartitionKey maps to UserType
    public string UserType
    {
        get => PartitionKey;
        set => PartitionKey = value;
    }

    // RowKey maps to UserName
    public string UserName
    {
        get => RowKey;
        set => RowKey = value;
    }

    // Custom property for password
    public string Password { get; set; }
}

[MANAGEUSER.CS]

using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Cosmos.Table;
using Newtonsoft.Json;

namespace Test.CosmosDbTable;

public class ManageUser
{
    private readonly ILogger<ManageUser> _logger;

    public ManageUser(ILogger<ManageUser> logger)
    {
        _logger = logger;
    }

    private async Task<CloudTable> CheckTableExists(string tableName)
    {
        // Initialize storage account connection string
        string storageConnectionString = Environment.GetEnvironmentVariable("AzureCosmosDBConnection");

        // Create a CloudStorageAccount object from the connection string
        var storageAccount = CloudStorageAccount.Parse(storageConnectionString);

        // Create a CloudTableClient object
        var tableClient = storageAccount.CreateCloudTableClient(new TableClientConfiguration());

        // Get a reference to the table
        var table = tableClient.GetTableReference(tableName);

        // Create the table if it doesn't exist
        await table.CreateIfNotExistsAsync();

        return table;
    }
}


Finally, step by step, we will add all methods to manage the CRUD functions.

INSERT

[Function("AddUser")]
public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req)
{
    try
    {   
        // Read the request body, which contains the user data in JSON format.
        string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

        _logger.LogInformation($"Request body: {requestBody}");

        // Deserialize the JSON request body into a User object.
        var input = JsonConvert.DeserializeObject<User>(requestBody);

        // Validate that required fields are present.
        if (string.IsNullOrWhiteSpace(input?.UserName) || string.IsNullOrWhiteSpace(input?.UserType))
        {
            // Return a 400 Bad Request response if validation fails.
            return new BadRequestObjectResult("UserName and UserType are required.");
        }

        // Ensure that the Cosmos DB table exists. 
        var table = await CheckTableExists(Environment.GetEnvironmentVariable("TableName"));

        // Create a new User entity to insert into the table.
        var user = new User
        {
            PartitionKey = input.UserType,
            RowKey = input.UserName,      
            Password = input.Password    
        };

        // Define an InsertOrReplace operation. This will insert the entity if it doesn't exist,
        // or replace it if an entity with the same PartitionKey and RowKey already exists.
        var insertOperation = TableOperation.InsertOrReplace(user);

        // Execute the table operation asynchronously.
        await table.ExecuteAsync(insertOperation);

        // Return a 200 OK response with a success message.
        return new OkObjectResult($"User '{user.RowKey}' added successfully.");
    }
    catch (Exception e)
    {
        _logger.LogError($"Exception thrown: {e.Message}");

        // Re-throw the exception to ensure the error is propagated appropriately.
        throw;
    }
}


SELECT

[Function("GetAllUsers")]
public async Task<IActionResult> GetAllUsers(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = "users")] HttpRequest req,
    ILogger log)
{
    // Log the function's trigger to track execution.
    _logger.LogInformation("GetAllUsers function triggered.");

    // Ensure the table exists.
    var table = await CheckTableExists(Environment.GetEnvironmentVariable("TableName"));

    // Create a query to retrieve all entities from the table.
    // This assumes `User` is the entity model that matches the table structure.
    var query = new TableQuery<User>();

    // A continuation token is used to handle paginated results.
    // It allows the function to retrieve entities in segments for scalability.
    TableContinuationToken token = null;

    // Initialize a list to store the retrieved user entities.
    var users = new List<User>();

    // Perform a loop to execute the query and handle paginated results.
    do
    {
        // Execute the query with the current continuation token.
        var segment = await table.ExecuteQuerySegmentedAsync(query, token);

        // Update the token to continue querying if there are more results.
        token = segment.ContinuationToken;

        // Add the results from the current segment to the list of users.
        users.AddRange(segment.Results);
    }
    while (token != null); // Continue until there are no more segments to retrieve.

    // Return the list of users as an HTTP 200 response in JSON format.
    return new OkObjectResult(users);
}

[Function("GetUser")]
public async Task<IActionResult> GetUser(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = "user/{partitionKey}/{rowKey}")] HttpRequest req,
    string partitionKey,
    string rowKey,
    ILogger log)
{
    _logger.LogInformation($"GetUser triggered for PartitionKey: {partitionKey}, RowKey: {rowKey}");

    // Ensure the table exists by checking if the specified table can be accessed.
    var table = await CheckTableExists(Environment.GetEnvironmentVariable("TableName"));

    // Create a retrieve operation to fetch the entity based on the provided PartitionKey and RowKey.
    var retrieveOperation = TableOperation.Retrieve<User>(partitionKey, rowKey);

    // Execute the retrieve operation on the table.
    var result = await table.ExecuteAsync(retrieveOperation);

    // Check if the result contains a valid User entity.
    if (result.Result is User user)
    {
        // If the user exists, return it in the HTTP response with a 200 OK status.
        return new OkObjectResult(user);
    }

    // If the user is not found, return a 404 Not Found response with a descriptive message.
    return new NotFoundObjectResult($"User with PartitionKey '{partitionKey}' and RowKey '{rowKey}' not found.");
}


UPDATE

[Function("UpdateUser")]
public async Task<IActionResult> UpdateUser(
    [HttpTrigger(AuthorizationLevel.Function, "put", Route = "user/{partitionKey}/{rowKey}")] HttpRequest req,
    string partitionKey,
    string rowKey,
    ILogger log)
{
    _logger.LogInformation($"UpdateUser triggered for PartitionKey: {partitionKey}, RowKey: {rowKey}");

    // Read the HTTP request body, which contains the updated user data in JSON format
    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

    // Deserialize the request body into a User object
    var input = JsonConvert.DeserializeObject<User>(requestBody);

    // Validate the input: ensure the User object is not null and that the Password field is provided
    if (input == null || string.IsNullOrWhiteSpace(input.Password))
    {
        // Return a 400 Bad Request response if validation fails
        return new BadRequestObjectResult("Invalid request body.");
    }

    // Ensure the table exists by checking or creating it if necessary
    var table = await CheckTableExists(Environment.GetEnvironmentVariable("TableName"));

    // Retrieve the existing user entity based on PartitionKey and RowKey
    var retrieveOperation = TableOperation.Retrieve<User>(partitionKey, rowKey);
    var result = await table.ExecuteAsync(retrieveOperation);

    // Check if the user exists in the table
    if (result.Result is User user)
    {
        // Update the user's Password field with the new value from the request body
        user.Password = input.Password;

        // Define a Replace operation to update the existing entity with the modified data
        var updateOperation = TableOperation.Replace(user);

        // Execute the Replace operation asynchronously
        await table.ExecuteAsync(updateOperation);

        // Return a 200 OK response with a success message
        return new OkObjectResult($"User '{user.UserName}' updated successfully.");
    }

    // If the user does not exist, return a 404 Not Found response with a descriptive message
    return new NotFoundObjectResult($"User with PartitionKey '{partitionKey}' and RowKey '{rowKey}' not found.");
}


DELETE

[Function("DeleteUser")]
public async Task<IActionResult> DeleteUser(
    [HttpTrigger(AuthorizationLevel.Function, "delete", Route = "user/{partitionKey}/{rowKey}")] HttpRequest req,
    string partitionKey,
    string rowKey,
    ILogger log)
{
    _logger.LogInformation($"DeleteUser triggered for PartitionKey: {partitionKey}, RowKey: {rowKey}");

    // Ensure the specified table exists or can be accessed.
    var table = await CheckTableExists(Environment.GetEnvironmentVariable("TableName"));

    // Create a retrieve operation to fetch the entity with the given PartitionKey and RowKey.
    var retrieveOperation = TableOperation.Retrieve<User>(partitionKey, rowKey);

    // Execute the retrieve operation to get the user entity from the table.
    var result = await table.ExecuteAsync(retrieveOperation);

    // Check if the retrieved result is a valid User entity.
    if (result.Result is User user)
    {
        // If the user exists, create a delete operation for the entity.
        var deleteOperation = TableOperation.Delete(user);

        // Execute the delete operation asynchronously to remove the entity from the table.
        await table.ExecuteAsync(deleteOperation);

        // Return a 200 OK response confirming the successful deletion of the user.
        return new OkObjectResult($"User '{user.UserName}' deleted successfully.");
    }

    // If no matching user is found, return a 404 Not Found response with an appropriate message.
    return new NotFoundObjectResult($"User with PartitionKey '{partitionKey}' and RowKey '{rowKey}' not found.");
}




Leave a Reply

Your email address will not be published. Required fields are marked *