Библиотека предназначена для выполнения загрузки большого количества объектов из источника порциями. Размер порции ограничен таймаутом и/или фиксированным размером.
Создадим экземпляр:
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);
}
}
});