public void DictionaryContainsAllKeysInInput_ReturnsEmpty(int inputKeysCount)
        {
            var inputKeys = Enumerable.Range(0, inputKeysCount).ToArray();

            var dictionary = inputKeys.ToDictionary(k => k);

            var missingKeys = MissingKeysResolver <int, int> .GetMissingKeys(inputKeys, dictionary, out var pooledArray);

            missingKeys.IsEmpty.Should().BeTrue();
            pooledArray.Should().BeNull();
        }
        public void EmptyDictionary_ReturnsInput()
        {
            var inputKeys = Enumerable.Range(0, 10).ToArray();

            var missingKeys = MissingKeysResolver <int, int> .GetMissingKeys(
                inputKeys,
                new Dictionary <int, int>(),
                out var pooledArray);

            missingKeys.ToArray().Should().BeEquivalentTo(inputKeys);
            pooledArray.Should().BeNull();
        }
        public void DictionaryIsSubsetOfInputKeys_CorrectlyReturnsOnlyThoseMissing(int inputKeysCount, int keysFoundCount)
        {
            var inputKeys = Enumerable.Range(0, inputKeysCount).ToArray();

            var dictionary = inputKeys
                             .OrderBy(_ => Guid.NewGuid())
                             .Take(keysFoundCount)
                             .ToDictionary(k => k);

            var missingKeys = MissingKeysResolver <int, int> .GetMissingKeys(
                inputKeys,
                dictionary,
                out var pooledArray);

            missingKeys.ToArray().Should().BeEquivalentTo(inputKeys.Except(dictionary.Keys));
            pooledArray.Should().NotBeNull();
        }
        public void DictionaryContainsKeysNotInInput_CorrectlyReturnsOnlyThoseMissing(
            int inputKeysCount, int keysFoundCount, int keysNotInInputCount)
        {
            var inputKeys = Enumerable.Range(0, inputKeysCount).ToArray();

            var dictionary = inputKeys
                             .OrderBy(_ => Guid.NewGuid())
                             .Take(keysFoundCount - keysNotInInputCount)
                             .Concat(Enumerable.Range(1, keysNotInInputCount).Select(k => - k))
                             .ToDictionary(k => k);

            var missingKeys = MissingKeysResolver <int, int> .GetMissingKeys(
                inputKeys,
                dictionary,
                out _);

            missingKeys.ToArray().Should().BeEquivalentTo(inputKeys.Except(dictionary.Keys));
        }
        public async ValueTask <Dictionary <TKey, TValue> > GetMany(
            TParams parameters,
            IEnumerable <TKey> keys,
            CancellationToken cancellationToken)
        {
            var(start, timer) = GetDateAndTimer();
            var keysMemory = keys.ToReadOnlyMemory(out var pooledKeysArray);

            try
            {
                cancellationToken.ThrowIfCancellationRequested();

                var getFromCacheTask = GetFromCache(parameters, keysMemory);

                var(resultsDictionary, cacheStats) = getFromCacheTask.IsCompleted
                    ? getFromCacheTask.Result
                    : await getFromCacheTask.ConfigureAwait(false);

                if (cacheStats.CacheHits == keysMemory.Length)
                {
                    return(FinalizeResults(resultsDictionary, cacheStats));
                }

                var missingKeys = MissingKeysResolver <TKey, TValue> .GetMissingKeys(
                    keysMemory,
                    resultsDictionary,
                    out var pooledMissingKeysArray);

                try
                {
                    var maxBatchSize = _maxBatchSizeFactory?.Invoke(parameters, missingKeys) ?? _maxBatchSize;
                    if (missingKeys.Length < maxBatchSize)
                    {
                        var resultsDictionaryTask = GetValuesFromFunc(
                            parameters,
                            missingKeys,
                            cancellationToken,
                            resultsDictionary);

                        resultsDictionary = resultsDictionaryTask.IsCompleted
                            ? resultsDictionaryTask.Result
                            : await resultsDictionaryTask.ConfigureAwait(false);
                    }
                    else
                    {
                        var batchSizes = BatchingHelper.GetBatchSizes(missingKeys.Length, maxBatchSize, _batchBehaviour);

                        resultsDictionary ??= new Dictionary <TKey, TValue>(_keyComparer);

                        Task[] tasks = null;
                        var    resultsDictionaryLock = new object();
                        var    tasksIndex            = 0;
                        var    totalKeysBatchedCount = 0;
                        for (var batchIndex = 0; batchIndex < batchSizes.Length; batchIndex++)
                        {
                            var keysStartIndex = totalKeysBatchedCount;
                            var batchSize      = batchSizes[batchIndex];

                            totalKeysBatchedCount += batchSize;

                            var getValuesFromFuncTask = GetValuesFromFunc(
                                parameters,
                                keysMemory.Slice(keysStartIndex, batchSize),
                                cancellationToken,
                                resultsDictionary,
                                resultsDictionaryLock);

                            if (getValuesFromFuncTask.IsCompletedSuccessfully)
                            {
                                continue;
                            }

                            if (tasks is null)
                            {
                                tasks = new Task[batchSizes.Length - batchIndex];
                            }

                            tasks[tasksIndex++] = getValuesFromFuncTask.AsTask();
                        }

                        if (!(tasks is null))
                        {
                            await Task.WhenAll(new ArraySegment <Task>(tasks, 0, tasksIndex)).ConfigureAwait(false);
                        }
                    }
                }
                finally
                {
                    if (!(pooledMissingKeysArray is null))
                    {
                        ArrayPool <TKey> .Shared.Return(pooledMissingKeysArray);
                    }
                }

                return(FinalizeResults(resultsDictionary, cacheStats));
            }
            catch (Exception ex) when(!(_onExceptionAction is null))
            {
                _onExceptionAction?.Invoke(new ExceptionEvent <TParams, TKey>(
                                               parameters,
                                               keysMemory,
                                               start,
                                               timer.Elapsed,
                                               ex));

                throw;
            }
            finally
            {
                if (!(pooledKeysArray is null))
                {
                    ArrayPool <TKey> .Shared.Return(pooledKeysArray);
                }
            }

            Dictionary <TKey, TValue> FinalizeResults(
                Dictionary <TKey, TValue> resultsDictionary,
                CacheGetManyStats cacheStats)
            {
                resultsDictionary = FilterResults(resultsDictionary, out var countExcluded);

                _onSuccessAction?.Invoke(new SuccessfulRequestEvent <TParams, TKey, TValue>(
                                             parameters,
                                             keysMemory,
                                             resultsDictionary,
                                             start,
                                             timer.Elapsed,
                                             cacheStats,
                                             countExcluded));

                return(resultsDictionary);
            }
        }