0

I have some code which queries an Azure storage table, then returns a collection of results/entities.

These results/entities are then used in another method, and if a result in that method is false, update a property on the entity and then store it back in the table by calling UpdateEntityAsync.

This results in the following exception:

The specified resource does not exist.

I don't understand why this exception is being thrown while I'm modifying an entity retrieved from the same table.

The code for retrieving the entities:

public async Task<IReadOnlyCollection<Message>> GetUndeliveredAsync()
{
    const int maxResultsPerPage = 100;
    IEnumerable<string>? selectProps = null;
    var results = new List<Message>();

    var tableClient = _tableService.GetTableClient();

    var queryResults = tableClient.QueryAsync<Message>($"{nameof(Message.IsDelivered)} eq false and {nameof(Message.FailureCount)} lt {MaxFailureCount}",
                                                                       maxResultsPerPage,
                                                                       selectProps,
                                                                       CancellationToken.None);

    await foreach (var queryResult in queryResults)
    {
        results.Add(queryResult);
    }

    return results;
}

The results from that ^^ method are then passed to...

public async Task AttemptDeliveryAndRecordAsync(Message message)
{
    // ... some business logic...

    // Dispatch the message.
    var dispatchResponse = await _dispatcher.AttemptDeliveryAsync(message, remoteUri);

    // Record whether successful.
    message.IsDelivered = dispatchResponse.IsSuccessful;
    message.LastFailureReason = dispatchResponse.FailureReason;

    // If unsuccessful, update the FailureCount property.
    if (!message.IsDelivered)
    {
        message.IncrementFailureCount(); // Increases an int property by 1.
    }

    // Store the updated Message object.
    await _tableService.UpdateTableEntityAsync(message);
}

And now the code for updating the storage table...

public async Task UpdateTableEntityAsync(ITableEntity tableEntity)
{
    var tableClient = GetTableClient();

    try
    {
        await tableClient.UpdateEntityAsync(tableEntity, ETag.All, TableUpdateMode.Replace);
    }
    catch (Exception exception)
    {
        throw new Exception($"Unable to update {tableEntity.GetType().Name}: {exception.Message}");
    }
}

Can anyone explain why the retrieved-and-modified entity cannot be updated in the table?

If I call AddEntityAsync then this returns a 409 ("The specified entity already exists.") exception - presumably this checks only the partition and row keys, whereas UpdateEntityAsync is also doing something with the Etag... but I'm not modifying the etag so I don't understand why this is happening.

UPDATE

Here is the class that I'm trying to update in the storage table - there is actually a hierarchy of two classes, a base class and a derived type:

public class InternalMessage : Message, ITableEntity
{
    // Default constructor, required for JSON deserialisation.
    public InternalMessage()
    {
    }

    public InternalMessage(Message message, string remoteUri)
    {
        foreach (var propInfo in message.GetType().GetProperties(System.Reflection.BindingFlags.Instance|System.Reflection.BindingFlags.Public))
        {
            propInfo.SetValue(this, propInfo.GetValue(message));
        }

        RemoteUri = remoteUri;
    }

    public ETag ETag { get; set; }

    public int FailureCount { get; set; } = default;

    public bool IsDelivered { get; set; } = default;

    public string? LastFailureReason { get; set; } = default;

    public string PartitionKey
    {
        get => ProjectId.ToString();
        set
        {
            if (!Guid.TryParse(value, out var projectId))
            {
                throw new ArgumentException($"Invalid value for {nameof(InternalMessage)}.{nameof(PartitionKey)}: {value}.");
            }

            ProjectId = projectId;
        }
    }

    public string? RemoteUri { get; set; }

    public string RowKey
    {
        get => HttpUtility.UrlEncode(Status);
        set => Status = value;
    }

    public DateTimeOffset? Timestamp { get; set; } = default!;

    public void IncrementFailureCount()
    {
        FailureCount++;
    }

    public Message ToMessage()
    {
        var message = new Message();

        foreach (var propInfo in message.GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public))
        {
            propInfo.SetValue(message, propInfo.GetValue(this));
        }

        return message;
    }
}

public class Message
{
    public Message()
    {
        OccurredOn = DateTime.UtcNow;
    }

    public Guid ProjectId { get; set; }

    public string Status { get; set; }

    public DateTime OccurredOn { get; set; }

    public string Username { get; set; }
}

It's an instance of InternalMessage which I'm trying to update - it successfully stores the initial instance, but updating the same table entity fails.

Furthermore, here is a screenshot of the entity/object attempting to be stored...

enter image description here

And lastly, here is a screenshot of the exception...

enter image description here

awj
  • 7,482
  • 10
  • 66
  • 120
  • Please edit your question and include the code for your `Message` class. I don't think the issue is with Etag because in that you would have got a 412 (Precondition failed) error. Also check the values of the entity passed to your UpdateTableEntityAsync method. – Gaurav Mantri Jul 02 '23 at 10:58
  • @GauravMantri I've added the `Message` class. – awj Jul 02 '23 at 19:25

0 Answers0