如何使用 FileHelpers 将 csv 文件中的二维数组映射到 poco 集合或字典字典?
How to map 2d array in csv file to poco collection or dictionary of dictionary using FileHelpers?
我的 csv 文件中有以下数据结构:
我想将其解析为以下数据结构:
[DelimitedRecord(","), IgnoreFirst(1)]
public class FxConversionRate
{
[FieldConverter(ConverterKind.Date, "d/M/yyyy")]
public DateTime Date;
public string Currency;
public double Rate;
}
否则想把它解析成Dictionary<string, Dictionary<DateTime, double>>
我怎样才能完成这两种方式?我不想修改源 csv table 布局,并且相信我需要自定义导入和映射。
谢谢
编辑
以下代码片段既将数据从 csv 读取到二维数组中,也将数据读取到数据结构中(在本例中为 Dictionary of Dictionary,但也可以是上面提出的数据结构 FxConversionRate
):
public class FxConversionTable
{
public Dictionary<Currency, Dictionary<DateTime, double>> FxConversionRates{ get; set; } //key1 = Currency, key2 = DateTime, value = double
public string[,] String2DArray{ get; set; }
public FxConversionTable()
{
FxConversionRates = new Dictionary<Currency, Dictionary<DateTime, double>>();
}
public void ReadFxConversionRatesFromCsvFile(string pathFileName)
{
var strings = new List<List<string>>();
using (var reader = new StreamReader(File.OpenRead(pathFileName)))
{
//read symbol rows and parse
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (string.IsNullOrEmpty(line))
continue;
var values = line.Split(',');
//populate string array
strings.Add(values.ToList());
//header
if (strings.Count == 1)
{
foreach (var currencyString in values.Skip(1))
{
Currency ccy = (Currency) Enum.Parse(typeof (Currency), currencyString);
FxConversionRates.Add(ccy, new Dictionary<DateTime, double>());
}
continue;
}
//populate data collection
var date = DateTime.ParseExact(values[0], "d/M/yyyy", CultureInfo.InvariantCulture);
for (int i = 1; i < values.Count(); i++)
{
var ccy = (Currency) Enum.Parse(typeof (Currency), strings[0][i]);
FxConversionRates[ccy].Add(date, Convert.ToDouble(values[i]));
}
}
}
String2DArray = FileIO.ListOfListTo2DArray<string>(strings);
}
}
但是,我仍在通过 FileHelpers 寻找更通用的解决方案...
这应该可以解决您的问题。这不是最优雅的解决方案,但它确实有效。您将需要添加大量错误检查,例如缺少列或数据或源文件损坏等。
private static void Main(string[] args)
{
var fileData = File.ReadAllBytes("Data.csv");
var tableData = CreateDataTableFromFile(fileData);
DataColumn dateColumn = tableData.Columns["Date"];
Dictionary<string, List<FxConversionRate>> rates = new Dictionary<string, List<FxConversionRate>>();
foreach (DataColumn column in tableData.Columns)
{
if (column != dateColumn)
{
foreach (DataRow row in tableData.Rows)
{
FxConversionRate rate = new FxConversionRate();
rate.Currency = column.ColumnName;
rate.Date = DateTime.Parse(row[dateColumn].ToString());
rate.Rate = double.Parse(row[column].ToString());
if (!rates.ContainsKey(column.ColumnName))
rates.Add(column.ColumnName, new List<FxConversionRate>());
rates[column.ColumnName].Add(rate);
}
}
}
foreach (var key in rates.Keys)
{
Console.WriteLine($"Found currency: {key}");
foreach (var rate in rates[key])
{
Console.WriteLine($" {rate.Date.ToShortDateString()} : {rate.Rate:###,###,##0.00}");
}
}
Console.WriteLine("Press any key");
Console.ReadKey();
}
private static DataTable CreateDataTableFromFile(byte[] importFile)
{
var cb = new DelimitedClassBuilder("temp", ",") { IgnoreFirstLines = 0, IgnoreEmptyLines = true, Delimiter = "," };
var ms = new MemoryStream(importFile);
var sr = new StreamReader(ms);
var headerArray = sr.ReadLine().Split(',');
foreach (var header in headerArray)
{
cb.AddField(header, typeof(string));
cb.LastField.FieldQuoted = true;
cb.LastField.QuoteChar = '"';
}
var engine = new FileHelperEngine(cb.CreateRecordClass());
return engine.ReadStreamAsDT(sr);
}
请注意,CreateDataTableFromFile 例程取自
您可以使用一些花哨的 LINQ。
有用的说明:使用 FileHelpers 可以更轻松地将定义文件格式 (FxConversionRateSpec
) 的 class 与目标 class (FxConversionRate
) 分开并在两者之间进行映射两人
// destination object
public class FxConversionRate
{
public DateTime Date { get; set; }
public string Currency { get; set; }
public double Rate { get; set; }
}
// file format specification (FileHelpers class)
[DelimitedRecord(","), IgnoreFirst(1)]
public class FxConversionRateSpec
{
[FieldConverter(ConverterKind.Date, "d/M/yyyy")]
public DateTime Date;
public double[] Rates;
}
class Program
{
static void Main(string[] args)
{
// trimmed down contents...
var contents =
@"DATE,AUD,CAD,CHF" + Environment.NewLine +
@"1/1/2000,88,71,3" + Environment.NewLine +
@"2/1/2000,82,83,86";
// get the records
var engine = new FileHelperEngine<FxConversionRateSpec>();
var records = engine.ReadString(contents);
// get the header
var currencies = contents
.Substring(0, contents.IndexOf(Environment.NewLine)) // take the first line
.Split(',') // split into currencies
.Skip(1); // skip the 'Date' column
// as IEnumerable<FxConversionRate>
var rates = records.SelectMany( // for each record of Date, Double[]
record => currencies.Zip(record.Rates, (c, r) => new { Currency = c, Rate = r}) // combine the rates array with the currency labels
.Select( // for each of the anonymous typed records Currency, Double
currencyRate =>
new FxConversionRate
{
Date = record.Date,
Currency = currencyRate.Currency,
Rate = currencyRate.Rate
}));
Assert.AreEqual(6, rates.Count(), "Exactly 6 records were expected");
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 1) && x.Currency == "AUD" && x.Rate == 88d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 1) && x.Currency == "CAD" && x.Rate == 71d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 1) && x.Currency == "CHF" && x.Rate == 3d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 2) && x.Currency == "AUD" && x.Rate == 82d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 2) && x.Currency == "CAD" && x.Rate == 83d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 2) && x.Currency == "CHF" && x.Rate == 86d) != null);
Console.WriteLine("All tests passed OK.");
Console.ReadKey();
}
}
请注意,创建一个 Dictionary
是非常可行的,尤其是使用 ToDictionary()
LINQ 扩展。
我的 csv 文件中有以下数据结构:
我想将其解析为以下数据结构:
[DelimitedRecord(","), IgnoreFirst(1)]
public class FxConversionRate
{
[FieldConverter(ConverterKind.Date, "d/M/yyyy")]
public DateTime Date;
public string Currency;
public double Rate;
}
否则想把它解析成Dictionary<string, Dictionary<DateTime, double>>
我怎样才能完成这两种方式?我不想修改源 csv table 布局,并且相信我需要自定义导入和映射。
谢谢
编辑
以下代码片段既将数据从 csv 读取到二维数组中,也将数据读取到数据结构中(在本例中为 Dictionary of Dictionary,但也可以是上面提出的数据结构 FxConversionRate
):
public class FxConversionTable
{
public Dictionary<Currency, Dictionary<DateTime, double>> FxConversionRates{ get; set; } //key1 = Currency, key2 = DateTime, value = double
public string[,] String2DArray{ get; set; }
public FxConversionTable()
{
FxConversionRates = new Dictionary<Currency, Dictionary<DateTime, double>>();
}
public void ReadFxConversionRatesFromCsvFile(string pathFileName)
{
var strings = new List<List<string>>();
using (var reader = new StreamReader(File.OpenRead(pathFileName)))
{
//read symbol rows and parse
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (string.IsNullOrEmpty(line))
continue;
var values = line.Split(',');
//populate string array
strings.Add(values.ToList());
//header
if (strings.Count == 1)
{
foreach (var currencyString in values.Skip(1))
{
Currency ccy = (Currency) Enum.Parse(typeof (Currency), currencyString);
FxConversionRates.Add(ccy, new Dictionary<DateTime, double>());
}
continue;
}
//populate data collection
var date = DateTime.ParseExact(values[0], "d/M/yyyy", CultureInfo.InvariantCulture);
for (int i = 1; i < values.Count(); i++)
{
var ccy = (Currency) Enum.Parse(typeof (Currency), strings[0][i]);
FxConversionRates[ccy].Add(date, Convert.ToDouble(values[i]));
}
}
}
String2DArray = FileIO.ListOfListTo2DArray<string>(strings);
}
}
但是,我仍在通过 FileHelpers 寻找更通用的解决方案...
这应该可以解决您的问题。这不是最优雅的解决方案,但它确实有效。您将需要添加大量错误检查,例如缺少列或数据或源文件损坏等。
private static void Main(string[] args)
{
var fileData = File.ReadAllBytes("Data.csv");
var tableData = CreateDataTableFromFile(fileData);
DataColumn dateColumn = tableData.Columns["Date"];
Dictionary<string, List<FxConversionRate>> rates = new Dictionary<string, List<FxConversionRate>>();
foreach (DataColumn column in tableData.Columns)
{
if (column != dateColumn)
{
foreach (DataRow row in tableData.Rows)
{
FxConversionRate rate = new FxConversionRate();
rate.Currency = column.ColumnName;
rate.Date = DateTime.Parse(row[dateColumn].ToString());
rate.Rate = double.Parse(row[column].ToString());
if (!rates.ContainsKey(column.ColumnName))
rates.Add(column.ColumnName, new List<FxConversionRate>());
rates[column.ColumnName].Add(rate);
}
}
}
foreach (var key in rates.Keys)
{
Console.WriteLine($"Found currency: {key}");
foreach (var rate in rates[key])
{
Console.WriteLine($" {rate.Date.ToShortDateString()} : {rate.Rate:###,###,##0.00}");
}
}
Console.WriteLine("Press any key");
Console.ReadKey();
}
private static DataTable CreateDataTableFromFile(byte[] importFile)
{
var cb = new DelimitedClassBuilder("temp", ",") { IgnoreFirstLines = 0, IgnoreEmptyLines = true, Delimiter = "," };
var ms = new MemoryStream(importFile);
var sr = new StreamReader(ms);
var headerArray = sr.ReadLine().Split(',');
foreach (var header in headerArray)
{
cb.AddField(header, typeof(string));
cb.LastField.FieldQuoted = true;
cb.LastField.QuoteChar = '"';
}
var engine = new FileHelperEngine(cb.CreateRecordClass());
return engine.ReadStreamAsDT(sr);
}
请注意,CreateDataTableFromFile 例程取自
您可以使用一些花哨的 LINQ。
有用的说明:使用 FileHelpers 可以更轻松地将定义文件格式 (FxConversionRateSpec
) 的 class 与目标 class (FxConversionRate
) 分开并在两者之间进行映射两人
// destination object
public class FxConversionRate
{
public DateTime Date { get; set; }
public string Currency { get; set; }
public double Rate { get; set; }
}
// file format specification (FileHelpers class)
[DelimitedRecord(","), IgnoreFirst(1)]
public class FxConversionRateSpec
{
[FieldConverter(ConverterKind.Date, "d/M/yyyy")]
public DateTime Date;
public double[] Rates;
}
class Program
{
static void Main(string[] args)
{
// trimmed down contents...
var contents =
@"DATE,AUD,CAD,CHF" + Environment.NewLine +
@"1/1/2000,88,71,3" + Environment.NewLine +
@"2/1/2000,82,83,86";
// get the records
var engine = new FileHelperEngine<FxConversionRateSpec>();
var records = engine.ReadString(contents);
// get the header
var currencies = contents
.Substring(0, contents.IndexOf(Environment.NewLine)) // take the first line
.Split(',') // split into currencies
.Skip(1); // skip the 'Date' column
// as IEnumerable<FxConversionRate>
var rates = records.SelectMany( // for each record of Date, Double[]
record => currencies.Zip(record.Rates, (c, r) => new { Currency = c, Rate = r}) // combine the rates array with the currency labels
.Select( // for each of the anonymous typed records Currency, Double
currencyRate =>
new FxConversionRate
{
Date = record.Date,
Currency = currencyRate.Currency,
Rate = currencyRate.Rate
}));
Assert.AreEqual(6, rates.Count(), "Exactly 6 records were expected");
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 1) && x.Currency == "AUD" && x.Rate == 88d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 1) && x.Currency == "CAD" && x.Rate == 71d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 1) && x.Currency == "CHF" && x.Rate == 3d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 2) && x.Currency == "AUD" && x.Rate == 82d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 2) && x.Currency == "CAD" && x.Rate == 83d) != null);
Assert.That(rates.Single(x => x.Date == new DateTime(2000, 1, 2) && x.Currency == "CHF" && x.Rate == 86d) != null);
Console.WriteLine("All tests passed OK.");
Console.ReadKey();
}
}
请注意,创建一个 Dictionary
是非常可行的,尤其是使用 ToDictionary()
LINQ 扩展。