Exemple #1
0
        /// <summary>
        /// Get a basic SOQL COUNT() query result
        /// <para>The query must start with SELECT COUNT() FROM, with no named field in the count clause. COUNT() must be the only element in the SELECT list.</para>
        /// </summary>
        /// <param name="queryString">SOQL query string starting with SELECT COUNT() FROM</param>
        /// <param name="queryAll">True if deleted records are to be included</param>
        /// <returns>The <see cref="Task{Int}"/> returning the count</returns>
        public async Task <int> CountQuery(string queryString, bool queryAll = false)
        {
            // https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_count.htm
            // COUNT() must be the only element in the SELECT list.

            if (!queryString.Replace(" ", "").ToLower().StartsWith("selectcount()from"))
            {
                throw new ForceApiException("CountQueryAsync may only be used with a query starting with SELECT COUNT() FROM");
            }

            var jsonClient = new JsonClient(AccessToken, _httpClient);
            var uri        = UriFormatter.Query(InstanceUrl, ApiVersion, queryString);
            var qr         = await jsonClient.HttpGetAsync <QueryResult <object> >(uri);

            return(qr.TotalSize);
        }
        /// <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);
                }
            }
        }
Exemple #3
0
        /// <summary>
        /// Retrieve records using a SOQL query.
        /// <para>Will automatically retrieve the complete result set if split into batches. If you wan tto limit results, use the LIMIT operator in your query.</para>
        /// </summary>
        /// <param name="queryString">SOQL query string, without any URL escaping/encoding</param>
        /// <param name="queryAll">True if deleted records are to be included</param>
        /// <returns>List{T} of results</returns>
        public async Task <List <T> > Query <T>(string queryString, bool queryAll = false)
        {
#if DEBUG
            Stopwatch sw = new Stopwatch();
            sw.Start();
#endif
            try
            {
                var queryUri = UriFormatter.Query(InstanceUrl, ApiVersion, queryString, queryAll);

                JsonClient client = new JsonClient(AccessToken, _httpClient);

                List <T> results = new List <T>();

                bool   done           = false;
                string nextRecordsUrl = string.Empty;

                //larger result sets will be split into batches (sized according to system and account settings)
                //if additional batches are indicated retrieve the rest and append to the result set.
                do
                {
                    if (!string.IsNullOrEmpty(nextRecordsUrl))
                    {
                        queryUri = new Uri(new Uri(InstanceUrl), nextRecordsUrl);
                    }

                    QueryResult <T> qr = await client.HttpGetAsync <QueryResult <T> >(queryUri);

#if DEBUG
                    Debug.WriteLine(string.Format("Got query resuts, {0} totalSize, {1} in this batch, final batch: {2}",
                                                  qr.TotalSize, qr.Records.Count.ToString(), qr.Done.ToString()));
#endif

                    results.AddRange(qr.Records);

                    done = qr.Done;

                    nextRecordsUrl = qr.NextRecordsUrl;

                    if (!done && string.IsNullOrEmpty(nextRecordsUrl))
                    {
                        //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
                        //In this situation we'll just get the previous set again and be stuck in a loop.
                        done = true;
                    }
                } while (!done);

#if DEBUG
                sw.Stop();
                Debug.WriteLine(string.Format("Query results retrieved in {0}ms", sw.ElapsedMilliseconds.ToString()));
#endif

                return(results);
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Error querying: " + ex.Message);
                throw ex;
            }
        }
        /// <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();
            }
        }