【问题标题】:Reading and copying large files/blobs without storing them in memory stream in C#读取和复制大文件/blob,而不将它们存储在 C# 的内存流中
【发布时间】:2021-04-02 18:07:56
【问题描述】:

下面是从我的 blob 存储中读取 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)));
                }
            
            }
        
    }

【问题讨论】:

  • “复制表格存储中的内容”的位在哪里?另外,想想流——它们就像管道,你有一些你想读的东西和你想写的东西。现在你使用的方法是给块块一个内存流并写入它,然后你(可能)重置它并将它提供给 tableblob 并让它从中读取.. 但是为什么不只是询问块块它的流并将该流提供给表 blob 并从中读取?这几乎就是流的全部概念。它们只是数据流。你还在想这一切……
  • ... 就“我必须读取所有数据,我必须把它放在某个地方,我必须写它...”而言 - 而是考虑如何通过给“想要读取的东西”直接连接到“想要提供要读取的数据的东西”(或者同样给“想要写入的东西”直接连接到“想要写入的东西”无需站在中间进行临时存储(内存或磁盘)的读/写操作
  • 那是我正在寻找的东西,暂时阅读和编写它是我的想法..但你明白这里在说什么问题,你有任何参考的例子吗?我编辑了读取从 ReadStream() 传递的数据的代码
  • 现在更困惑了。以为您在谈论 azure table storage.. 您似乎在谈论下载 blockblob 并将其转换为 CSV,但您发布了两种读取方法;我会期待读和写。概念保持不变;如果您有“提供可以读取的流的东西”(=“可以提供它将写入的流的东西”)并且您有“提供可以写入的流的东西”(=“您可以可以提供它将读取的流”)然后您可以将它们配对并让它们直接进行读/写而无需临时存储
  • 好的,我在这里做的是从 azure blob 存储中读取 blob,这些 blob 基本上是 csv 文件,我正在将内容复制到 Azure 表格存储中。我没有提供将数据插入表格存储的方法,但这基本上是获取所有 ReadCsv 数据并将它们批量插入表格中。我没有找到任何为 azure blob 提供流的东西。任何想法是否支持 blob

标签: c# azure-blob-storage memorystream


【解决方案1】:

在您坚持认为 CsvHelper 无法从连接到 blob 的流中读取时,我将一些东西放在一起:

  • WinForms 核心应用 (3.1)
  • CsvHelper 最新 (19)
  • Azure.Storage.Blobs (12.8)

我磁盘中的 CSV:

在我的 blob 存储上:

在我的调试器中,它通过 Read/GetRecord 记录了 CAf255 OK:

或通过 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 中的一个字符串道具,没有产生等,只需让文件读取 OK。我也没有为所有的头文件而烦恼——似乎只要在选项中说“文件有头文件”就可以正常工作——你可以看到我的调试器有一个 X 实例,其中一个正确填充的 Sid 属性显示了第一个值。我又运行了一些循环,它们也填充好了

【讨论】:

  • 谢谢,我正在尝试这里提到的所有内容。非常感谢
  • 我按照你的方式做了,虽然上面的方法没有弹出错误,但它没有命中 while 循环和 foreach 循环内的代码。有异步问题的东西??还有什么是新的 BlobOpenReadOptions(true) { BufferSize = 16384 }。我无法解决这个问题
  • 如您所见,从我计算机上的 CSV 屏幕截图、我的 Azure 存储资源管理器显示它上传到 blob 存储的屏幕截图以及我的 Visual Studio 调试器下载文件的屏幕截图,将其解析为 csv 并进入一个循环,在该循环中显示解析的内容,我不确定我能做些什么来说服您该代码有效 - 也许对我单步调试器进行屏幕录制?
  • 我发现了 BlobOptionRead 问题,我指的不是正确的版本,但问题仍然存在,代码没有进入 foreach 或 while 循环
  • 那没有记录?
【解决方案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() 会做什么?
  • 你认为data变量的内容在哪里?
  • :D 是的.. 所以这个想法是:我们得到一个流到 blob,我们将流提供给 csv 阅读器,我们从阅读器读取一点,它从流中提取一点(希望它会这样做,而不是在内部执行 ReadToEnd),并且当我们从阅读器中一点一点地读取读取时,我们正在推进 azure 表存储......而且我们从不使用太多内存
  • 那么编码 utf8 呢?如果我将流传递给 ReadCSV() 是否需要这样做,我也需要解决这个问题
  • 请注意,UTF8 无论如何都是默认的,所以你不需要为它做任何特定的事情
猜你喜欢
  • 2011-10-10
  • 1970-01-01
  • 2015-09-10
  • 1970-01-01
  • 1970-01-01
  • 2015-09-10
  • 1970-01-01
  • 2022-10-04
  • 1970-01-01
相关资源
最近更新 更多