Microsoft.Azure.Cosmos.Table LocationMode.SecondaryOnly RA-GRS 异常此操作只能针对主存储位置执行

Microsoft.Azure.Cosmos.Table LocationMode.SecondaryOnly RA-GRS Exception This operation can only be executed against the primary storage location

在解析使用 SAS 令牌的连接字符串时,我很难 Microsoft.Azure.Cosmos.Table 自动初始化 SecondaryUri。

所以我最终在连接字符串中明确指定了 TableSecondaryEndpoint,这有效,但我无法查询辅助节点,因为 SDK 在尝试请求之前抛出了异常。

在我的测试中,我发现这是 Microsoft.WindowsAzure.Storage.Table 8.7.0 中不存在的回归(Microsoft.Azure.Cosmos.Table 1.0.6 的基础)

非常欢迎专家意见,这点。谢谢。

这里异常的项目代码(也复制在下面): https://github.com/golfalot/SOshowAzureTableBug

附带问题详细说明了此处提出的 SecondaryUri 初始化问题: https://github.com/Azure/azure-cosmos-table-dotnet/issues/36

using System;
using System.Collections.Generic;

using LEGACY_STORAGE = Microsoft.WindowsAzure.Storage;
using LEGACY_RETRY = Microsoft.WindowsAzure.Storage.RetryPolicies;
using LEGACY_TABLE = Microsoft.WindowsAzure.Storage.Table; //8.7.0 because this is the base for 1.0.6

using NEWEST_TABLE = Microsoft.Azure.Cosmos.Table; // version 1.0.6
using Microsoft.Azure.Cosmos.Table; // had to add this to get access CreateCloudTableClient extension method

using System.Diagnostics;

namespace SOshowAzureTableBug
{
    class Program
    {
        // the SAS token is immaterial in reproducing the problem
        const string connectionTableSAS = "TableSecondaryEndpoint=http://127.0.0.1:10002/devstoreaccount1-secondary;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;SharedAccessSignature=immaterial";
        static void Main(string[] args)
        {

            /* Legacy Table SDK */
            var storageAccountLegacy = LEGACY_STORAGE.CloudStorageAccount.Parse(connectionTableSAS);
            var tableClientLegacy = storageAccountLegacy.CreateCloudTableClient();
            Debug.Assert(tableClientLegacy.StorageUri.SecondaryUri != null); // demonstrate SecondaryUri initialised

            var tableRequestOptionsLegacy = new LEGACY_TABLE.TableRequestOptions () { LocationMode = LEGACY_RETRY.LocationMode.SecondaryOnly };
            tableClientLegacy.DefaultRequestOptions = tableRequestOptionsLegacy;

            var tableLegacy = tableClientLegacy.GetTableReference("foo"); // don't need table to exist to show the issue
            var retrieveOperation = LEGACY_TABLE.TableOperation.Retrieve(string.Empty, string.Empty, new List<string>() { "bar" });

            var tableResult = tableLegacy.Execute(retrieveOperation);
            Console.WriteLine("Legacy PASS");


            /* Newset Table SDK */
            var storageAccountNewest = NEWEST_TABLE.CloudStorageAccount.Parse(connectionTableSAS);
            var tableClientNewest = storageAccountNewest.CreateCloudTableClient(new TableClientConfiguration());
            Debug.Assert(tableClientNewest.StorageUri.SecondaryUri != null); // demonstrate SecondaryUri initialised

            var tableRequestOptionsNewest = new NEWEST_TABLE.TableRequestOptions() { LocationMode = NEWEST_TABLE.LocationMode.SecondaryOnly };
            tableClientNewest.DefaultRequestOptions = tableRequestOptionsNewest;

            var tableNewset = tableClientNewest.GetTableReference("foo"); // don't need table to exist to show the issue
            var retrieveOperationNewset = NEWEST_TABLE.TableOperation.Retrieve(string.Empty, string.Empty, new List<string>() { "bar" });

            /* throws Microsoft.Azure.Cosmos.Table.StorageException
             * Exception thrown while initializing request: This operation can only be executed against the primary storage location
             */
            var tableResultNewset = tableNewset.Execute(retrieveOperationNewset);

            Console.WriteLine("Press any key to exit");
            Console.Read();
        }
    }
}

我相信您遇到了 SDK 的错误。

当我尝试以下代码时,出现了与您相同的错误:

        var account = CloudStorageAccount.Parse(connectionString);

        var requestOptions = new TableRequestOptions()
        {
            LocationMode = LocationMode.SecondaryOnly
        };
        var client = account.CreateCloudTableClient();
        client.DefaultRequestOptions = requestOptions;
        var table = client.GetTableReference("myTable");
        var op = TableOperation.Retrieve("", "");
        var result1 = table.Execute(op);

我反编译了库代码,找到了罪魁祸首源代码:

if (commandLocationMode == CommandLocationMode.PrimaryOnly)
                {
                    if (restCMD.LocationMode == LocationMode.SecondaryOnly)
                    {
                        throw new InvalidOperationException("This operation can only be executed against the primary storage location.");//This is the error that gets thrown.
                    }
                    Logger.LogInformational(executionState.OperationContext, "This operation can only be executed against the primary storage location.", Array.Empty<object>());
                    executionState.CurrentLocation = StorageLocation.Primary;
                    restCMD.LocationMode = LocationMode.PrimaryOnly;
                }

但是,如果我不在客户端级别设置 DefaultRequestOptions 并在下面的 Execute 方法中指定它,我不会收到错误,但那是因为命中了主要端点中学的(我在 Fiddler 中检查过)。

        var account = CloudStorageAccount.Parse(connectionString);

        var requestOptions = new TableRequestOptions()
        {
            LocationMode = LocationMode.SecondaryOnly
        };
        var client = account.CreateCloudTableClient();
        var table = client.GetTableReference("myTable");
        var op = TableOperation.Retrieve("", "");
        var result1 = table.Execute(op, requestOptions);

解决方法

如果您的 objective 是从辅助位置查询实体,那么您可以在 CloudTable 上使用 ExecuteQuery 方法,如下所示。这有效(同样,我检查了 Fiddler)。

        var account = CloudStorageAccount.Parse(connectionString);

        var requestOptions = new TableRequestOptions()
        {
            LocationMode = LocationMode.SecondaryOnly
        };
        var client = account.CreateCloudTableClient();
        client.DefaultRequestOptions = requestOptions;
        var table = client.GetTableReference("myTable");
        TableQuery query = new TableQuery();
        var result = table.ExecuteQuery(query).ToList();