问题
I'm trying to read two types of records out of a CSV file with the following structure:
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
I know this question has been covered previously in
- Reading multiple classes from single csv file using CsvHelper
- Multiple Record Types in One File?
but when I run my implementation it gets a CsvMissingFieldException
saying that Fields 'Date' do not exist in the CSV file
. I have two definition and map classes, one for the location and the other for the counts, which are:
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");
}
}
The code that I have for reading the csv file is:
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);
}
}
The exception gets thrown on the tempCount
line. From what I can tell it still expects a Location record, but I would have thought GetRecord<CountDefinition>
would specify the record type. I've also tried ClearRecordCache
and unregistering the LocationMap
to no avail.
How should this code be changed to get it to read a csv file of this structure?
回答1:
Try this
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);
}
}
}
回答2:
I got a response from Josh Close on the issue tracker:
CsvReader not recognising different registered class maps
Here is his answer to this question:
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); } }
来源:https://stackoverflow.com/questions/38515214/csvhelper-read-different-record-types-in-same-csv