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