CsvHelper - 读取同一 CSV 中的不同记录类型
CsvHelper - Read different record types in same CSV
我正在尝试从具有以下结构的 CSV 文件中读取两种类型的记录:
PlaceName,Longitude,Latitude,Elevation
NameString,123.456,56.78,40
Date,Count
1/1/2012,1
2/1/2012,3
3/1/2012,10
4/2/2012,6
我知道这个问题之前在
但是当我 运行 我的实现时它得到一个 CsvMissingFieldException
说 Fields 'Date' do not exist in the CSV file
。我有两个定义和映射 类,一个用于位置,另一个用于计数,它们是:
public class LocationDefinition
{
public string PlaceName { get; set; }
public double Longitude { get; set; }
public double Latitude { get; set; }
public double Elevation { get; set; }
}
public sealed class LocationMap : CsvClassMap<LocationDefinition>
{
public LocationMap()
{
Map(m => m.PlaceName).Name("PlaceName");
Map(m => m.Longitude).Name("Longitude");
Map(m => m.Latitude).Name("Latitude");
Map(m => m.Elevation).Name("Elevation");
}
}
public class CountDefinition
{
public DateTime Date { get; set; }
public int Count { get; set; }
}
public sealed class CountMap : CsvClassMap<CountDefinition>
{
public CountMap()
{
Map(m => m.Date).Name("Date");
Map(m => m.Count).Name("Count");
}
}
我读取 csv 文件的代码是:
LocationDefinition Location;
var Counts = new List<CountDefinition>();
using (TextReader fileReader = File.OpenText(@"Path\To\CsvFile"))
using (var csvReader = new CsvReader(fileReader))
{
csvReader.Configuration.RegisterClassMap<LocationMap>();
csvReader.Configuration.RegisterClassMap<CountMap>();
// Only reads a single line of Location data
csvReader.Read();
LocationData = csvReader.GetRecord<LocationDefinition>();
csvReader.Read(); // skip blank line
csvReader.Read(); // skip second header section
// Read count data records
while (csvReader.Read())
{
var tempCount = csvReader.GetRecord<CountDefinition>();
Counts.Add(tempCount);
}
}
在 tempCount
行抛出异常。据我所知,它仍然需要 Location 记录,但我认为 GetRecord<CountDefinition>
会指定记录类型。我也试过 ClearRecordCache
并取消注册 LocationMap
无济于事。
应该如何更改此代码才能使其读取这种结构的 csv 文件?
试试这个
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
enum State
{
FIND_RECORD,
GET_LOCATION,
GET_DATES
}
class Program
{
const string FILENAME = @"c:\temp\test.txt";
static void Main(string[] args)
{
StreamReader reader = new StreamReader(FILENAME);
State state = State.FIND_RECORD;
LocationDefinition location = null;
string inputLine = "";
while ((inputLine = reader.ReadLine()) != null)
{
inputLine = inputLine.Trim();
if (inputLine.Length == 0)
{
state = State.FIND_RECORD;
}
else
{
switch (state)
{
case State.FIND_RECORD :
if (inputLine.StartsWith("PlaceName"))
{
state = State.GET_LOCATION;
}
else
{
if (inputLine.StartsWith("Date"))
{
state = State.GET_DATES;
}
}
break;
case State.GET_DATES :
if (location.dates == null) location.dates = new CountDefinition();
location.dates.dates.Add(new CountDefinition(inputLine));
break;
case State.GET_LOCATION :
location = new LocationDefinition(inputLine);
break;
}
}
}
}
}
public class LocationDefinition
{
public static List<LocationDefinition> locations = new List<LocationDefinition>();
public CountDefinition dates { get; set; }
public string PlaceName { get; set; }
public double Longitude { get; set; }
public double Latitude { get; set; }
public double Elevation { get; set; }
public LocationDefinition(string location)
{
string[] array = location.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
PlaceName = array[0];
Longitude = double.Parse(array[1]);
Latitude = double.Parse(array[2]);
Elevation = double.Parse(array[3]);
locations.Add(this);
}
}
public class CountDefinition
{
public List<CountDefinition> dates = new List<CountDefinition>();
public DateTime Date { get; set; }
public int Count { get; set; }
public CountDefinition() { ;}
public CountDefinition(string count)
{
string[] array = count.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
Date = DateTime.Parse(array[0]);
Count = int.Parse(array[1]);
dates.Add(this);
}
}
}
我收到了 Josh Close 关于问题跟踪器的回复:
CsvReader not recognising different registered class maps
这是他对这个问题的回答:
Since you don't have a single header, you'll need to ignore headers
and use indexes instead. This brings up an idea though. I could have
the ReadHeader method parse headers for a specific record type.
Here is an example that should work for you though.
void Main()
{
LocationDefinition Location;
var Counts = new List<CountDefinition>();
using (var stream = new MemoryStream())
using (var reader = new StreamReader(stream))
using (var writer = new StreamWriter(stream))
using (var csvReader = new CsvReader(reader))
{
writer.WriteLine("PlaceName,Longitude,Latitude,Elevation");
writer.WriteLine("NameString,123.456,56.78,40");
writer.WriteLine();
writer.WriteLine("Date,Count");
writer.WriteLine("1/1/2012,1");
writer.WriteLine("2/1/2012,3");
writer.WriteLine("3/1/2012,10");
writer.WriteLine("4/2/2012,6");
writer.Flush();
stream.Position = 0;
csvReader.Configuration.HasHeaderRecord = false;
csvReader.Configuration.RegisterClassMap<LocationMap>();
csvReader.Configuration.RegisterClassMap<CountMap>();
csvReader.Read(); // get header
csvReader.Read(); // get first record
var locationData = csvReader.GetRecord<LocationDefinition>();
csvReader.Read(); // skip blank line
csvReader.Read(); // skip second header section
// Read count data records
while (csvReader.Read())
{
var tempCount = csvReader.GetRecord<CountDefinition>();
Counts.Add(tempCount);
}
}
}
public class LocationDefinition
{
public string PlaceName { get; set; }
public double Longitude { get; set; }
public double Latitude { get; set; }
public double Elevation { get; set; }
}
public sealed class LocationMap : CsvClassMap<LocationDefinition>
{
public LocationMap()
{
Map(m => m.PlaceName);
Map(m => m.Longitude);
Map(m => m.Latitude);
Map(m => m.Elevation);
}
}
public class CountDefinition
{
public DateTime Date { get; set; }
public int Count { get; set; }
}
public sealed class CountMap : CsvClassMap<CountDefinition>
{
public CountMap()
{
Map(m => m.Date);
Map(m => m.Count);
}
}
我正在尝试从具有以下结构的 CSV 文件中读取两种类型的记录:
PlaceName,Longitude,Latitude,Elevation
NameString,123.456,56.78,40
Date,Count
1/1/2012,1
2/1/2012,3
3/1/2012,10
4/2/2012,6
我知道这个问题之前在
但是当我 运行 我的实现时它得到一个 CsvMissingFieldException
说 Fields 'Date' do not exist in the CSV file
。我有两个定义和映射 类,一个用于位置,另一个用于计数,它们是:
public class LocationDefinition
{
public string PlaceName { get; set; }
public double Longitude { get; set; }
public double Latitude { get; set; }
public double Elevation { get; set; }
}
public sealed class LocationMap : CsvClassMap<LocationDefinition>
{
public LocationMap()
{
Map(m => m.PlaceName).Name("PlaceName");
Map(m => m.Longitude).Name("Longitude");
Map(m => m.Latitude).Name("Latitude");
Map(m => m.Elevation).Name("Elevation");
}
}
public class CountDefinition
{
public DateTime Date { get; set; }
public int Count { get; set; }
}
public sealed class CountMap : CsvClassMap<CountDefinition>
{
public CountMap()
{
Map(m => m.Date).Name("Date");
Map(m => m.Count).Name("Count");
}
}
我读取 csv 文件的代码是:
LocationDefinition Location;
var Counts = new List<CountDefinition>();
using (TextReader fileReader = File.OpenText(@"Path\To\CsvFile"))
using (var csvReader = new CsvReader(fileReader))
{
csvReader.Configuration.RegisterClassMap<LocationMap>();
csvReader.Configuration.RegisterClassMap<CountMap>();
// Only reads a single line of Location data
csvReader.Read();
LocationData = csvReader.GetRecord<LocationDefinition>();
csvReader.Read(); // skip blank line
csvReader.Read(); // skip second header section
// Read count data records
while (csvReader.Read())
{
var tempCount = csvReader.GetRecord<CountDefinition>();
Counts.Add(tempCount);
}
}
在 tempCount
行抛出异常。据我所知,它仍然需要 Location 记录,但我认为 GetRecord<CountDefinition>
会指定记录类型。我也试过 ClearRecordCache
并取消注册 LocationMap
无济于事。
应该如何更改此代码才能使其读取这种结构的 csv 文件?
试试这个
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
enum State
{
FIND_RECORD,
GET_LOCATION,
GET_DATES
}
class Program
{
const string FILENAME = @"c:\temp\test.txt";
static void Main(string[] args)
{
StreamReader reader = new StreamReader(FILENAME);
State state = State.FIND_RECORD;
LocationDefinition location = null;
string inputLine = "";
while ((inputLine = reader.ReadLine()) != null)
{
inputLine = inputLine.Trim();
if (inputLine.Length == 0)
{
state = State.FIND_RECORD;
}
else
{
switch (state)
{
case State.FIND_RECORD :
if (inputLine.StartsWith("PlaceName"))
{
state = State.GET_LOCATION;
}
else
{
if (inputLine.StartsWith("Date"))
{
state = State.GET_DATES;
}
}
break;
case State.GET_DATES :
if (location.dates == null) location.dates = new CountDefinition();
location.dates.dates.Add(new CountDefinition(inputLine));
break;
case State.GET_LOCATION :
location = new LocationDefinition(inputLine);
break;
}
}
}
}
}
public class LocationDefinition
{
public static List<LocationDefinition> locations = new List<LocationDefinition>();
public CountDefinition dates { get; set; }
public string PlaceName { get; set; }
public double Longitude { get; set; }
public double Latitude { get; set; }
public double Elevation { get; set; }
public LocationDefinition(string location)
{
string[] array = location.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
PlaceName = array[0];
Longitude = double.Parse(array[1]);
Latitude = double.Parse(array[2]);
Elevation = double.Parse(array[3]);
locations.Add(this);
}
}
public class CountDefinition
{
public List<CountDefinition> dates = new List<CountDefinition>();
public DateTime Date { get; set; }
public int Count { get; set; }
public CountDefinition() { ;}
public CountDefinition(string count)
{
string[] array = count.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
Date = DateTime.Parse(array[0]);
Count = int.Parse(array[1]);
dates.Add(this);
}
}
}
我收到了 Josh Close 关于问题跟踪器的回复:
CsvReader not recognising different registered class maps
这是他对这个问题的回答:
Since you don't have a single header, you'll need to ignore headers and use indexes instead. This brings up an idea though. I could have the ReadHeader method parse headers for a specific record type.
Here is an example that should work for you though.
void Main() { LocationDefinition Location; var Counts = new List<CountDefinition>(); using (var stream = new MemoryStream()) using (var reader = new StreamReader(stream)) using (var writer = new StreamWriter(stream)) using (var csvReader = new CsvReader(reader)) { writer.WriteLine("PlaceName,Longitude,Latitude,Elevation"); writer.WriteLine("NameString,123.456,56.78,40"); writer.WriteLine(); writer.WriteLine("Date,Count"); writer.WriteLine("1/1/2012,1"); writer.WriteLine("2/1/2012,3"); writer.WriteLine("3/1/2012,10"); writer.WriteLine("4/2/2012,6"); writer.Flush(); stream.Position = 0; csvReader.Configuration.HasHeaderRecord = false; csvReader.Configuration.RegisterClassMap<LocationMap>(); csvReader.Configuration.RegisterClassMap<CountMap>(); csvReader.Read(); // get header csvReader.Read(); // get first record var locationData = csvReader.GetRecord<LocationDefinition>(); csvReader.Read(); // skip blank line csvReader.Read(); // skip second header section // Read count data records while (csvReader.Read()) { var tempCount = csvReader.GetRecord<CountDefinition>(); Counts.Add(tempCount); } } } public class LocationDefinition { public string PlaceName { get; set; } public double Longitude { get; set; } public double Latitude { get; set; } public double Elevation { get; set; } } public sealed class LocationMap : CsvClassMap<LocationDefinition> { public LocationMap() { Map(m => m.PlaceName); Map(m => m.Longitude); Map(m => m.Latitude); Map(m => m.Elevation); } } public class CountDefinition { public DateTime Date { get; set; } public int Count { get; set; } } public sealed class CountMap : CsvClassMap<CountDefinition> { public CountMap() { Map(m => m.Date); Map(m => m.Count); } }