如何批量检索实体?

How to batch retrieve entities?

在 Azure table 存储中,如何查询与分区中特定行键匹配的一组实体???

我正在使用 Azure table 存储,需要检索与分区中的一组行键匹配的一组实体。

基本上如果这是 SQL 它可能看起来像这样:

SELECT TOP 1 SomeKey
FROM TableName WHERE SomeKey IN (1, 2, 3, 4, 5);

我想节省成本并减少做一堆 table 检索操作,我可以使用 table 批处理操作来完成。出于某种原因,我收到一个异常:

"A batch transaction with a retrieve operation cannot contain any other operations"

这是我的代码:

public async Task<IList<GalleryPhoto>> GetDomainEntitiesAsync(int someId, IList<Guid> entityIds)
{
    try
    {
        var client = _storageAccount.CreateCloudTableClient();
        var table = client.GetTableReference("SomeTable");
        var batchOperation = new TableBatchOperation();
        var counter = 0;
        var myDomainEntities = new List<MyDomainEntity>();

        foreach (var id in entityIds)
        {
            if (counter < 100)
            {
                batchOperation.Add(TableOperation.Retrieve<MyDomainEntityTableEntity>(someId.ToString(CultureInfo.InvariantCulture), id.ToString()));
                ++counter;
            }
            else
            {
                var batchResults = await table.ExecuteBatchAsync(batchOperation);
                var batchResultEntities = batchResults.Select(o => ((MyDomainEntityTableEntity)o.Result).ToMyDomainEntity()).ToList();
                myDomainEntities .AddRange(batchResultEntities );
                batchOperation.Clear();
                counter = 0;
            }
        }

        return myDomainEntities;
    }
    catch (Exception ex)
    {
        _logger.Error(ex);
        throw;
    }
}

如何在不手动遍历行键集并对每个行键执行单独的检索 table 操作的情况下实现目标?我不想承担与执行此操作相关的成本,因为我可能有数百个要过滤的行键。

由于有数百个行键,因此排除了将 $filter 与行键列表一起使用的可能性(无论如何都会导致部分分区扫描)。

根据您收到的错误,批处理似乎同时包含查询和其他类型的操作(这是不允许的)。从您的代码片段中,我不明白您为什么会收到该错误。

您唯一的其他选择是执行单个查询。不过,您可以异步执行这些操作,因此您不必等待每个 return。 Table 存储在给定分区上每秒提供超过 2,000 个事务,因此这是一个可行的解决方案。

不确定我一开始是怎么错过这个的,但这是 TableBatchOperation 类型的 MSDN 文档中的一个片段:

A batch operation may contain up to 100 individual table operations, with the requirement that each operation entity must have same partition key. A batch with a retrieve operation cannot contain any other operations. Note that the total payload of a batch operation is limited to 4MB.

我最终按照 David Makogon 的建议异步执行了各个检索操作。

我创造了自己的贫民窟link-table。我知道它效率不高(也许没问题),但我只在数据未在本地缓存时才发出此请求,这仅意味着切换设备。无论如何,这似乎有效。检查两个数组的长度让我推迟 context.done();

var query = new azure.TableQuery()
          .top(1000)
          .where('PartitionKey eq ?', 'link-' + req.query.email.toLowerCase() );

           tableSvc.queryEntities('linkUserMarker',query, null, function(error, result, response) {

            if( !error && result ){

                var markers = [];
                result.entries.forEach(function(e){
                   tableSvc.retrieveEntity('markerTable', e.markerPartition._,  e.RowKey._.toString() , function(error, marker, response){

                       markers.push( marker );
                       if( markers.length == result.entries.length ){
                            context.res = {
                            status:200,
                                body:{
                                    status:'error',
                                    markers: markers
                                }
                            };
                            context.done();
                       }

                   });
                });

            }   else {
                notFound(error);
            }
        });

我制作了一个辅助方法来在每个分区的单个请求中完成它。

这样使用:

var items = table.RetrieveMany<MyDomainEntity>(partitionKey, nameof(TableEntity.RowKey), 
     rowKeysList, columnsToSelect);

这是帮助方法:

    public static List<T> RetrieveMany<T>(this CloudTable table, string partitionKey, 
        string propertyName, IEnumerable<string> valuesRange, 
        List<string> columnsToSelect = null)
        where T : TableEntity, new()
    {
        var enitites = table.ExecuteQuery(new TableQuery<T>()
            .Where(TableQuery.CombineFilters(
                TableQuery.GenerateFilterCondition(
                    nameof(TableEntity.PartitionKey),
                    QueryComparisons.Equal,
                    partitionKey),
                TableOperators.And,
                GenerateIsInRangeFilter(
                    propertyName,
                    valuesRange)
            ))
            .Select(columnsToSelect))
            .ToList();
        return enitites;
    }


    public static string GenerateIsInRangeFilter(string propertyName, 
         IEnumerable<string> valuesRange)
    {
        string finalFilter = valuesRange.NotNull(nameof(valuesRange))
            .Distinct()
            .Aggregate((string)null, (filterSeed, value) =>
            {
                string equalsFilter = TableQuery.GenerateFilterCondition(
                    propertyName,
                    QueryComparisons.Equal,
                    value);
                return filterSeed == null ?
                    equalsFilter :
                    TableQuery.CombineFilters(filterSeed,
                                              TableOperators.Or,
                                              equalsFilter);
            });
        return finalFilter ?? "";
    }

我已经在 rowKeysList 中对少于 100 个值进行了测试,但是,如果有更多值甚至抛出异常,我们总是可以将请求拆分成多个部分。