public 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.");
            }

            var hits = new List <T>();

            if (IsCacheEnabled && options.ShouldReadCache())
            {
                var cacheHits = await Cache.GetAllAsync <T>(idList.Select(id => id.Value)).AnyContext();

                hits.AddRange(cacheHits.Where(kvp => kvp.Value.HasValue).Select(kvp => kvp.Value.Value));
            }

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

            if (itemsToFind.Count == 0)
            {
                return(hits.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(GetIndexById(id)).Type(ElasticType.Name);
                    if (id.Routing != null)
                    {
                        f.Routing(id.Routing);
                    }

                    return(f);
                });
            }

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

            _logger.Trace(() => multiGetResults.GetRequest());

            foreach (var doc in multiGetResults.Documents)
            {
                if (!doc.Found)
                {
                    continue;
                }

                hits.Add(((IMultiGetHit <T>)doc).ToFindHit().Document);
                itemsToFind.Remove(doc.Id);
            }

            // fallback to doing a find
            if (itemsToFind.Count > 0 && (HasParent || 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).Select(h => h.Document));
                    }
                } while (await response.NextPageAsync().AnyContext());
            }

            if (IsCacheEnabled && options.ShouldUseCache())
            {
                foreach (var item in hits.OfType <IIdentity>())
                {
                    await Cache.SetAsync(item.Id, item, options.GetExpiresIn()).AnyContext();
                }
            }

            return(hits.AsReadOnly());
        }
        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());
        }