public virtual async Task <FindResults <TResult> > FindAsAsync <TResult>(IRepositoryQuery query, ICommandOptions options = null) where TResult : class, new()
        {
            options = ConfigureOptions(options.As <T>());
            bool useSnapshotPaging = options.ShouldUseSnapshotPaging();
            // don't use caching with snapshot paging.
            bool allowCaching = IsCacheEnabled && useSnapshotPaging == false;

            await OnBeforeQueryAsync(query, options, typeof(TResult)).AnyContext();

            await RefreshForConsistency(query, options).AnyContext();

            async Task <FindResults <TResult> > GetNextPageFunc(FindResults <TResult> r)
            {
                var previousResults = r;

                if (previousResults == null)
                {
                    throw new ArgumentException(nameof(r));
                }

                string scrollId = previousResults.GetScrollId();

                if (!String.IsNullOrEmpty(scrollId))
                {
                    var scrollResponse = await _client.ScrollAsync <TResult>(options.GetSnapshotLifetime(), scrollId).AnyContext();

                    _logger.LogRequest(scrollResponse, options.GetQueryLogLevel());

                    var results = scrollResponse.ToFindResults();
                    results.Page    = previousResults.Page + 1;
                    results.HasMore = scrollResponse.Hits.Count >= options.GetLimit() || scrollResponse.Hits.Count >= options.GetMaxLimit();

                    // clear the scroll
                    if (!results.HasMore)
                    {
                        await _client.ClearScrollAsync(s => s.ScrollId(scrollId));
                    }

                    return(results);
                }

                if (options.ShouldUseSearchAfterPaging())
                {
                    options.SearchAfterToken(previousResults.GetSearchAfterToken());
                }

                if (options == null)
                {
                    return(new FindResults <TResult>());
                }

                options?.PageNumber(!options.HasPageNumber() ? 2 : options.GetPage() + 1);
                return(await FindAsAsync <TResult>(query, options).AnyContext());
            }

            string cacheSuffix = options?.HasPageLimit() == true?String.Concat(options.GetPage().ToString(), ":", options.GetLimit().ToString()) : null;

            FindResults <TResult> result;

            if (allowCaching)
            {
                result = await GetCachedQueryResultAsync <FindResults <TResult> >(options, cacheSuffix : cacheSuffix).AnyContext();

                if (result != null)
                {
                    ((IGetNextPage <TResult>)result).GetNextPageFunc = async r => await GetNextPageFunc(r).AnyContext();

                    return(result);
                }
            }

            ISearchResponse <TResult> response;

            if (useSnapshotPaging == false || !options.HasSnapshotScrollId())
            {
                var searchDescriptor = await CreateSearchDescriptorAsync(query, options).AnyContext();

                if (useSnapshotPaging)
                {
                    searchDescriptor.Scroll(options.GetSnapshotLifetime());
                }

                if (query.ShouldOnlyHaveIds())
                {
                    searchDescriptor.Source(false);
                }

                response = await _client.SearchAsync <TResult>(searchDescriptor).AnyContext();
            }
            else
            {
                response = await _client.ScrollAsync <TResult>(options.GetSnapshotLifetime(), options.GetSnapshotScrollId()).AnyContext();
            }

            if (response.IsValid)
            {
                _logger.LogRequest(response, options.GetQueryLogLevel());
            }
            else
            {
                if (response.ApiCall.HttpStatusCode.GetValueOrDefault() == 404)
                {
                    return(new FindResults <TResult>());
                }

                _logger.LogErrorRequest(response, "Error while searching");
                throw new ApplicationException(response.GetErrorMessage(), response.OriginalException);
            }

            if (useSnapshotPaging)
            {
                result         = response.ToFindResults();
                result.HasMore = response.Hits.Count >= options.GetLimit();

                // clear the scroll
                if (!result.HasMore)
                {
                    var scrollId = result.GetScrollId();
                    if (!String.IsNullOrEmpty(scrollId))
                    {
                        await _client.ClearScrollAsync(s => s.ScrollId(result.GetScrollId()));
                    }
                }

                ((IGetNextPage <TResult>)result).GetNextPageFunc = GetNextPageFunc;
            }
            else
            {
                int limit = options.GetLimit();
                result         = response.ToFindResults(limit);
                result.HasMore = response.Hits.Count > limit || response.Hits.Count >= options.GetMaxLimit();
                ((IGetNextPage <TResult>)result).GetNextPageFunc = GetNextPageFunc;
            }

            if (options.HasSearchAfter())
            {
                result.SetSearchBeforeToken();
                if (result.HasMore)
                {
                    result.SetSearchAfterToken();
                }
            }
            else if (options.HasSearchBefore())
            {
                // reverse results
                bool hasMore = result.HasMore;
                result         = new FindResults <TResult>(result.Hits.Reverse(), result.Total, result.Aggregations.ToDictionary(k => k.Key, v => v.Value), GetNextPageFunc, result.Data.ToDictionary(k => k.Key, v => v.Value));
                result.HasMore = hasMore;

                result.SetSearchAfterToken();
                if (result.HasMore)
                {
                    result.SetSearchBeforeToken();
                }
            }
            else if (result.HasMore)
            {
                result.SetSearchAfterToken();
            }

            result.Page = options.GetPage();

            if (!allowCaching)
            {
                return(result);
            }

            var nextPageFunc = ((IGetNextPage <TResult>)result).GetNextPageFunc;

            ((IGetNextPage <TResult>)result).GetNextPageFunc = null;
            await SetCachedQueryResultAsync(options, result, cacheSuffix : cacheSuffix).AnyContext();

            ((IGetNextPage <TResult>)result).GetNextPageFunc = nextPageFunc;

            return(result);
        }