Search code examples
c#azure-functionsazure-table-storage

Writing using TableOutput, Reading using TableClient: String to Int cast error


I have the following class:

public class MyData
{
  public string PartitionKey { get; set; }
  public string RowKey { get; set; }
  public int NumOranges { get; set; }
  public int NumApples { get; set; }
  public int NumBananas { get; set; }
}

I am writing instances to Azure Table Storage using the .NET 8 Isolated Azure Functions TableOutput binding.

When I attempt to read the data using Azure.Data.Table.TableClient (v12.10.0) like this:

  var result = tableClient.Query<MyData>(filter: d => d.PartitionKey = 'val');

I get Unable to cast object of type 'System.String' to type 'System.Int32' errors.

I though table storage was schema-less.

If I change the type of all my int properties to long then it works. I can use that as a workaround but I would love to know the cause and if I can do something more preventative or logical about it.

The issue is not to do with the actual values. All values are well within the Int32.MaxValue. I have double-checked by getting the data as TableEntity and checked the Max() of each property.


Solution

  • Azure Table Storage stores numbers as long (Edm.Int64), so I have used Convert.ToInt32(entity["PropertyName"]) to read them as int and avoid type mismatches.

    ReadFromTableStorage.cs :

    using System.Net;
    using Azure.Data.Tables;
    using Microsoft.Azure.Functions.Worker;
    using Microsoft.Azure.Functions.Worker.Http;
    using Microsoft.Extensions.Logging;
    using System.Text.Json;
    using Azure;
    using FunctionApp28.Models;
    
    namespace FunctionApp28.Functions
    {
        public static class ReadFromTableStorage
        {
            [Function("ReadFromTableStorage")]
            public static async Task<HttpResponseData> Run(
                [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequestData req,
                FunctionContext executionContext)
            {
                var logger = executionContext.GetLogger("ReadFromTableStorage");
                logger.LogInformation("Reading from Azure Table Storage...");
    
                string connectionString = Environment.GetEnvironmentVariable("AzureWebJobsStorage");
                string tableName = "kamtable";
    
                if (string.IsNullOrEmpty(connectionString))
                {
                    logger.LogError("AzureWebJobsStorage connection string is missing.");
                    var errorResponse = req.CreateResponse(HttpStatusCode.InternalServerError);
                    await errorResponse.WriteStringAsync("Storage connection string not found.");
                    return errorResponse;
                }
    
                try
                {
                    var tableClient = new TableClient(connectionString, tableName);
                    Pageable<TableEntity> entities = tableClient.Query<TableEntity>();
                    var resultList = new List<MyData>();
                    foreach (var entity in entities)
                    {
                        resultList.Add(new MyData
                        {
                            PartitionKey = entity.PartitionKey,
                            RowKey = entity.RowKey,
                            NumOranges = entity.ContainsKey("NumOranges") ? Convert.ToInt32(entity["NumOranges"]) : 0,
                            NumApples = entity.ContainsKey("NumApples") ? Convert.ToInt32(entity["NumApples"]) : 0,
                            NumBananas = entity.ContainsKey("NumBananas") ? Convert.ToInt32(entity["NumBananas"]) : 0
                        });
                    }
    
                    var response = req.CreateResponse(HttpStatusCode.OK);
                    await response.WriteStringAsync(JsonSerializer.Serialize(resultList));
                    return response;
                }
                catch (Exception ex)
                {
                    logger.LogError($"Error reading from Table Storage: {ex.Message}");
                    var errorResponse = req.CreateResponse(HttpStatusCode.InternalServerError);
                    await errorResponse.WriteStringAsync($"Error retrieving data: {ex.Message}");
                    return errorResponse;
                }
            }
        }
    }
    

    WriteToTableStorage.cs :

    using System.Net;
    using Azure.Data.Tables;
    using Microsoft.Azure.Functions.Worker;
    using Microsoft.Azure.Functions.Worker.Http;
    using Microsoft.Extensions.Logging;
    using Newtonsoft.Json;
    using FunctionApp28.Models;
    
    namespace FunctionApp28.Functions
    {
        public static class WriteToTableStorage
        {
            private const string TableName = "kamtable";
            [Function("WriteToTableStorage")]
            public static async Task<HttpResponseData> Run(
                [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
                FunctionContext context)
            {
                var logger = context.GetLogger("WriteToTableStorage");
                string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
                var data = JsonConvert.DeserializeObject<MyData>(requestBody);
    
                if (data == null)
                {
                    var badResponse = req.CreateResponse(HttpStatusCode.BadRequest);
                    await badResponse.WriteStringAsync("Invalid request payload.");
                    return badResponse;
                }
    
                string connectionString = Environment.GetEnvironmentVariable("AzureWebJobsStorage");
                var tableClient = new TableClient(connectionString, TableName);
                await tableClient.CreateIfNotExistsAsync();
    
                var entity = new TableEntity(data.PartitionKey, data.RowKey)
                {
                    { "NumOranges", data.NumOranges },
                    { "NumApples", data.NumApples },
                    { "NumBananas", data.NumBananas }
                };
    
                await tableClient.AddEntityAsync(entity);
                logger.LogInformation("Data written to table storage successfully.");
                var response = req.CreateResponse(HttpStatusCode.OK);
                await response.WriteStringAsync("Entity written to Table Storage.");
                return response;
            }
        }
    }
    

    MyData.cs :

    namespace FunctionApp28.Models
    {
        public class MyData
        {
            public string PartitionKey { get; set; }
            public string RowKey { get; set; }
            public int NumOranges { get; set; }
            public int NumApples { get; set; }
            public int NumBananas { get; set; }
        }
    }
    

    Output :

    I have sent the below POST request.

    {
        "PartitionKey": "Fruit",
        "RowKey": "123",
        "NumOranges": 5,
        "NumApples": 10,
        "NumBananas": 3
    }
    

    enter image description here

    enter image description here

    enter image description here