/// <summary> /// Retrieve a <see cref="IAsyncEnumerable{T}"/> using a SOQL query. Batches will be retrieved asynchronously. /// <para>When using the iterator, the initial result batch will be returned as soon as it is received. The additional result batches will be retrieved only as needed.</para> /// </summary> /// <param name="queryString">SOQL query string, without any URL escaping/encoding</param> /// <param name="queryAll">Optional. True if deleted records are to be included.await Defaults to false.</param> /// <param name="batchSize">Optional. Size of result batches between 200 and 2000</param> /// <param name="cancellationToken">Optional. Cancellation token</param> /// <returns><see cref="IAsyncEnumerable{T}"/> of results</returns> public async IAsyncEnumerable <T> QueryAsync <T>(string queryString, bool queryAll = false, int?batchSize = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) { Dictionary <string, string> headers = new Dictionary <string, string>(); // Add call options Dictionary <string, string> callOptions = HeaderFormatter.SforceCallOptions(ClientName); headers.AddRange(callOptions); // Add query options headers if batch size specified if (batchSize.HasValue) { Dictionary <string, string> queryOptions = HeaderFormatter.SforceQueryOptions(batchSize.Value); headers.AddRange(queryOptions); } var jsonClient = new JsonClient(AccessToken, _httpClient); var nextRecordsUri = UriFormatter.Query(InstanceUrl, ApiVersion, queryString, queryAll); bool hasMoreRecords = true; while (hasMoreRecords) { var qr = await jsonClient.HttpGetAsync <QueryResult <T> >(nextRecordsUri, headers); #if DEBUG Debug.WriteLine($"Got query resuts, {qr.TotalSize} totalSize, {qr.Records.Count} in this batch, final batch: {qr.Done}"); #endif if (!string.IsNullOrEmpty(qr.NextRecordsUrl)) { nextRecordsUri = new Uri(new Uri(InstanceUrl), qr.NextRecordsUrl); hasMoreRecords = true; } else { // Normally if a query has remaining batches, NextRecordsUrl will have a value, and Done will be false. // In case of some unforseen error, consider the results done if we're missing the NextRecordsURL hasMoreRecords = false; } foreach (T record in qr.Records) { yield return(record); } } }
/// <summary> /// Retrieve a <see cref="IAsyncEnumerator{T}"/> using a SOQL query. Batches will be retrieved asynchronously. /// <para>When using the iterator, the initial result batch will be returned as soon as it is received. The additional result batches will be retrieved only as needed.</para> /// </summary> /// <param name="queryString">SOQL query string, without any URL escaping/encoding</param> /// <param name="queryAll">Optional. True if deleted records are to be included.await Defaults to false.</param> /// <param name="batchSize">Optional. Size of result batches between 200 and 2000</param> /// <returns><see cref="IAsyncEnumerator{T}"/> of results</returns> public IAsyncEnumerator <T> QueryAsyncEnumerator <T>(string queryString, bool queryAll = false, int?batchSize = null) { Dictionary <string, string> headers = new Dictionary <string, string>(); //Add call options Dictionary <string, string> callOptions = HeaderFormatter.SforceCallOptions(ClientName); headers.AddRange(callOptions); //Add query options headers if batch size specified if (batchSize.HasValue) { Dictionary <string, string> queryOptions = HeaderFormatter.SforceQueryOptions(batchSize.Value); headers.AddRange(queryOptions); } var jsonClient = new JsonClient(AccessToken, _httpClient); // Enumerator on the current batch items IEnumerator <T> currentBatchEnumerator = null; var done = false; var nextRecordsUri = UriFormatter.Query(InstanceUrl, ApiVersion, queryString, queryAll); return(AsyncEnumerable.CreateEnumerator(MoveNextAsync, Current, Dispose)); async Task <bool> MoveNextAsync(CancellationToken token) { if (token.IsCancellationRequested) { return(false); } // If items remain in the current Batch enumerator, go to next item if (currentBatchEnumerator?.MoveNext() == true) { return(true); } // if done, no more items. if (done) { return(false); } // else : no enumerator or currentBatchEnumerator ended // so get the next batch var qr = await jsonClient.HttpGetAsync <QueryResult <T> >(nextRecordsUri, headers); #if DEBUG Debug.WriteLine($"Got query resuts, {qr.TotalSize} totalSize, {qr.Records.Count} in this batch, final batch: {qr.Done}"); #endif currentBatchEnumerator = qr.Records.GetEnumerator(); if (!string.IsNullOrEmpty(qr.NextRecordsUrl)) { nextRecordsUri = new Uri(new Uri(InstanceUrl), qr.NextRecordsUrl); done = false; } else { //Normally if query has remaining batches, NextRecordsUrl will have a value, and Done will be false. //In case of some unforseen error, flag the result as done if we're missing the NextRecordsURL done = true; } return(currentBatchEnumerator.MoveNext()); } T Current() { return(currentBatchEnumerator == null ? default(T) : currentBatchEnumerator.Current); } void Dispose() { currentBatchEnumerator?.Dispose(); jsonClient.Dispose(); } }