Распакуйте содержимое SQL Blob и верните ответ в PushStreamContent .NET Core.

Я разрабатываю новый API в службе .NET Core, новый API должен читать BLOB из таблицы SQL, распаковывать его с помощью DeflateStream. А затем вернуть его (потоковое) клиенту.

Чтобы не потреблять много памяти. Я возвращаю ответ типа и PushStreamContent чтобы я мог напрямую скопировать поток sql в поток ответов, не загружая большой двоичный объект в память. Так что я закончил с чем-то вроде этого.

return this.ResponseMessage(new HttpResponseMessage
        {
            Content = new PushStreamContent(async (outStream, httpContent, transportContext) =>
            {
                using (SqlConnection connection = new SqlConnection(connectionString))
                {
                    await connection.OpenAsync();
                    using (SqlCommand command = new SqlCommand(query, connection))
                    {

                        // The reader needs to be executed with the SequentialAccess behavior to enable network streaming
                        // Otherwise ReadAsync will buffer the entire BLOB into memory which can cause scalability issues or even OutOfMemoryExceptions
                        using (SqlDataReader reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess))
                        {
                            if (await reader.ReadAsync() && !(await reader.IsDBNullAsync(0)))
                            {
                                using (Stream streamToDecompress = reader.GetStream(0))
                                using (Stream decompressionStream = new DeflateStream(streamToDecompress, CompressionMode.Decompress))
                                {
                                    // This copyToAsync will take for ever
                                    await decompressionStream.CopyToAsync(outStream);
                                    outStream.close();

                                    return;
                                }
                            }

                            throw new Exception("Couldn't retrieve blob");
                        }
                    }
                }
            },
            "application/octet-stream")
        });

Проблема здесь в том, что шаг, который копирует deflateStream в поток вывода ответа, занимает вечность, как указано в коде. Хотя я попробовал тот же самый метод, но с записью потока в файл вместо его копирования в поток resp, и это сработало как шарм.

Так вы, ребята, можете помочь мне с этим ?? Я ошибаюсь в использовании PushStreamContent? Должен ли я использовать другой подход? Дело в том, что я не хочу загружать весь блоб в память, я хочу прочитать его и распаковать на лету. SqlClient поддерживает потоковую передачу больших двоичных объектов и я хочу этим воспользоваться.


person user3299116    schedule 19.07.2020    source источник
comment
Под этим copyToAsync будет длиться вечно, вы имеете в виду, что он зависает на неопределенный срок? Вы уверены, что клиент читает до конца потока ответов?   -  person David Browne - Microsoft    schedule 20.07.2020
comment
@DavidBrowne-Microsoft, спасибо за ответ. Да, зависает на неопределенный срок. Я знаю, что могу читать из потока ответов (когда-то исходившего от SqlReader). Если я закомментирую поток deflate и скопирую поток ответа без распаковки в outStream, он сработает. Но похоже, что поток deflate не читает ответ.   -  person user3299116    schedule 20.07.2020


Ответы (1)


Это тупик в PushStreamContent, который я не претендую на понимание. Но я воспроизвел это и изменил

await decompressionStream.CopyToAsync(outStream);

to

decompressionStream.CopyTo(outStream);

решает это.

Вот полный репро:

public ResponseMessageResult Get()
{
    var data =  new string[] { "value1", "value2" };

    var jsonData = Newtonsoft.Json.JsonConvert.SerializeObject(data);

    var msSource = new MemoryStream(Encoding.UTF8.GetBytes(jsonData));
    var msDest = new MemoryStream();
    var compressionStream = new DeflateStream(msDest, CompressionMode.Compress);
    msSource.CopyTo(compressionStream);
    compressionStream.Close();

    var compressedBytes = msDest.ToArray();

    var query = "select @bytes buf";
    var connectionString = "server=localhost;database=tempdb;integrated security=true";

    
    return this.ResponseMessage(new HttpResponseMessage
    {
        Content = new PushStreamContent(async (outStream, httpContent, transportContext) =>
        {
            using (SqlConnection connection = new SqlConnection(connectionString))
            {
                await connection.OpenAsync();
                using (SqlCommand command = new SqlCommand(query, connection))
                {
                    command.Parameters.Add("@bytes", SqlDbType.VarBinary, -1).Value = compressedBytes;

                    // The reader needs to be executed with the SequentialAccess behavior to enable network streaming
                    // Otherwise ReadAsync will buffer the entire BLOB into memory which can cause scalability issues or even OutOfMemoryExceptions
                    using (SqlDataReader reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess))
                    {
                        if (await reader.ReadAsync() && !(await reader.IsDBNullAsync(0)))
                        {
                            using (Stream streamToDecompress = reader.GetStream(0))
                            {
                                //var buf = new MemoryStream();
                                //streamToDecompress.CopyTo(buf);
                                //buf.Position = 0;

                                using (Stream decompressionStream = new DeflateStream(streamToDecompress, CompressionMode.Decompress))
                                {
                                    
                                    // This copyToAsync will take for ever
                                    //await decompressionStream.CopyToAsync(outStream);
                                    decompressionStream.CopyTo(outStream);
                                    outStream.Close();

                                    return;
                                }
                            }
                        }

                        throw new Exception("Couldn't retrieve blob");
                    }
                }
            }
        },
    "application/octet-stream")
    });
}
person David Browne - Microsoft    schedule 20.07.2020