public virtual async Task <FindResults <TResult> > FindAsAsync <TResult>(IRepositoryQuery query, ICommandOptions options = null) where TResult : class, new()
        {
            if (query == null)
            {
                query = new RepositoryQuery();
            }

            bool useSnapshotPaging = options.ShouldUseSnapshotPaging();
            // don't use caching with snapshot paging.
            bool allowCaching = IsCacheEnabled && useSnapshotPaging == false;

            options = ConfigureOptions(options);
            await OnBeforeQueryAsync(query, options, typeof(TResult)).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();

                    if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Trace))
                    {
                        _logger.LogTrace(scrollResponse.GetRequest());
                    }

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

                if (options.ShouldUseSearchAfterPaging())
                {
                    var lastDocument = previousResults.Documents.LastOrDefault();
                    if (lastDocument != null)
                    {
                        var searchAfterValues = new List <object>();
                        var sorts             = query.GetSorts();
                        if (sorts.Count > 0)
                        {
                            foreach (var sort in query.GetSorts())
                            {
                                if (sort.SortKey.Property?.DeclaringType == lastDocument.GetType())
                                {
                                    searchAfterValues.Add(sort.SortKey.Property.GetValue(lastDocument));
                                }
                                else if (typeof(TResult) == typeof(T) && sort.SortKey.Expression is Expression <Func <T, object> > valueGetterExpression)
                                {
                                    var valueGetter       = valueGetterExpression.Compile();
                                    var typedLastDocument = lastDocument as T;
                                    if (typedLastDocument != null)
                                    {
                                        var value = valueGetter.Invoke(typedLastDocument);
                                        searchAfterValues.Add(value);
                                    }
                                }
                                else if (sort.SortKey.Name != null)
                                {
                                    var propertyInfo = lastDocument.GetType().GetProperty(sort.SortKey.Name);
                                    if (propertyInfo != null)
                                    {
                                        searchAfterValues.Add(propertyInfo.GetValue(lastDocument));
                                    }
                                }
                                else
                                {
                                    // TODO: going to to need to take the Expression and pull the string name from it
                                }
                            }
                        }
                        else if (lastDocument is IIdentity lastDocumentId)
                        {
                            searchAfterValues.Add(lastDocumentId.Id);
                        }

                        if (searchAfterValues.Count > 0)
                        {
                            options.SearchAfter(searchAfterValues.ToArray());
                        }
                        else
                        {
                            throw new ArgumentException("Unable to automatically calculate values for SearchAfterPaging.");
                        }
                    }
                }

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

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

            if (_logger.IsEnabled(Microsoft.Extensions.Logging.LogLevel.Trace))
            {
                _logger.LogTrace(response.GetRequest());
            }
            if (!response.IsValid)
            {
                if (response.ApiCall.HttpStatusCode.GetValueOrDefault() == 404)
                {
                    return(new FindResults <TResult>());
                }

                string message = response.GetErrorMessage();
                _logger.LogError(response.OriginalException, message);
                throw new ApplicationException(message, response.OriginalException);
            }

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

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