将 CsvHelper 与流一起使用

Using CsvHelper with a Stream

我正在尝试使用 CsvHelper 读取 CSV 文件并从中创建 DataTable。第一行将是提供列名的 header 记录,但除此之外,文件的结构未知。如果我使用以下代码(取自 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 的创建将失败并显示错误消息 "Synchronous reads are not supported."

我已经尝试了 CsvHelper 的其他几种方法来尝试以不同的方式处理数据,但是每当通过传入 Stream 而不是文件路径。我开始怀疑真正的问题是在于 StreamReader 的实现还是在于 CsvHelper。在我的情况下(一个 Blazor 服务器应用程序),传入流更有意义。有任何想法吗?

编辑:

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

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

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);
    }
}

正如 David Specht 在他的回答中指出的,以及我在对原始问题的编辑中指出的那样,它 确实 与某些流一起工作。在下面的示例中,流 file.Data 的实现与 CsvHelper 不兼容,导致 "Synchronous reads are not supported" 错误。这个特定的流是 BlazorInputFile 组件的实例 IFileListEntry.Data,由 Steve Sanderson 创建并在 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);
        }
    }
}

我在使用 BlazorInputFile 和 CsvReader 时遇到了同样的问题。我查看了 BlazorInputFile 的代码,如果您同步读取流,您可以看到 Steve 在何处抛出错误。此外,在 CsvReader 的 git 中有几个未解决的项目和讨论似乎与为什么在 BlazorInputFile 中不允许它有关。 CsvParser's ReadAsync can read stream synchronously and improve async/await performance 另一方面,有一个扩展方法 ReadAllAsync 添加到 returns 异步 MemoryStream 的 IFileListEntry。您只需在创建 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);