如何批量检索实体?

时间:2016-01-02 05:48:25

标签: c# azure azure-table-storage

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

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

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

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

我想节省成本并减少做一堆表检索操作,我可以使用表批量操作来完成它。出于某种原因,我得到了一个例外:

  

“具有检索操作的批处理事务不能包含任何其他操作”

这是我的代码:

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

如何在不手动循环遍历行键并为每个行执行单独的Retrieve表操作的情况下,如何实现我所追求的目标?我不想承担与此相关的成本,因为我可以拥有数百个我想要过滤的行键。

4 个答案:

答案 0 :(得分:1)

使用数百个行密钥,使用$filter排除行密钥列表(无论如何都会导致部分分区扫描)。

由于您遇到错误,批处理似乎包含查询和其他类型的操作(不允许)。我从你的代码片段中看不出你为什么会收到这个错误。

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

答案 1 :(得分:1)

我在每个分区的单个请求中创建了一个帮助方法。

像这样使用:

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个值,但是,如果有更多值,它甚至会抛出异常,我们总是可以将请求拆分为多个部分。

答案 2 :(得分:0)

我不确定我是如何错过这一点的,但这里是TableBatchOperation类型的MSDN文档的片段:

  

批处理操作最多可包含100个单独的表操作,并要求每个操作实体必须具有相同的分区键。 具有检索操作的批处理不能包含任何其他操作。请注意,批处理操作的总有效负载限制为4MB。

我最终按照David Makogon的建议异步执行单独的检索操作。

答案 3 :(得分:0)

我制作了自己的贫民区链接表。我知道它不是那么有效(也许它很好)但我只是在本地缓存数据时才提出这个请求,这只意味着切换设备。无论如何,这似乎有效。检查两个数组的长度让我推迟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);
            }
        });