如何使用 CSVHelper 将 csv 文件读入 List<List<Object>>

How to read csv file into List<List<Object>> with CSVHelper

我在 text/csv 文件中有此数据:

ID,Boo,Soo
0,True,qwerty
0,True,qwerty

0,True,qwerty
0,True,qwerty

0,True,qwerty
0,True,qwerty
0,True,qwerty

0,True,qwerty
0,True,qwerty
0,True,qwerty
0,True,qwerty

0,True,qwerty
0,True,qwerty
0,True,qwerty
0,True,qwerty

特别注意空行。我想使用 NuGet 中的 CSVHelper 将此数据读入多个列表,其中边界由空行确定。

因此,如果有一个 class MyClass 具有属性 IDBooSoo,我将初始化示例数据直接在上面的代码中,我想要这样的 List<List<Mycass>>

var data = {
    new List<MyClass> {
        { new MyClass {ID = 0, Boo = true, Soo = "qwerty"} },
        { new MyClass {ID = 0, Boo = true, Soo = "qwerty"} }
    },
    new List<MyClass> {
        { new MyClass {ID = 0, Boo = true, Soo = "qwerty"} },
        { new MyClass {ID = 0, Boo = true, Soo = "qwerty"} }
    },
    new List<MyClass> {
        { new MyClass {ID = 0, Boo = true, Soo = "qwerty"} },
        { new MyClass {ID = 0, Boo = true, Soo = "qwerty"} },
        { new MyClass {ID = 0, Boo = true, Soo = "qwerty"} }
    },
    new List<MyClass> {
        { new MyClass {ID = 0, Boo = true, Soo = "qwerty"} },
        { new MyClass {ID = 0, Boo = true, Soo = "qwerty"} },
        { new MyClass {ID = 0, Boo = true, Soo = "qwerty"} },
        { new MyClass {ID = 0, Boo = true, Soo = "qwerty"} }
    },
    new List<MyClass> {
        { new MyClass {ID = 0, Boo = true, Soo = "qwerty"} },
        { new MyClass {ID = 0, Boo = true, Soo = "qwerty"} },
        { new MyClass {ID = 0, Boo = true, Soo = "qwerty"} },
        { new MyClass {ID = 0, Boo = true, Soo = "qwerty"} }
    },
}

但是,当然,我事先并不知道数据的真实情况。我不知道每个列表中需要多少个条目,我也不知道我需要多少个列表。空白行之间的每个列表中可以有任意数量的项目。

这是我目前的代码:

for (int j = 0; j < rnd.Next(4, 10); j++)
{
    for (int i = 0; i < rnd.Next(1, 7); i++)
    {
        ListMyClass.Add(new MyClass { ID = 0, Boo = true, Soo = "qwerty" });
    }

    ListListMyClass.Add(new List<MyClass>(ListMyClass));
    ListMyClass.Clear();
}

using (var csvWiter = new CsvWriter(new StreamWriter("csvHelper.csv"), CultureInfo.InvariantCulture))
{
    foreach (var listRecord in ListListMyClass)
    {
        csvWiter.WriteRecords(listRecord);
        csvWiter.NextRecord();
    }
}

//this code reads all objects, but that's wrong.
using (var reader = new StreamReader("csvHelper.csv"))
            using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
            {
                var list = csv.GetRecords<MyClass>().ToList();
                ;
            }

我该怎么做?

编辑: 我用 Michael Jones 的解决方案解决了这个问题。谢谢。

其他解决方案:

csvReader.Configuration.IgnoreBlankLines = false;
while (csvReader.Read())
            {
                if (csvReader.Context.Record.IsEmpty())
                {
                    ListListMyClass.Add(new List<MyClass>(ListObject));
                    ListMyClass.Clear();
                    continue;
                }
                ListMyClass.Add(csvReader.GetRecord<MyClass>());
            }

谢谢

您可以做的是使用迭代器块提供在空白行之间中断的单独流:

public IEnumerable<TextReader> ReadSeparatedFile(string filePath)
{
     using (var rdr = new StreamReader(filePath))
     {
          string line = rdr.ReadLine();
          while(line is object)
          {
              var buffer = new StringBuilder();
              do
              {
                  if (line != string.Empty) buffer.AppendLine(line);
                  line = rdr.ReadLine();
              } while (line is object && line != string.Empty())
              yield return new StringReader(buffer.ToString());
          }
     }
}

现在您可以调用此方法并获得 CsvReader:

的流序列
var data = ReadSeparatedFile("csvHelper.csv");
var result = new List<List<MyClass>>();
foreach(var stream in data)
{
    using (var rdr = new CsvReader(stream))
    {
        result.Add(rdr.GetRecords<MyClass>());
    }
}

个人不熟悉 CsvReader 类型如何将 csv 列映射到 object 字段,您可能也可能不想将 header 行注入每个序列。但是我们可以通过只添加一行代码并更改另一行代码来做到这一点:

public IEnumerable<TextReader> ReadSeparatedFile(string filePath)
{
     using (var rdr = new StreamReader(filePath))
     {
          string header = rdr.ReadLine() + "\n";
          string line = rdr.ReadLine();
          while(line is object)
          {
              var buffer = new StringBuilder(header);
              do
              {
                  if (line != string.Empty) buffer.AppendLine(line);
                  line = rdr.ReadLine();
              } while (line is object && line != string.Empty())
              yield return new StringReader(buffer.ToString());
          }
     }
}

请阅读文档:https://joshclose.github.io/CsvHelper/api/CsvHelper/CsvReader 您需要调用 csv.GetRecord<MyClass>(); 来获取 CSV 的一条记录(行) 和 csv.Read() 将 Reader 设置为下一行。但是,请考虑在将来使用正确格式的文件。

老实说,我认为您不应该像这样创建 CSV 文件。您可以使用其他格式,例如 JSON 吗?在 Newtonsoft.Json 中查找 serializing/deserializing JSON。对于这样的事情,CSV 是一个糟糕的文件格式选择。

话虽如此,这将达到目的,尽管感觉很丑陋。 (你必须用 CSV 索引装饰你的模型,除非你知道如何手动设置 header。我没有深入研究库)。

void Main()
{
    using (var reader = new StreamReader(@"C:\temp\csvHelper.csv"))
    {
        IEnumerable<IEnumerable<MyClass>> setOfSets = ReadCSVCollection<MyClass>(reader);
    }
}

public static IEnumerable<IEnumerable<T>> ReadCSVCollection<T>(StreamReader reader, bool hasHeader = true)
{
    StringBuilder buffer = new StringBuilder();
    string header = hasHeader ? reader.ReadLine() : null;

    while (!reader.EndOfStream)
    {
        string line = reader.ReadLine();
        if (line.Trim() == string.Empty)
        {
            yield return ReadCSVString<T>(buffer.ToString());
            buffer = new StringBuilder();
            continue;
        }
        buffer.AppendLine(line);
    }

    yield return ReadCSVString<T>(buffer.ToString());
}

public static IEnumerable<T> ReadCSVString<T>(string input, bool hasHeader = false)
{
    using (StringReader sr = new StringReader(input))
    using (CsvReader csv = new CsvReader(sr, CultureInfo.InvariantCulture))
    {
        csv.Configuration.HasHeaderRecord = hasHeader;
        return csv.GetRecords<T>().ToList();
    }
}


public class MyClass
{
    [CsvHelper.Configuration.Attributes.Index(0)]
    public int ID { get; set; }
    [CsvHelper.Configuration.Attributes.Index(1)]
    public bool Boo { get; set; }
    [CsvHelper.Configuration.Attributes.Index(2)]
    public string Soo { get; set; }
}

// Define other methods and classes here

我将添加另一个更依赖于 CsvHelper 的选项。

public static void Main(string[] args)
{        
    using (MemoryStream stream = new MemoryStream())
    using (StreamWriter writer = new StreamWriter(stream))
    using (StreamReader reader = new StreamReader(stream))
    using (CsvReader csv = new CsvReader(reader, CultureInfo.InvariantCulture))
    {
        writer.WriteLine("ID,Boo,Soo");
        writer.WriteLine("0,True,\"Test\nqwerty\"");
        writer.WriteLine("0,True,qwerty");
        writer.WriteLine("");
        writer.WriteLine("0,True,qwerty");
        writer.Flush();
        stream.Position = 0;

        csv.Configuration.IgnoreBlankLines = false;

        csv.Read();
        csv.ReadHeader();

        var data = new List<List<MyClass>>();
        var resultSet = new List<MyClass>();

        while (csv.Read())
        {
            if (csv.Context.RawRecord.Trim() == string.Empty)
            {
                data.Add(resultSet);
                resultSet = new List<MyClass>();
                continue;
            }

            var record = csv.GetRecord<MyClass>();

            resultSet.Add(record);
        }
        if (resultSet.Count > 0)
        {
            data.Add(resultSet);
        }
    } 
    Console.ReadKey();
}

另一种选择:使用 SoftCircuits.CsvParser,您的代码将如下所示:

List<List<MyClass>> data = new List<List<MyClass>>();

using (CsvDataReader<MyClass> reader = new CsvDataReader<MyClass>(path))
{
    reader.ReadHeaders(true);

    data.Add(new List<MyClass>());
    while (reader.Read(out MyClass item))
    {
        if (reader.ColumnCount == 0)
            data.Add(new List<MyClass>());
        else
            data[data.Count - 1].Add(item);
    }
}

我的测试显示 SoftCircuits.CsvParser 平均比 CsvHelper 快四倍。