64
votes

Want to execute queries Async on Azure Storage Client Version 4.0.1

There is NO method ExecuteQueryAsync()..

I am missing something? Should we continue to use the ExecuteQuerySegmentedAsync still? Thanks.

3
Can anyone point me to a doc that talks about which methods are available in the storage API and which aren't on different architectures? It's frustrating that the docs and getting started guides say to do one thing and then the methods aren't there.Rory

3 Answers

91
votes

I end up making an extension method to use ExecuteQuerySegmentedAsync. I am not sure whether this solution is optimal, if anybody has any comment please don’t hesitate.

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

When table query contains take clause specified solution will return more items than requested by query. Small change of while expression will solve that problem.

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

EDITED: Thanks to a suggestion from PaulG, corrected the issue with result count when query contains take clause and ExecuteQuerySegmentedAsync returns items in several passes.

1
votes

This is in addition on to @JoseCh.'s answer.

Here is an extension method which allows you to specify an 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;
     }
}

It can be used if you only want to return the result set of a single column in storage:

// 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()