更新Azure存储表检索实体时遇“指定资源不存在”错误求助
查询Azure存储表获取实体集合,将实体传入业务方法修改属性后,调用UpdateEntityAsync存回表时抛出异常:"The specified resource does not exist";调用AddEntityAsync则返回409异常:"The specified entity already exists"。未修改ETag,无法理解问题原因。
检索实体代码
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; }
处理并更新实体的方法
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); }
更新存储表代码
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}"); } }
更新补充:实体类定义
实际存储和待更新的是继承自Message的InternalMessage类(实现ITableEntity接口),初始存储成功,但更新失败:
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; } }
问题根源
类型不匹配导致键值不一致:
检索时使用QueryAsync<Message>,但实际存储的是InternalMessage。Message未实现ITableEntity,查询返回的对象不会包含InternalMessage中RowKey的UrlEncode处理逻辑——表中存储的RowKey是编码后的Status值,但更新时传递的Message对象的RowKey(转换后)是原始未编码值,导致Azure找不到对应实体。实体转换丢失关键属性:
InternalMessage转换为Message时,丢失了ETag、PartitionKey、RowKey等ITableEntity接口必需的属性,即使强制转换为ITableEntity,这些属性也无正确值,更新请求自然失败。
解决方案
统一实体类型:
检索时直接查询InternalMessage类型,确保返回实体包含完整的ITableEntity属性:public async Task<IReadOnlyCollection<InternalMessage>> GetUndeliveredAsync() { const int maxResultsPerPage = 100; IEnumerable<string>? selectProps = null; var results = new List<InternalMessage>(); var tableClient = _tableService.GetTableClient(); var queryResults = tableClient.QueryAsync<InternalMessage>($"{nameof(InternalMessage.IsDelivered)} eq false and {nameof(InternalMessage.FailureCount)} lt {MaxFailureCount}", maxResultsPerPage, selectProps, CancellationToken.None); await foreach (var queryResult in queryResults) { results.Add(queryResult); } return results; }同时修改业务方法参数类型为
InternalMessage,避免类型转换:public async Task AttemptDeliveryAndRecordAsync(InternalMessage message) { // ... 业务逻辑直接修改message属性 await _tableService.UpdateTableEntityAsync(message); }保留键值处理一致性:
确保更新时使用的实体,其PartitionKey和RowKey的生成逻辑与存储时完全一致(特别是RowKey的UrlEncode处理)。维护ETag完整性:
若需乐观并发控制,更新时传递实体自身的ETag而非ETag.All:await tableClient.UpdateEntityAsync(tableEntity, tableEntity.ETag, TableUpdateMode.Replace);
内容的提问来源于stack exchange,提问作者awj

