【问题标题】:Using CsvHelper with a Stream将 CsvHelper 与流一起使用
【发布时间】:2019-11-13 02:23:54
【问题描述】:

我正在尝试使用 CsvHelper 读取 CSV 文件并从中创建一个 DataTable。第一行将是提供列名的标题记录,但除此之外,文件的结构是未知的。如果我使用以下代码(取自 CsvHelper 的作者的示例),它可以工作。

using (var reader = new StreamReader("path\\to\\file.csv"))
using (var csv = new CsvReader(reader))
{
    // Do any configuration to `CsvReader` before creating CsvDataReader.
    using (var dr = new CsvDataReader(csv))
    {        
        var dt = new DataTable();
        dt.Load(dr);
    }
}

但是,如果我为 StreamReader 使用替代构造函数,该构造函数将 Stream 作为参数而不是文件路径,则 CsvDataReader 的创建将失败,并显示错误消息“不支持同步读取”。

我尝试了 CsvHelper 的其他一些方法来尝试以不同的方式处理数据,但每当通过传入 Stream 而不是文件路径创建 StreamReader 时,我都会遇到相同的错误。我开始怀疑真正的问题是否在于 StreamReader 的实现或 CsvHelper。在我的情况(Blazor Server 应用程序)中,传入 Stream 更有意义。有任何想法吗?

编辑:

我相信 David Specht 是正确的,因为我使用的特定流有一些独特之处。在进一步的测试中,我发现一些字符串确实可以工作。在出现错误的情况下,我正在使用 IFileListEntry.Data 流接口从 Steve Sanderson 的 BlazorInputFile 组件(在 GitHub 上)读取流。我怀疑它的实现中有一些东西会导致我遇到错误。如果是这种情况,那么解决方法会很有帮助。 (也许从另一个流创建一个流以在异步和同步流之间切换?还不确定如何做到这一点,但我会试一试。)

【问题讨论】:

    标签: csv stream streamreader blazor csvhelper


    【解决方案1】:

    正如 David Specht 在他的回答中指出的那样,正如我在对原始问题的编辑中指出的那样,它确实适用于某些 Streams。在下面的示例中,流的实现中的某些内容file.Data 不适用于CsvHelper,导致“不支持同步读取”错误。这个特定的流是 IFileListEntry.Data 的一个实例,来自 Steve Sanderson 创建的 BlazorInputFile 组件,可在 GitHub 上获得。 (总而言之,这个组件似乎工作得很好,记住我正在使用版本 0.1.0-preview-00002 所以谢谢,史蒂夫!)

    通过使用Stream.CopyToAsync() 将流复制到新流,问题就消失了。要记住的一个警告是,在此函数执行后,输入和输出流都将位于流的末尾。将用于创建CsvReader 的流必须设置回开头,以便CsvDataReader 构造函数正常工作。如果不这样做,将会出现“No header record was found”错误。

    以下示例适用于我,希望对其他人有所帮助!

    using (var stream2 = new MemoryStream())
    {
        await file.Data.CopyToAsync(stream2);   // although file.Data is itself a stream, using it directly causes "synchronous reads are not supported" errors below.
        stream2.Seek(0, SeekOrigin.Begin);      // at the end of the copy method, we are at the end of both the input and output stream and need to reset the one we want to work with.
        var reader = new System.IO.StreamReader(stream2);
    
        using (var csv = new CsvReader(reader))
        {
            using (var dr = new CsvDataReader(csv)) // error happens here when "file.Data" is used as the stream: "Synchronous reads are not supported"
                                                    // error happens here when the stream isn't reset to the beginning: "No header record was found"
            {
                var dt = new DataTable();
                dt.Load(dr);
            }
        }
    }
    

    【讨论】:

    • 非常感谢!这是我发现的第一个真正解决我的问题的解决方案。我没有使用 CsvDataReader,但第一位特别是 .Seek 帮助很大,因为我收到“未找到标头记录”错误!
    • 应该注意的是,CsvHelper 有一个问题(在撰写本文时打开)表明,即使您调用其中一个异步读取方法,在某些条件下它仍然可以执行同步读取。见github.com/JoshClose/CsvHelper/issues/1400@Zac 在下面提到了这一点,但我想把它说出来。
    • Steve 的“原型”BlazorInputFile 现在已经被官方的InputFile 组件取代,docs.microsoft.com/en-us/aspnet/core/blazor/… 官方组件没有 Zac 提到的 ReadAllAsync 扩展,据我所知可以看到,但我们可以在此处查看其来源:github.com/SteveSandersonMS/BlazorInputFile/blob/master/… 我们看到的内容与您在这里的回答密切相关,因此我相信您的回答。这似乎是 CsvHelper 问题最实用的解决方案。
    • 具有讽刺意味的是,docs.microsoft.com/en-us/aspnet/core/blazor/… 声明:“避免将传入的文件流直接读取到内存中。例如,不要将文件字节复制到 MemoryStream 中或作为字节数组读取。这些方法可以导致性能和安全问题,尤其是在 Blazor Server 中。”但我可以这样做,因为我使用的是 Blazor WebAssembly,而且我不会让文件太大。
    • @BellarmineHead 我正在使用 Blazor Server 和 MSDN 文档警告直接将文件读取到内存被考虑如何使用 csvHelper 读取文件任何示例?
    【解决方案2】:

    我在使用 BlazorInputFile 和 CsvReader 时遇到了同样的问题。我查看了 BlazorInputFile 的代码,如果您同步读取流,您可以看到 Steve 在哪里抛出错误。此外,CsvReader 的 git 中有几个未解决的项目和讨论似乎与为什么在 BlazorInputFile 中不允许这样做有关。 CsvParser's ReadAsync can read stream synchronouslyimprove async/await performance 另一方面,有一个扩展方法 ReadAllAsync 添加到 IFileListEntry,它返回一个异步 MemoryStream。您只需在创建 StreamReader 时调用它即可。

    注意:

    1. 使用最新版本的 CsvReader,您必须指定 CultureInfo 参数。
    2. 我已成功使用此功能,并将 csv 映射到 IList 而不是 DataTable,因此您仍然可能会遇到问题,但它应该可以工作。
                using var stream = new StreamReader(await file.ReadAllAsync());
                using var csv = new CsvReader(stream, CultureInfo.InvariantCulture);
                using var dr = new CsvDataReader(csv);
                var dt = new DataTable();
                dt.Load(dr);
    

    【讨论】:

    • 您是否在不先保存文件的情况下读取从 blazor 上传的文件内容?您可以在此答案中添加更多部分吗? like file.ReadAllAsync() 是actula上传的文件还是保存到某个路径的文件?
    【解决方案3】:

    可能您正在使用的流需要异步读取?以下对我有用。

    var request = WebRequest.Create("https://people.sc.fsu.edu/~jburkardt/data/csv/addresses.csv");
    var response = request.GetResponse();
    
    using (var stream = response.GetResponseStream())
    using (var csv = new CsvReader(new StreamReader(stream)))
    {
        using (var dr = new CsvDataReader(csv))
        {
            var dt = new DataTable();
            dt.Load(dr);
        }
    }
    

    【讨论】:

    • 嗨大卫,我怀疑你是对的。在进一步的测试中,我发现一些流确实有效。请参阅我上面提供的更多信息的编辑。如果是这种情况,那么我需要某种解决方法。
    猜你喜欢
    • 2015-10-18
    • 2016-12-19
    • 2011-08-18
    • 2013-08-18
    • 1970-01-01
    • 2019-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多