Чтение и копирование больших файлов/BLOB-объектов без сохранения их в потоке памяти в С#

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

public async Task<Stream> ReadStream(string containerName, string digestFileName, string fileName, string connectionString)
        {
            string data = string.Empty;
            string fileExtension = Path.GetExtension(fileName);
            var contents = await DownloadBlob(containerName, digestFileName, connectionString);
                           
            return contents;
        }

    public async Task<Stream> DownloadBlob(string containerName, string fileName, string connectionString)
    {        

       Microsoft.Azure.Storage.CloudStorageAccount storageAccount = Microsoft.Azure.Storage.CloudStorageAccount.Parse(connectionString);
        CloudBlobClient serviceClient = storageAccount.CreateCloudBlobClient();
        CloudBlobContainer container = serviceClient.GetContainerReference(containerName);
        CloudBlockBlob blob = container.GetBlockBlobReference(fileName);
        if (!blob.Exists())
        {
            throw new Exception($"Unable to upload data in table store for document");
        }
       
        return await blob.OpenReadAsync();  
}

     private IEnumerable<Dictionary<string, EntityProperty>> ReadCSV(Stream source, IEnumerable<TableField> cols)
    {
        
            using (TextReader reader = new StreamReader(source, Encoding.UTF8))
            {
            
                var cache = new TypeConverterCache();
                cache.AddConverter<float>(new CSVSingleConverter());
                cache.AddConverter<double>(new CSVDoubleConverter());
                var csv = new CsvReader(reader,
                    new CsvHelper.Configuration.CsvConfiguration(global::System.Globalization.CultureInfo.InvariantCulture)
                    {
                        Delimiter = ";",
                        HasHeaderRecord = true,
                        CultureInfo = global::System.Globalization.CultureInfo.InvariantCulture,
                        TypeConverterCache = cache
                    });
                csv.Read();
                csv.ReadHeader();


                var map = (
                        from col in cols
                        from src in col.Sources()
                        let index = csv.GetFieldIndex(src, isTryGet: true)
                        where index != -1
                        select new { col.Name, Index = index, Type = col.DataType }).ToList();

                while (csv.Read())
                {
                    yield return map.ToDictionary(
                        col => col.Name,
                        col => EntityProperty.CreateEntityPropertyFromObject(csv.GetField(col.Type, col.Index)));
                }
            
            }
        
    }

Где тот бит, что "скопировать содержимое в табличное хранилище"? Кроме того, подумайте о потоках — они похожи на каналы, у вас есть что-то, что вы хотите прочитать, и что-то, на что вы хотите записать. Прямо сейчас вы используете методы, в которых вы даете блок-блобу поток памяти, и он записывает в него, затем вы (вероятно) сбрасываете его и передаете его табличному блобу и заставляете его читать из него.. но почему бы просто не запросить у блок-блоба его поток и передать этот поток в табличный BLOB-объект и заставить его читать из него? Это почти вся идея потока; это просто поток данных. Ты все еще думаешь обо всем этом...

Caius Jard 24.12.2020 09:34

