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...
And lastly, here is a screenshot of the exception...