如何异步执行 Azure 表存储查询?客户端版本为 4.0.1。

68

想要在Azure存储客户端版本4.0.1上异步执行查询?

很遗憾,没有ExecuteQueryAsync()方法。

我是否漏掉了什么?我们是否继续使用ExecuteQuerySegmentedAsync?谢谢。


1
有没有人能指导我一份文档,讲述存储API中哪些方法在不同架构上可用,哪些不可用?文件和入门指南说要做一件事,但实际上方法并不存在,这真是令人沮丧。 - Rory
3个回答

94

我最终创建了一个扩展方法来使用ExecuteQuerySegmentedAsync。我不确定这个解决方案是否是最优的,如果有任何评论,请不要犹豫。

public static async Task<IList<T>> ExecuteQueryAsync<T>(this CloudTable table, TableQuery<T> query, CancellationToken ct = default(CancellationToken), Action<IList<T>> onProgress = null) where T : ITableEntity, new()
    {

        var items = new List<T>();
        TableContinuationToken token = null;

        do
        {

            TableQuerySegment<T> seg = await table.ExecuteQuerySegmentedAsync<T>(query, token);
            token = seg.ContinuationToken;
            items.AddRange(seg);
            if (onProgress != null) onProgress(items);

        } while (token != null && !ct.IsCancellationRequested);

        return items;
    }

1
Joe,是的,你的代码示例是做这个的最佳方式(使用ExecuteQuerySegmentedAsync并组合每个段的结果)。你的代码看起来很好。我唯一的建议是,如果可能的话,通过使用接受整数的构造函数来预先分配足够大以容纳大多数实体的items列表的大小。这将减少所需的调整大小的数量。 - Mike Fisher
1
@MikeFisher 感谢您的评论,我会考虑。然而,很难事先知道查询结果的数量。也许可以在每次迭代中进行优化。您可以将列表调整为比原来多1000条记录。 - Jose Ch.
3
您忘记将 cancellationToken 传递到 ExecuteQuerySegmentedAsync 方法中。 - Carl Prothman
每当我调用 'TableQuerySegment<T> seg = await table.ExecuteQuerySegmentedAsync<T>(query, token);' 时,我的应用程序似乎会停滞不前。我正在一个 UWP 项目中执行此操作。 - Adrian K
为什么ExecuteQuerySegmentedAsync没有传递cancellationToken?也许是因为你不想让这个方法抛出OperationCancelledException异常?你想要它返回已经接收到的结果吗?相比之下,抛出异常并将CancellationToken传递到ExecuteQuerySegmentedAsync()方法中立即停止请求会更好吗?否则,上述扩展方法的调用代码可能会错误地获取结果,认为这些是所有项目,而操作已被取消,这只是其中的一部分。 - Artemious
@Artemious 即使你所有的陈述都是正确的,也没有道理不将 ct(CancellationToken)传递到 ExecuteQuerySegmentedAsync 中,因为这个扩展在其他地方都没有使用它。我认为 @Jose Ch. 忘记添加它了,我试图编辑帖子,因为 ct 小于 6 个字符 :< - Rutix

15

当表查询包含指定的take子句时,解决方案将返回比查询请求的更多的项目。稍微改变while表达式即可解决这个问题。

public static async Task<IList<T>> ExecuteQueryAsync<T>(this CloudTable table, TableQuery<T> query, CancellationToken ct = default(CancellationToken), Action<IList<T>> onProgress = null) where T : ITableEntity, new()
{
    var runningQuery = new TableQuery<T>()
    {
        FilterString = query.FilterString,
        SelectColumns = query.SelectColumns
    };

    var items = new List<T>();
    TableContinuationToken token = null;

    do
    {
        runningQuery.TakeCount = query.TakeCount - items.Count;

        TableQuerySegment<T> seg = await table.ExecuteQuerySegmentedAsync<T>(runningQuery, token);
        token = seg.ContinuationToken;
        items.AddRange(seg);
        if (onProgress != null) onProgress(items);

    } while (token != null && !ct.IsCancellationRequested && (query.TakeCount == null || items.Count < query.TakeCount.Value));

    return items;
}

编辑:由于PaulG的建议,纠正了当查询包含take子句且ExecuteQuerySegmentedAsync返回多次时结果计数的问题。


1
这是对@JoseCh回答的补充。
下面是一个扩展方法,可以让您指定一个EntityResolver:
public static async Task<IList<TResult>> ExecuteQueryAsync<T, TResult>(this CloudTable table, TableQuery query, EntityResolver<TResult> resolver, Action<IList<TResult>> onProgress = null, CancellationToken cancelToken = default(CancellationToken))
            where T : ITableEntity, new()
{
    var items = new List<TResult>();
    TableContinuationToken token = null;

    do
    {
        TableQuerySegment<TResult> seg = await table.ExecuteQuerySegmentedAsync(query: query, resolver: resolver, token: new TableContinuationToken(), cancellationToken: cancelToken).ConfigureAwait(false);
        token = seg.ContinuationToken;
        items.AddRange(seg);
        onProgress?.Invoke(items);
     }
     while (token != null && !cancelToken.IsCancellationRequested);
         return items;
     }
}

如果您只想返回存储中单个列的结果集,则可以使用它:

// maps to a column name in storage
string propertyName = nameof(example.Category);

// Define the query, and select only the Category property.
var projectionQuery = new TableQuery().Select(new string[] { propertyName });

// Define an entity resolver to work with the entity after retrieval.
EntityResolver<string> resolver = (pk, rk, ts, props, etag) => props.ContainsKey(propertyName) ? props[propertyName].StringValue : null;

var categories = (await someTable.ExecuteQueryAsync<DynamicTableEntity, string>(query: projectionQuery, resolver: resolver).ConfigureAwait(false)).ToList()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接