public virtual async Task <bool> ExistsAsync(IRepositoryQuery query, ICommandOptions options = null)
        {
            options = ConfigureOptions(options.As <T>());
            await OnBeforeQueryAsync(query, options, typeof(T)).AnyContext();

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

            var searchDescriptor = (await CreateSearchDescriptorAsync(query, options).AnyContext()).Size(0);

            searchDescriptor.DocValueFields(_idField.Value);
            var response = await _client.SearchAsync <T>(searchDescriptor).AnyContext();

            if (response.IsValid)
            {
                _logger.LogRequest(response, options.GetQueryLogLevel());
            }
            else
            {
                if (response.ApiCall.HttpStatusCode.GetValueOrDefault() == 404)
                {
                    return(false);
                }

                _logger.LogErrorRequest(response, "Error checking if document exists");
                throw new ApplicationException(response.GetErrorMessage(), response.OriginalException);
            }

            return(response.HitsMetadata.Total.Value > 0);
        }
        public virtual async Task <bool> ExistsAsync(Id id, ICommandOptions options = null)
        {
            if (String.IsNullOrEmpty(id.Value))
            {
                return(false);
            }

            // documents that use soft deletes or have parents without a routing id need to use search for exists
            if (!SupportsSoftDeletes && (!HasParent || id.Routing != null))
            {
                var response = await _client.DocumentExistsAsync(new DocumentPath <T>(id.Value), d => {
                    d.Index(ElasticIndex.GetIndex(id));
                    if (id.Routing != null)
                    {
                        d.Routing(id.Routing);
                    }

                    return(d);
                }).AnyContext();

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

                return(response.Exists);
            }

            return(await ExistsAsync(q => q.Id(id), o => options.As <T>()).AnyContext());
        }
        public virtual async Task <T> GetByIdAsync(Id id, ICommandOptions options = null)
        {
            if (String.IsNullOrEmpty(id.Value))
            {
                return(null);
            }

            options = ConfigureOptions(options.As <T>());
            if (IsCacheEnabled && options.HasCacheKey())
            {
                throw new ArgumentException("Cache key can't be set when calling GetById");
            }

            if (IsCacheEnabled && options.ShouldReadCache())
            {
                var value = await GetCachedFindHit(id).AnyContext();

                if (value?.Document != null)
                {
                    _logger.LogTrace("Cache hit: type={EntityType} key={Id}", EntityTypeName, id);

                    return(ShouldReturnDocument(value.Document, options) ? value.Document : null);
                }
            }

            FindHit <T> findHit;

            if (!HasParent || id.Routing != null)
            {
                var request = new GetRequest(ElasticIndex.GetIndex(id), id.Value);
                if (id.Routing != null)
                {
                    request.Routing = id.Routing;
                }
                var response = await _client.GetAsync <T>(request).AnyContext();

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

                findHit = response.Found ? response.ToFindHit() : null;
            }
            else
            {
                // we don't have the parent id so we have to do a query
                // TODO: Ensure this find one query is not cached.
                findHit = await FindOneAsync(NewQuery().Id(id), options.Clone().DefaultCacheKey(id)).AnyContext();
            }

            if (IsCacheEnabled && options.ShouldUseCache())
            {
                await AddDocumentsToCacheAsync(findHit ?? new FindHit <T>(id, null, 0), options).AnyContext();
            }

            return(ShouldReturnDocument(findHit?.Document, options) ? findHit?.Document : null);
        }
        public virtual async Task <CountResult> CountAsync(IRepositoryQuery query, ICommandOptions options = null)
        {
            options = ConfigureOptions(options.As <T>());

            CountResult result;

            if (IsCacheEnabled && options.ShouldReadCache())
            {
                result = await GetCachedQueryResultAsync <CountResult>(options, "count").AnyContext();

                if (result != null)
                {
                    return(result);
                }
            }

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

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

            var searchDescriptor = await CreateSearchDescriptorAsync(query, options).AnyContext();

            searchDescriptor.Size(0);

            var response = await _client.SearchAsync <T>(searchDescriptor).AnyContext();

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

                _logger.LogErrorRequest(response, "Error getting document count");
                throw new ApplicationException(response.GetErrorMessage(), response.OriginalException);
            }

            result = new CountResult(response.Total, response.ToAggregations());
            if (IsCacheEnabled && options.ShouldUseCache())
            {
                await SetCachedQueryResultAsync(options, result, "count").AnyContext();
            }

            return(result);
        }
        public virtual async Task <FindHit <T> > FindOneAsync(IRepositoryQuery query, ICommandOptions options = null)
        {
            options = ConfigureOptions(options.As <T>());
            if (IsCacheEnabled && (options.ShouldUseCache() || options.ShouldReadCache()) && !options.HasCacheKey())
            {
                throw new ArgumentException("Cache key is required when enabling cache.", nameof(options));
            }

            var result = IsCacheEnabled && options.ShouldReadCache() && options.HasCacheKey() ? await GetCachedFindHit(options).AnyContext() : null;

            if (result != null)
            {
                return(result.FirstOrDefault());
            }

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

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

            var searchDescriptor = (await CreateSearchDescriptorAsync(query, options).AnyContext()).Size(1);
            var response         = await _client.SearchAsync <T>(searchDescriptor).AnyContext();

            if (response.IsValid)
            {
                _logger.LogRequest(response, options.GetQueryLogLevel());
            }
            else
            {
                if (response.ApiCall.HttpStatusCode.GetValueOrDefault() == 404)
                {
                    return(FindHit <T> .Empty);
                }

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

            result = response.Hits.Select(h => h.ToFindHit()).ToList();

            if (IsCacheEnabled && options.ShouldUseCache())
            {
                await AddDocumentsToCacheAsync(result, options).AnyContext();
            }

            return(result.FirstOrDefault());
        }
 public virtual Task <CountResult> CountBySearchAsync(ISystemFilter systemFilter, string filter = null, string aggregations = null, ICommandOptions options = null)
 {
     return(CountAsync(q => q.SystemFilter(systemFilter).FilterExpression(filter).AggregationsExpression(aggregations), o => options.As <T>()));
 }
 public virtual Task <FindResults <T> > SearchAsync(ISystemFilter systemFilter, string filter = null, string criteria = null, string sort = null, string aggregations = null, ICommandOptions options = null)
 {
     return(FindAsAsync <T>(q => q.SystemFilter(systemFilter).FilterExpression(filter).SearchExpression(criteria).SortExpression(sort).AggregationsExpression(aggregations), o => options.As <T>()));
 }
        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);
        }
        public virtual async Task <IReadOnlyCollection <T> > GetByIdsAsync(Ids ids, ICommandOptions options = null)
        {
            var idList = ids?.Distinct().Where(i => !String.IsNullOrEmpty(i)).ToList();

            if (idList == null || idList.Count == 0)
            {
                return(EmptyList);
            }

            if (!HasIdentity)
            {
                throw new NotSupportedException("Model type must implement IIdentity.");
            }

            options = ConfigureOptions(options.As <T>());
            if (IsCacheEnabled && options.HasCacheKey())
            {
                throw new ArgumentException("Cache key can't be set when calling GetByIds");
            }

            var hits = new List <FindHit <T> >();

            if (IsCacheEnabled && options.ShouldReadCache())
            {
                hits.AddRange(await GetCachedFindHit(idList).AnyContext());
            }

            var itemsToFind = idList.Except(hits.Select(i => (Id)i.Id)).ToList();

            if (itemsToFind.Count == 0)
            {
                return(hits.Where(h => h.Document != null && ShouldReturnDocument(h.Document, options)).Select(h => h.Document).ToList().AsReadOnly());
            }

            var multiGet = new MultiGetDescriptor();

            foreach (var id in itemsToFind.Where(i => i.Routing != null || !HasParent))
            {
                multiGet.Get <T>(f => {
                    f.Id(id.Value).Index(ElasticIndex.GetIndex(id));
                    if (id.Routing != null)
                    {
                        f.Routing(id.Routing);
                    }

                    return(f);
                });
            }

            var multiGetResults = await _client.MultiGetAsync(multiGet).AnyContext();

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

            foreach (var doc in multiGetResults.Hits)
            {
                hits.Add(((IMultiGetHit <T>)doc).ToFindHit());
                itemsToFind.Remove(new Id(doc.Id, doc.Routing));
            }

            // fallback to doing a find
            if (itemsToFind.Count > 0 && (HasParent || ElasticIndex.HasMultipleIndexes))
            {
                var response = await FindAsync(q => q.Id(itemsToFind.Select(id => id.Value)), o => o.PageLimit(1000)).AnyContext();

                do
                {
                    if (response.Hits.Count > 0)
                    {
                        hits.AddRange(response.Hits.Where(h => h.Document != null));
                    }
                } while (await response.NextPageAsync().AnyContext());
            }

            if (IsCacheEnabled && options.ShouldUseCache())
            {
                await AddDocumentsToCacheAsync(hits, options).AnyContext();
            }

            return(hits.Where(h => h.Document != null && ShouldReturnDocument(h.Document, options)).Select(h => h.Document).ToList().AsReadOnly());
        }