Я хочу отправить большой список объектов в другой веб-сервис для приема. Веб-сервис имеет ограничение в байтах 6 МБ. Я хочу отправить свой список из более чем 7000 объектов «пакетами» по 5 МБ.
Для этого мне нужно вычислить размер объектов в байтах и отправить пакет, когда общее количество байтов достигнет 5 МБ. Проблема в том, что каждый объект может иметь несколько дочерних объектов, и каждый дочерний объект также может иметь несколько дочерних объектов.
Пакет объектов сначала сериализуется в Json, а затем отправляется в теле запроса.
Есть ли способ вычислить размер каждого объекта в байтах во время выполнения, а затем добавить значение к промежуточной сумме?
Фрагмент кода ниже показывает, что мне нужно отправить список:
jsonList = JsonConvert.SerializeObject(list, 0);
request = new HttpRequestMessage(HttpMethod.Post, url);
request.Content = new StringContent(jsonList , Encoding.UTF8, "application/json");
request.Method = HttpMethod.Post;
request.Headers.Add("Authorization", "Bearer " + authToken);
response = await client.SendAsync(request);
jsonResponse = await response.Content.ReadAsStringAsync();
Похоже, вас не интересует какой-либо размер в памяти — только сериализованный размер. Способ узнать это — сериализовать его.
Предполагая, что все ваши родительские элементы имеют размер менее 5 МБ, я бы сериализовал один родительский элемент в пакет, когда еще есть место. Запустите новый пакет, когда длина родительского файла json превышает доступное пространство пакета.
Хотя int
имеет размер памяти 4 байта, сериализованный как JSON, он может выглядеть так: "field_name": 1234567890,\n
(27 байт в формате utf8) плюс фигурные скобки и т. д. Итак, это действительно сложно сказать. Проведите эксперименты, чтобы определить разумный размер партии.
Самый простой метод — просто угадать достаточно небольшое количество и использовать оператор Linq Chunk
.
Если это недостаточно точно, вы можете запустить цикл в функции-итераторе, получив MemoryStream
достаточно элементов, чтобы просто заполнить поток до нужного размера, а затем запустить другой поток.
public static IEnumerable<Stream>(IEnumerable<YourClass> source, long maxLength)
{
var serializer = JsonSerializer.Create(); // add settings?
using var enumer = source.GetEnumerator();
var (ms, writer) = GetNewStream();
while (enumer.MoveNext());
{
var currentPos = ms.Position;
serializer.Serialize(writer, enumer.Current);
if (ms.Position >= maxLength)
{
writer.Close();
ms.SetLength(currentPos); // truncate to old position
ms.Write((byte)']');
ms.Position = 0; // reset ready for eading
yield return ms;
(ms, writer) = GetNewStream();
serializer.Serialize(writer, enumer.Current);
}
}
if (ms.Length != 1) // was not empty list
{
writer.Close();
ms.Write((byte)']');
ms.Position = 0; // reset ready for eading
yield return ms;
}
}
private static (MemoryStream, StreamWriter) GetNewStream()
{
var ms = new MemoryStream();
ms.Write((byte)'[');
var writer = new StreamWriter(ms, leaveOpen: true)
return (ms, writer);
}
Хороший. Я тоже рассматривал MemoryStream как очевидное решение, но поленился его напечатать :-)
Небольшая придирка: это останавливается на 5000, в вопросе упоминалось 5 МБ. Также я думаю, что ты проглотил объект Current
между блоками. Вы отключаетесь после предыдущего объекта и не добавляете Current
в новый поток перед вызовом MoveNext()
Вы совершенно правы, исправили некоторые ошибки.
Это хорошая вещь, спасибо.
Начните StringBuilder
с "["
, сериализуйте объекты списка в цикле один за другим и продолжайте добавлять в конструктор (не забывайте запятые между объектами).
Незадолго до достижения предела закройте с помощью "]"
, преобразуйте в байты UTF8, запустите запрос, а затем перезагрузите построитель с помощью нового "["
.
Если ваш контент в основном находится в диапазоне ASCII, builder.Length
должен быть равен или лишь немного меньше длины контента в байтах.
Если у вас много специальных символов, UTF-8 должен представлять их несколькими байтами, чтобы байты вашего контента росли быстрее, чем длина строки. В этом случае вам необходимо преобразовать каждую строку json в UTF8 и измерить количество байтов, чтобы отслеживать общее количество байтов.
Оставьте себе немного свободного пространства в КБ до предела, чтобы учесть заголовки и прочее, и все будет в порядке.
Я собирался предложить то же самое, за исключением того, что вместо этого можно использовать StreamContent
, используя Encoding.UTF8
для записи объектов в экземпляр MemoryStream
, чтобы вы имели точное знание длины. Конечно, вы также можете реализовать свой собственный HttpContent
, который не будет копировать больше лимита.
Сильно ли различаются размеры объектов?