You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

更新Azure存储表检索实体时遇“指定资源不存在”错误求助

问题:Azure存储表更新实体时抛出"The specified resource does not exist"异常

查询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; }
}

问题根源

  1. 类型不匹配导致键值不一致:
    检索时使用QueryAsync<Message>,但实际存储的是InternalMessage。Message未实现ITableEntity,查询返回的对象不会包含InternalMessage中RowKey的UrlEncode处理逻辑——表中存储的RowKey是编码后的Status值,但更新时传递的Message对象的RowKey(转换后)是原始未编码值,导致Azure找不到对应实体。

  2. 实体转换丢失关键属性:
    InternalMessage转换为Message时,丢失了ETag、PartitionKey、RowKey等ITableEntity接口必需的属性,即使强制转换为ITableEntity,这些属性也无正确值,更新请求自然失败。


解决方案

  1. 统一实体类型:
    检索时直接查询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);
    }
    
  2. 保留键值处理一致性:
    确保更新时使用的实体,其PartitionKey和RowKey的生成逻辑与存储时完全一致(特别是RowKey的UrlEncode处理)。

  3. 维护ETag完整性:
    若需乐观并发控制,更新时传递实体自身的ETag而非ETag.All:

    await tableClient.UpdateEntityAsync(tableEntity, tableEntity.ETag, TableUpdateMode.Replace);
    

内容的提问来源于stack exchange,提问作者awj

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 16:35:17