PartialLoader

in English

Библиотека предназначена для выполнения загрузки большого количества объектов из источника порциями. Размер порции ограничен таймаутом и/или фиксированным размером.

Порядок использования

Создадим экземпляр:

            PartialLoader<Cat> partialLoader = new();
        

Установим источник данных (обязательно):

            public async IAsyncEnumerable<Cat> GenerateManyCats()
            {
                ...
            }

            partialLoader.SetDataProvider(GenerateManyCats());
        

Установим величину таймаута (по умолчанию - без таймаута):

            partialLoader.SetTimeout(TimeSpan.FromMilliseconds(200));
        

Установим размер порции (по умолчанию - не ограничено):

            partialLoader.SetPaging(5000);
        

Перед каждым запросом следующей порции данных установим один или несколько "утилизаторов" - Action<Cat>, которые будут как-то обрабатывать каждый полученный объект:

            partialLoader
                .AddUtilizer(item => ...)
                .AddUtilizer(item => ...)
                ;
        

Необходимость каждый раз вновь устанавливать "утилизаторы" вызвана тем, что обычно данный класс должен использоваться на сервере ASP.NET. Сам экземпляр надо сохранять между запросами, но контекст сохраняться не будет, а "утилизаторы" скорее всего будут зависеть от контекста.

Запросим очередную порцию:

            await partialLoader.LoadAsync();
        

Проверим, все ли данные получены:

            if(partialLoader.State is PartialLoaderState.Partial)
            {
                ... // Не все    
            }
            else 
            {
                ... // Все    
            }
        

Если не все, опять установит "утилизаторы" и запросим следующую порцию.

Потомки PartialLoader<T> со специальным поведением и вспомогательные классы

ChunkPartialLoader<T> - сохраняет очередную порцию в списке, доступном через свойство Chunk.

            do {
                await partialLoader.LoadAsync();
                List<Cat> moreCats = partialLoader.Chunk;
            }
            while(partialLoader.State is PartialLoaderState.Partial);
        

ResultPartialLoader<T> - добавляет очередную порцию в список, доступный через свойство Result.

            do {
                await partialLoader.LoadAsync();
            }
            while(partialLoader.State is PartialLoaderState.Partial);
            List<Cat> allCats = partialLoader.Result;
        

ChunkResultPartialLoader<T> - комбинация предыдущих.

            do {
                await partialLoader.LoadAsync();
                List<Cat> moreCats = partialLoader.Chunk;
            }
            while(partialLoader.State is PartialLoaderState.Partial);
            List<Cat> allCats = partialLoader.Result;
        

Пример:

Обработка запроса огромного количества котов несколькими порциями.

            app.MapGet("/manyManyCatsByPortions",  async (HttpContext context) =>
            {
                PartialLoader>Cat< partialLoader;
                string key = null!;

                // Получаем хранилище через механизм внедрения зависимостей.
                CatsLoaderStorage loaderStorage = context.RequestServices.GetRequiredService<CatsLoaderStorage>();

                if (!context.Request.Headers.ContainsKey(Constants.PartialLoaderSessionKey))
                {
                    // Если это первый запрос, то создаём PartialLoader и стартуем генерацию.
                    partialLoader = context.RequestServices.GetRequiredService<PartialLoader<Cat>>()
                        .SetTimeout(TimeSpan.FromMilliseconds(timeout))
                        .SetPaging(paging)
                        .SetDataProvider(GenerateManyCats(count, delay))
                        .SetIsNullEnding(true)
                    ;
                    key = Guid.NewGuid().ToString();
                    loaderStorage.Data[key] = partialLoader;
                }
                else
                {
                    // Если это последующий запрос, то берём PartialLoader из хранилища и продолжаем генерацию.
                    key = context.Request.Headers[Constants.PartialLoaderSessionKey];
                    partialLoader = loaderStorage.Data[key];
                }

                JsonSerializerOptions jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = false };
                jsonOptions.Converters.Add(new TransferJsonConverterFactory(context.RequestServices)
                    .AddTransient>ICat<());

                // Добавляем заголовок ответа с идентификатором серии запросов.
                context.Response.Headers.Add(Constants.PartialLoaderSessionKey, key);

                // Получаем порцию данных, одновременно записывая их в поток
                await context.Response.WriteAsJsonAsync(partialLoader, jsonOptions).ConfigureAwait(false);

                if (partialLoader.State is PartialLoaderState.Full)
                {
                    if (key is { })
                    {
                        loaderStorage.Data.Remove(key);
                    }
                }

            });