如何使用 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 扩展。