68

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.

Ohad Schneider
  • 36,600
  • 15
  • 168
  • 198
Jose Ch.
  • 3,856
  • 1
  • 20
  • 34
  • 1
    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 Jan 14 '18 at 00:51

3 Answers3

94

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;
    }
Jose Ch.
  • 3,856
  • 1
  • 20
  • 34
  • 1
    Joe, yes, your code example is the best way to do this (using `ExecuteQuerySegmentedAsync` and combining the results from each segment). Your code looks good. The only suggestion I have is for you to, if possible, pre-allocate the `items` list to a size that will be big enough to hold all the entities most of the time by using the constructor that accepts an integer. This will reduce the amount of resizing needed. – Mike Fisher Jun 19 '14 at 05:44
  • 1
    @MikeFisher Thanks for your comment, will consider. However, it’s hard to know a priori the results number from a query. Perhaps it can be optimized on each iteration. You resize to a list 1000 records larger. – Jose Ch. Jul 03 '14 at 03:05
  • 3
    You forgot to pass in the cancellationToken into ExecuteQuerySegmentedAsync – Carl Prothman Dec 06 '16 at 22:48
  • Whenever I call 'TableQuerySegment seg = await table.ExecuteQuerySegmentedAsync(query, token);' my app seems to hang. I'm doing this in a UWP project. – Adrian K Mar 10 '17 at 20:46
  • Why no cancellationToken is passed to ExecuteQuerySegmentedAsync? Maybe because you don't want this method to throw an OperationCancelledException? Do you want it to return the results you've already received? Wouldn't it be better to throw an exception instead and to pass the CancellationToken into ExecuteQuerySegmentedAsync() method to stop the request immediately? Otherwise, the calling code of the above extension method may wrongly take the results, thinking these are all the items, whereas the operation was cancelled and these are just some of them. – Artemious Apr 24 '18 at 11:20
  • @Artemious even if all your statements are correct it doesn't make sense to not pass the `ct` (CancellationToken) into the `ExecuteQuerySegmentedAsync` since this extensions doesn't use it anywhere else. I think @Jose Ch. forgot to add it, I tried to edit the post because ct is lower than 6 characters : – Rutix Aug 25 '21 at 14:33
15

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.

Davor
  • 184
  • 1
  • 7
1

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()
Zze
  • 18,229
  • 13
  • 85
  • 118