... с точки зрения «я должен прочитать все данные, я должен их куда-то поместить, я должен их записать ...» - вместо этого подумайте о том, как передавать данные, предоставляя «то, что хочет читать» прямое соединение с «вещью, которая хочет предоставить данные для чтения» (или, в равной степени, предоставление «вещи, на которую нужно записать» прямое соединение с «вещью, которая хочет писать», не стоя посередине и не выполняя чтение/запись во временное хранилище (память или диск)

Caius Jard 24.12.2020 09:41

это то, что я ищу, и чтение и запись временно были моей идеей ... но вы понимаете, о чем я здесь говорю, у вас есть какой-нибудь пример для справки. Я отредактировал код, в котором я читаю данные, переданные из ReadStream().

Ankit Kumar 24.12.2020 09:46

Теперь больше запутался. Думал, вы говорите о хранилище лазурных таблиц. Кажется, вы говорите о загрузке блок-блоба и преобразовании его в CSV, но вы опубликовали два метода чтения; Я бы ожидал чтения и записи. Концепция остается прежней; если у вас есть «что-то, что предоставляет поток, из которого вы можете читать» («что-то, что вы можете снабдить потоком, в который будет производиться запись»), и у вас есть «что-то, что предоставляет поток, в который вы можете писать» («что-то, что вы можете может предоставить поток, из которого он будет читать"), тогда вы можете соединить их и заставить их выполнять чтение/запись напрямую без временного хранилища.

Caius Jard 24.12.2020 09:49

Хорошо, здесь я читаю большие двоичные объекты из хранилища больших двоичных объектов Azure, большие двоичные объекты в основном представляют собой CSV-файлы, и я копирую содержимое в табличное хранилище Azure. Я не предоставил метод, при котором он вставляет данные в табличное хранилище, но это в основном берет все данные ReadCsv и вставляет их в таблицу пакетно. Я не нахожу ничего, что поставляет поток для лазурных BLOB-объектов. любая идея, если это поддерживается для больших двоичных объектов

Ankit Kumar 24.12.2020 09:59
CloudBlockBlock.OpenRead возможно? Он «предоставляет поток для чтения из большого двоичного объекта», а CsvReader читает из TextReader, который является абстрактным родителем, например, StreamReader, который читает из потока (например, предоставленного большим двоичным объектом), поэтому вы получаете CsvReader для чтения из StreamReader, который читает из потока больших двоичных объектов.
Caius Jard 24.12.2020 10:08

не могли бы вы изменить мой код на то, что вы упомянули ниже, и опубликовать его как ответ. Я не могу заставить его работать с openRead

Ankit Kumar 24.12.2020 10:51
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
3 111
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я предполагаю, что это может выглядеть примерно так (измените свой ReadCSV, чтобы он принимал поток, а не строки):

private IEnumerable<Dictionary<string, EntityProperty>> ReadCSV(Stream source, IEnumerable<TableField> cols)
{
    using (TextReader reader = new StreamReader(source))

И это (измените свой DownloadBlob, чтобы он вместо этого возвращал поток):

public async Task<Stream> GetBlobStream(string containerName, string fileName, string connectionString)
    {
        
        Microsoft.Azure.Storage.CloudStorageAccount storageAccount = Microsoft.Azure.Storage.CloudStorageAccount.Parse(connectionString);
        CloudBlobClient serviceClient = storageAccount.CreateCloudBlobClient();
        CloudBlobContainer container = serviceClient.GetContainerReference(containerName);
        CloudBlockBlob blob = container.GetBlockBlobReference(fileName);
        if (!blob.Exists())
        {
            throw ...
        }
            
        return await blob.OpenReadAsync();

    }

А затем соедините их вместе:

var stream = GetBlobStream(...)

ReadCSV(stream, ...)

Я думал, что целью здесь было избежать чтения массивного файла в память. Как вы думаете, что data = streamReader.ReadToEnd() собирается делать?

Caius Jard 24.12.2020 14:31

Как вы думаете, где живет содержимое переменной data?

Caius Jard 24.12.2020 14:40

:D да.. так что идея такова: мы получаем поток в блоб, мы отдаем поток в csv-ридер, мы немного читаем из ридера, который извлекает бит из потока (надеюсь, он будет делать это, а не сделать ReadToEnd внутренне), и по мере того, как мы считываем чтение по крупицам из считывателя, мы помещаем данные в лазурное хранилище таблиц... и мы никогда не используем много памяти.

Caius Jard 24.12.2020 14:42

тогда как насчет кодировки utf8? Требуется ли это, если я передам поток в ReadCSV(), мне также нужно будет решить это

Ankit Kumar 24.12.2020 14:43

Вы можете указать кодировку при создании StreamReader, но в любом случае по умолчанию используется UTF8.

Caius Jard 24.12.2020 14:46

Обратите внимание, что UTF8 в любом случае используется по умолчанию, поэтому вам не нужно делать для него ничего особенного.

Caius Jard 24.12.2020 14:48

привет ... попробовал, но получил ошибку в строке csv.ReadHeader () как «Запись заголовка не найдена». в CsvHelper.CsvReader.ParseNamedIndexes() в CsvHelper.CsvReader.ReadHeader(). заголовки есть и я могу читать при чтении как MemoryStream.

Ankit Kumar 24.12.2020 19:10

Тогда вам нужно будет выполнить некоторую отладку и посмотреть, что он делает...

Caius Jard 24.12.2020 19:17

изменил вопрос с последним кодом, я предполагаю, что синтаксический анализ не происходит, как должно быть

Ankit Kumar 24.12.2020 19:22

Загрузите код CsvHelper и добавьте его как проект, сошлитесь на него. Таким образом, вы можете выполнить отладку в коде и посмотреть, как он себя ведет.

Caius Jard 24.12.2020 19:29

кажется, помощник csv не читает поток, возвращаемый из openreadasync(). любой альтернативный подход с файловым потоком?

Ankit Kumar 26.12.2020 18:28

Это не имеет смысла; он будет читать из ридера, который читает из потока памяти, но не читает из ридера, который читает из сетевого потока? CsvHelper не знает о потоке, он знает только о читателе

Caius Jard 26.12.2020 18:49

это странно, но это то, что происходит. я разместил это как отдельный вопрос, и люди предполагают, что это ограничение csvhelper здесь, что он не читает сетевой поток..stackoverflow.com/questions/65442508/…

Ankit Kumar 26.12.2020 20:26

Вы прочитали это из блоба. не могли бы вы вставить свой код, как вы пытались

Ankit Kumar 26.12.2020 22:18

Да, я прочитал это с блоба. Я просто пишу ответ с точным кодом

Caius Jard 26.12.2020 22:20
Ответ принят как подходящий

По вашему настоянию, что CsvHelper не может читать из потока, подключенного к большому двоичному объекту, я кое-что собрал:

  • Основное приложение WinForms (3.1)
  • CsvHelper последние (19)
  • Azure.Storage.Blobs (12.8)

CSV с моего диска:

В моем хранилище больших двоичных объектов:

В моем отладчике есть запись CAf255 OK по Read/GetRecord:

Или по EnumerateRecords:

Используя этот код:

    private async void button1_Click(object sender, EventArgs e)
    {
        var cstr = "MY CONNECTION STRING HERE";

        var bbc = new BlockBlobClient(cstr, "temp", "call.csv");

        var s = await bbc.OpenReadAsync(new BlobOpenReadOptions(true) { BufferSize = 16384 });

        var sr = new StreamReader(s);

        var csv = new CsvHelper.CsvReader(sr, new CsvConfiguration(CultureInfo.CurrentCulture) { HasHeaderRecord = true });

        var x = new X();

        //try by read/getrecord (breakpoint and skip over it if you want to try the other way)
        while(await csv.ReadAsync())
        {
            var rec = csv.GetRecord<X>();
            Console.WriteLine(rec.Sid);
        }

        //try by await foreach
        await foreach (var r in csv.EnumerateRecordsAsync(x))
        {
            Console.WriteLine(r.Sid);
        }
    }

О, и класс, который представляет запись CSV в моем приложении (я смоделировал только одно свойство, Sid, чтобы доказать концепцию):

class X {
    public string Sid{ get; set; }
}

Может быть, немного вернемся назад, начнем с простого. Одна строковая поддержка в вашем CSV, без уступок и т. д., Просто получите чтение файла в порядке. Я также не беспокоился обо всех заголовочных ошибках - кажется, просто работает нормально, говоря «файл имеет заголовки» в параметрах - вы можете видеть, что у моего отладчика есть экземпляр X с правильно заполненным свойством Sid, показывающим первое значение. Я выполнил еще несколько циклов, и они тоже заполнились нормально.

спасибо, я пробую все упомянутое здесь. большое спасибо

Ankit Kumar 26.12.2020 22:44

Я сделал так, как вы, и хотя из вашего метода выше не возникает ошибки, он не попадает в код внутри цикла while и цикла foreach. что-то с асинхронной проблемой?? Также что нового в BlobOpenReadOptions(true) {BufferSize = 16384}. я не могу это решить

Ankit Kumar 27.12.2020 16:27

Как вы можете видеть, на снимке экрана CSV на моем компьютере, на снимке экрана моего Azure Storage Explorer, показывающем, что он загружен в хранилище BLOB-объектов, и на снимке экрана моего отладчика Visual Studio, который загрузил файл, проанализировал его в CSV и вошел в цикл где он показывает проанализированный контент, я не совсем уверен, что я могу сделать, чтобы убедить вас, что код работает - возможно, сделайте запись экрана, на которой я выполняю один шаг в отладчике?

Caius Jard 27.12.2020 17:55

Я выяснил проблему с BlobOptionRead, я не имел в виду правильную версию, но проблема остается, код не входит в цикл foreach или while

Ankit Kumar 27.12.2020 18:58

Тогда записей нет?

Caius Jard 27.12.2020 19:04

файл есть, поток sr тоже имеет длину и позицию

Ankit Kumar 27.12.2020 19:05
Console.WriteLIne(sr.ReadToEnd()) должен распечатать весь файл, поэтому отладьте, почему ваш читатель Csv не читает его. Мой CsvReader читает мой файл и даже читает ваш файл (когда я скопировал и сохранил ваши данные с помощью блокнота). Используйте другой файл CSV, проверьте окончания строк и т. д.
Caius Jard 27.12.2020 19:07

наконец понял, что это была моя ошибка, когда StreamReader читал поток и устанавливал позицию последней в потоке вместо нуля, и это было причиной того, что предыдущий метод не работал. удалил стримридер и теперь все работает нормально. Спасибо за вашу помощь. ребята, как вы, причина того, что stackoverflow настолько успешен.

Ankit Kumar 28.12.2020 01:24

Вы (ваш код) вообще не читаете из средства чтения потока и не устанавливаете позицию потока напрямую; вы просто получаете поток, оборачиваете его потоковым считывателем и отдаете потоковый читатель csvreader — он выполняет все чтение и позиционирование. Если вы прочитаете поток до конца, а затем отдадите его для чтения чему-то другому, «чему-то еще» не останется ничего, что можно было бы прочитать.

Caius Jard 28.12.2020 09:14

Другие вопросы по теме

Похожие вопросы

В моем приложении C#: как мне восстановить текстовое сообщение, полученное от модема GSM, с помощью AT-команд, чтобы отображать смайлики/смайлики, а также слова?
Ошибка имени поставщика строки подключения MicrosoftConfigurationBuilders (C#, Oracle)
Воспроизведение музыки с использованием С#?
Как включить плавающее уведомление и уведомление о блокировке экрана в xamarin android (Java или Kotlin в порядке)
Связывание модели DropDownList ASP.NET MVC в представлении PagedList
Как использовать ActionFilter один раз для выполнения на всех контроллерах
Динамически генерировать выражение свойства и пустой аргумент
Я создаю массив и хочу указать его длину из конструктора
Возникновение «System.InvalidOperationException: «Схема уже существует: Identity.Application» при попытке запуска через IIS Express
C# Присвоение переменной, указывающей на ссылочный тип, другой переменной, что хранится в последней переменной?