簡體   English   中英

如何批量檢索實體?

[英]How to batch retrieve entities?

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

如何在不手動循環遍歷行鍵集並為每個行鍵執行單獨的檢索表操作的情況下實現我的目標? 我不想承擔與執行此操作相關的成本,因為我可能有數百個要過濾的行鍵。

我制作了一個輔助方法來在每個分區的單個請求中執行此操作。

像這樣使用它:

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 個值,但是,如果它甚至拋出異常(如果有更多),我們總是可以將請求分成幾部分。

對於數百個行鍵,這排除了將$filter與行鍵列表一起使用(這無論如何都會導致部分分區掃描)。

由於您遇到的錯誤,該批次似乎同時包含查詢和其他類型的操作(這是不允許的)。 從您的代碼片段中,我不明白您為什么會收到該錯誤。

您唯一的其他選擇是執行單個查詢。 不過,您可以異步執行這些操作,因此您不必等待每個返回。 表存儲在給定分區上提供超過 2,000 個事務/秒,因此它是一個可行的解決方案。

不知道我是怎么錯過的,但這里是 MSDN 文檔中 TableBatchOperation 類型的片段:

一個批處理操作最多可以包含 100 個單獨的表操作,並且要求每個操作實體必須具有相同的分區鍵。 具有檢索操作的批次不能包含任何其他操作。 請注意,批處理操作的總有效負載限制為 4MB。

我最終按照 David Makogon 的建議異步執行了單個檢索操作。

我制作了自己的貧民區鏈接表。 我知道它效率不高(也許沒問題),但我只在數據未在本地緩存時才發出此請求,這僅意味着切換設備。 無論如何,這似乎有效。 檢查兩個數組的長度讓我推遲 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);
            }
        });

我在尋找解決方案時看到了您的帖子,就我而言,我需要同時查找多個 id。

因為沒有包含 linq 支持( https://docs.microsoft.com/en-us/rest/api/storageservices/query-operators-supported-for-the-table-service )我只是做了一個巨大的或等於鏈.

到目前為止似乎對我有用,希望它可以幫助任何人。

public async Task<ResponseModel<ICollection<TAppModel>>> ExecuteAsync(
    ICollection<Guid> ids,
    CancellationToken cancellationToken = default
)
{
    if (!ids.Any())
        throw new ArgumentOutOfRangeException();

    // https://docs.microsoft.com/en-us/rest/api/storageservices/query-operators-supported-for-the-table-service
    // Contains not support so make a massive or equals statement...lol
    var item = Expression.Parameter(typeof(TTableModel), typeof(TTableModel).FullName);
    var expressions = ids
        .Select(
            id => Expression.Equal(
                Expression.Constant(id.ToString()),
                Expression.MakeMemberAccess(
                    Expression.Parameter(typeof(TTableModel), nameof(ITableEntity.RowKey)),
                    typeof(TTableModel).GetProperty(nameof(ITableEntity.RowKey))
                )
            )
        )
        .ToList();

    var builderExpression = expressions.First();
    builderExpression = expressions
        .Skip(1)
        .Aggregate(
            builderExpression, 
            Expression.Or
        );

    var finalExpression = Expression.Lambda<Func<TTableModel, bool>>(builderExpression, item);

    var result = await _azureTableService.FindAsync(
        finalExpression,
        cancellationToken
    );

    return new(
        result.Data?.Select(_ => _mapper.Map<TAppModel>(_)).ToList(),
        result.Succeeded,
        result.User,
        result.Messages.ToArray()
    );
}


    public async Task<ResponseModel<ICollection<TTableEntity>>> FindAsync(
        Expression<Func<TTableEntity,bool>> filter,
        CancellationToken ct = default
    )
    {
        try
        {
            var queryResultsFilter = _tableClient.QueryAsync<TTableEntity>(
                FilterExpressionTree(filter),
                cancellationToken: ct
            );

            var items = new List<TTableEntity>();
            await foreach (TTableEntity qEntity in queryResultsFilter)
                items.Add(qEntity);

            return new ResponseModel<ICollection<TTableEntity>>(items);
        }
        catch (Exception exception)
        {
            _logger.Error(
                nameof(FindAsync),
                exception,
                exception.Message
            );

            // OBSFUCATE
            // TODO PASS ERROR ID
            throw new Exception();
        }
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM