如何使用 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
具有属性 ID
、Boo
和 Soo
,我将初始化示例数据直接在上面的代码中,我想要这样的 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 快四倍。
我在 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
具有属性 ID
、Boo
和 Soo
,我将初始化示例数据直接在上面的代码中,我想要这样的 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 快四倍。