protected async Task <TResult> GetCachedQueryResultAsync <TResult>(ICommandOptions options, string cachePrefix = null, string cacheSuffix = null)
        {
            if (IsCacheEnabled && (options.ShouldUseCache() || options.ShouldReadCache()) && !options.HasCacheKey())
            {
                throw new ArgumentException("Cache key is required when enabling cache.", nameof(options));
            }

            if (!IsCacheEnabled || !options.ShouldReadCache() || !options.HasCacheKey())
            {
                return(default);
        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 <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());
        }
        protected async Task <TResult> GetCachedQueryResultAsync <TResult>(ICommandOptions options, string cachePrefix = null, string cacheSuffix = null)
        {
            if (!IsCacheEnabled || options == null || !options.ShouldReadCache() || !options.HasCacheKey())
            {
                return(default(TResult));
            }

            string cacheKey = cachePrefix != null ? cachePrefix + ":" + options.GetCacheKey() : options.GetCacheKey();

            if (!String.IsNullOrEmpty(cacheSuffix))
            {
                cacheKey += ":" + cacheSuffix;
            }

            var result = await Cache.GetAsync <TResult>(cacheKey, default(TResult)).AnyContext();

            _logger.Trace(() => $"Cache {(result != null ? "hit" : "miss")}: type={ElasticType.Name} key={cacheKey}");

            return(result);
        }
        protected async Task SetCachedQueryResultAsync <TResult>(ICommandOptions options, TResult result, string cachePrefix = null, string cacheSuffix = null)
        {
            if (!IsCacheEnabled || result == null || options == null || !options.ShouldUseCache() || !options.HasCacheKey())
            {
                return;
            }

            string cacheKey = cachePrefix != null ? cachePrefix + ":" + options.GetCacheKey() : options.GetCacheKey();

            if (!String.IsNullOrEmpty(cacheSuffix))
            {
                cacheKey += ":" + cacheSuffix;
            }

            await Cache.SetAsync(cacheKey, result, options.GetExpiresIn()).AnyContext();

            _logger.Trace(() => $"Set cache: type={ElasticType.Name} key={cacheKey}");
        }
 protected async Task <TResult> GetCachedQueryResultAsync <TResult>(ICommandOptions options, string cachePrefix = null, string cacheSuffix = null)
 {
     if (!IsCacheEnabled || options == null || !options.ShouldReadCache() || !options.HasCacheKey())
     {
         return(default);
        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());
        }