Search code examples
c#.netazureazure-cosmosdbazure-table-storage

UpsertEntityAsync throwing error after i migrate from Microsoft.Azure.Cosmos.Table to Azure.Data.Tables


i am in progress of migrating an existing project that have Microsoft.Azure.Cosmos.Table to Azure.Data.Tables. I was following the migration guide and implemented my custom model with ITableEntity

with cosmos the insert looked like

private async Task InsertOrMergeAsync(CloudTable table, OfficeSupplyEntity record)
{
    var insertOrMergeOperation = TableOperation.InsertOrMerge(record);
    await table.ExecuteAsync(insertOrMergeOp);
}

after migration it converted to

    private async Task InsertOrMergeAsync(TableClient table, OfficeSupplyEntity record)
    {
        await table.UpsertEntityAsync(record);
    }

but now I get an error

An error occurred while processing this request.
RequestId:915023cb-f88d-47c3-81f0-1debbf39c8ef
Time:2024-12-19T07:26:55.088Z
Status: 400 (Bad Request)
ErrorCode: InvalidInput

Content:
{"odata.error":{"code":"InvalidInput","message":{"lang":"en-US","value":"An error occurred while processing this request.\nRequestId:915023cb-f88d-47c3-81f0-1debbf39c8ef\nTime:2024-12-19T07:26:55.088Z"}}}

Headers:
Server: Azurite-Table/3.33.0
x-ms-error-code: REDACTED
x-ms-request-id: 915023cb-f88d-47c3-81f0-1debbf39c8ef
x-ms-version: REDACTED
Date: Thu, 19 Dec 2024 07:26:55 GMT
Connection: keep-alive
Keep-Alive: REDACTED
Transfer-Encoding: chunked
Content-Type: application/json;odata=minimalmetadata;streaming=true;charset=utf-8

in the guide also it is unclear in this section

directly quoting below

Previously in Microsoft.Azure.Cosmos.Table, we would create a TableOperation and execute it with the table client. The result of the operation must be casted back to the entity type.

// Create the InsertOrReplace table operation
TableOperation insertOrMergeOperation = TableOperation.InsertOrMerge(entity);

// Execute the operation.
TableResult result = cloudTable.Execute(insertOrMergeOperation);

// Cast the result.
OfficeSupplyOld insertedCustomer = result.Result as OfficeSupplyOld;

Now in Azure.Data.Tables, using the TableClient, we can simply pass our entity to the UpsertEntity method which will create or update the entity depending on whether or not it already exists.

// Upsert the newly created entity.
tableClient.UpsertEntity(tableEntity);

in the guide what is tableEntity? is it different from entity?


Solution

  • UpsertEntityAsync throwing error after i migrate from Microsoft.Azure.Cosmos.Table to Azure.Data.Tables

    tableEntity and entity are different, where tableEntity is a flexible container for the properties of an entity in Azure Table Storage and entity is a variable used in code to represent an instance of a table entity.

    As the Azure Data Table doesn't support storing arrays it throws an error. Try with the below code for storing arrays in Azure Data Table. It Converts the array into a JSON string using a serializer JsonSerializer.Serialize, this allows the array to be stored as a single property in Azure Table Storage. Now deserialize using JsonSerializer.Deserialize so the JSON string is deserialized into an array.

    class TableStorageArray
    {
        private const string storageAccountName = "<accName>";  
        private const string storageAccountKey = "<primaryKey>";  
        private const string tableName = "TableB";
    
        private async Task InsertOrMergeAsync(TableClient tableClient, Azure.Data.Tables.TableEntity record)
        {
            await tableClient.UpsertEntityAsync(record, TableUpdateMode.Merge);
        }
    
        private async Task InsertOrMergeAsync(CosmosTable.CloudTable table, CosmosTable.DynamicTableEntity record)
        {
            var operation = CosmosTable.TableOperation.InsertOrMerge(record);
            await table.ExecuteAsync(operation);
        }
    
        private async Task StorageAsync()
        {
            string connectionString = $"DefaultEndpointsProtocol=https;AccountName={storageAccountName};AccountKey={storageAccountKey};EndpointSuffix=core.windows.net";
    
            var tableClient = new TableServiceClient(connectionString).GetTableClient(tableName);
            await tableClient.CreateIfNotExistsAsync();
    
            var storageAccount = CosmosTable.CloudStorageAccount.Parse(connectionString);
            var table = storageAccount.CreateCloudTableClient().GetTableReference(tableName);
            await table.CreateIfNotExistsAsync();
    
            Console.WriteLine("Table connected or created.");
    
            var partitionKey = "1";
            var rowKey = "1";
            var arrayField = new[] { "Value1", "Value6", "Value9" };
            var arrayAsJson = JsonSerializer.Serialize(arrayField);
    
            var recordForTableClient = new Azure.Data.Tables.TableEntity(partitionKey, rowKey)
            {
                { "Name", "Sai Paul" },
                { "ArrayField", arrayAsJson }
            };
    
            var recordForCloudTable = new CosmosTable.DynamicTableEntity(partitionKey, rowKey)
            {
                Properties = {
                    { "Name", new CosmosTable.EntityProperty("Sai Paul") },
                    { "ArrayField", new CosmosTable.EntityProperty(arrayAsJson) }
                }
            };
    
            await InsertOrMergeAsync(tableClient, recordForTableClient);
    
            await InsertOrMergeAsync(table, recordForCloudTable);
    
            var retrievedEntity = await tableClient.GetEntityAsync<Azure.Data.Tables.TableEntity>(partitionKey, rowKey);
            string retrievedJson = retrievedEntity.Value.GetString("ArrayField");
            var deserializedArray = JsonSerializer.Deserialize<string[]>(retrievedJson);
    
            Console.WriteLine("Deserialized Array:");
            foreach (var item in deserializedArray)
            {
                Console.WriteLine(item);
            }
        }
    
        static async Task Main(string[] args)
        {
            try
            {
                var example = new TableStorageArray();
                await example.StorageAsync();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }
        }
    }
    

    Output:

    Table connected or created.
    Deserialized Array:
    Value1
    Value6
    Value9