Запись списка сложных объектов в файл CSV с использованием CSVHelper - PullRequest
0 голосов
/ 02 октября 2019

У меня есть список объектов класса, который в свою очередь содержит список объектов другого класса. Они выглядят так:

public class Column
{
    public string ColName { get; set; }
    public List<Item> ItemList { get; set; }
}

public class Item
{
    public DateTime TimeStamp { get; set; }
    public double Value { get; set; }
}

У них есть два важных свойства:

  1. Length из List<Item> ItemList во всех столбцах будет одинаковым. Я не буду знать, что это за длина до времени выполнения.
  2. Фактические значения TimeStamp в каждом столбце будут одинаковыми. Другими словами, список TimeStamp в каждом столбце будет идентичен.

Если вы посмотрите на следующую фиктивную функцию, которую я написал для создания списка Column объектов, вы получитечеткая картина. Это точное описание того, как будут выглядеть мои фактические данные (которые я получаю откуда-то еще в программе):

private static List<Column> GetColumns()
{
    var dt1 = DateTime.Now;
    var dt2 = dt1.AddSeconds(1);
    var dt3 = dt2.AddSeconds(1);
    var dt4 = dt3.AddSeconds(1);

    var col1 = new Column()
    {
        ColName = "ABC",
        ItemList = new List<Item>
        {
            new Item() { TimeStamp = dt1, Value = 1 },
            new Item() { TimeStamp = dt2, Value = 2 },
            new Item() { TimeStamp = dt3, Value = 3 },
            new Item() { TimeStamp = dt4, Value = 4 }
        }
    };

    var col2 = new Column()
    {
        ColName = "XYZ",
        ItemList = new List<Item>
        {
            new Item() { TimeStamp = dt1, Value = 4 },
            new Item() { TimeStamp = dt2, Value = 3 },
            new Item() { TimeStamp = dt3, Value = 2 },
            new Item() { TimeStamp = dt4, Value = 1 }
        }
    };

    var col3 = new Column()
    {
        ColName = "KLM",
        ItemList = new List<Item>
        {
            new Item() { TimeStamp = dt1, Value = 1 },
            new Item() { TimeStamp = dt2, Value = 2 },
            new Item() { TimeStamp = dt3, Value = 4 },
            new Item() { TimeStamp = dt4, Value = 8 }
        }
    };

    var list = new List<Column>
    {
        col1,
        col2,
        col3,
    };

    return list;
}

Важным примечанием является то, что я не буду знать длину List<Column> дово время выполнения, и я не буду знать длину ItemList внутри них до времени выполнения. Кроме того, я не буду знать имена каждого столбца до времени выполнения. Теперь моя цель - записать эту информацию в файл CSV следующего формата.

enter image description here

Мне кажется, что dynamic - это путьчтобы перейти сюда, поэтому я начал с этого:

var columns = GetColumns();

var timeStamps = columns.First().ItemList.Select(x => x.TimeStamp).ToList();

var writeList = new List<dynamic>();
for (int i = 0; i < timeStamps.Count; i++)
{
    dynamic csvItem = new ExpandoObject();
    csvItem.TimeStamp = timeStamps[i];
    // How to get columns?
    writeList.Add(csvItem);
}

using (var writer = new StreamWriter("output.csv"))
{
    using (var csv = new CsvHelper.CsvWriter(writer))
    {
        csv.WriteRecords(writeList);
    }
}

Это начало, но так как я не знаю количество столбцов, которые у меня будут, и названия их, я не уверен, какпродолжить отсюда. Использование dynamic здесь не вариант?

В качестве альтернативы я мог бы вообще отказаться от использования CSVHelper и написать что-то с нуля, как показано ниже, но это немного грязно. Я перебираю список столбцов дважды, и всего три цикла. Я ищу более элегантное решение, если это возможно.

var columns = GetColumns();

var timeStamps = columns.First().ItemList.Select(x => x.TimeStamp).ToList();

var headers = "TimeStamp";
foreach (var col in columns)
{
    headers += "," + col.ColName;
}

using (var fs = new FileStream("output.csv", FileMode.Create, FileAccess.Write))
{
    using (var sw = new StreamWriter(fs))
    {
        sw.WriteLine(headers);
        for (int i = 0; i < timeStamps.Count; i++)
        {
            var line = timeStamps[i].ToString("yyyy/MM/dd HH:mm:ss");
            foreach (var col in columns)
            {
                line += "," + col.ItemList[i].Value;
            }
            sw.WriteLine(line);
        }
    }
}

Ответы [ 3 ]

0 голосов
/ 02 октября 2019

Вот версия, использующая dynamic.

var columns = GetColumns();

var writeList = new List<dynamic>();

for (int i = 0; i < columns[0].ItemList.Count; i++)
{
    var csvItem = new ExpandoObject() as IDictionary<string, object>;

    csvItem.Add("TimeStamp", columns[0].ItemList[i].TimeStamp.ToString("yyyy/MM/dd HH:mm:ss"));

    foreach (var column in columns)
    {
        csvItem.Add(column.ColName, column.ItemList[i].Value);
    }

    writeList.Add(csvItem);
}

using (var writer = new StreamWriter("output.csv"))
{
    using (var csv = new CsvHelper.CsvWriter(writer))
    {
        csv.WriteRecords(writeList);
    }
}
0 голосов
/ 04 октября 2019

Вот способ обработки записи CSV с использованием StringBuilder. Этот код подходит для ситуаций, когда вам просто нужно конвертировать ваш объект в CSV. Но если вам нужно использовать csv в качестве механизма персистентности (который в любом случае безумен, используйте вместо этого mongoDb или Sqlite), вам придется добавить больше функциональности в эти методы расширения.

Разделитель : тип файла CSV - значения, разделенные запятыми. Так что этот код использует ",". Вы также можете создать TAB с разделителями, если хотите внести некоторые простые изменения в этот код.

Использование:

var columns = GetColumns();

Console.WriteLine(columns.ToCsv());
//OR
columns.SaveAsCsv(@"c:\columns.csv");

Csv Extensions;

public static class ColumnExtensions
{
    public static void SaveAsCsv(this List<Column> columns, string filePath)
    {
        File.WriteAllText(columns.ToCsv(), filePath);
    }

    public static string ToCsv(this List<Column> columns)
    {
        var csv = new StringBuilder();

        // Write as an expression or simply 
        //csv.AppendCsvHeader(nameof(Item.TimeStamp));
        csv.AppendCsvHeader<string, DateTime>(x => columns.First().ItemList.First().TimeStamp);

        for (var index = 0; index < columns.Count; index++)
        {
            var column = columns[index];

            // Most csv readers don't care if you have a "," at the end of the line. But for completeness we avoid doing that.
            // It makes the code a bit more complicated though. You can ignore this you want.
            csv.AppendCsvHeader(column.ColName, index == columns.Count - 1);
        }

        csv.AppendLine();

        for (var i = 0; i < columns[0].ItemList.Count; i++)
        {
            csv.AppendCsvField(columns[0]
                .ItemList[i]
                .TimeStamp.ToString("yyyy/MM/dd HH:mm:ss"));

            for (var index = 0; index < columns.Count; index++)
            {
                var column = columns[index];

                csv.AppendCsvField(column.ItemList[i]
                        .Value.ToString("N"), index == columns.Count - 1);
            }

            csv.AppendLine();
        }

        return csv.ToString();
    }
}

public static class CsvExtensions
{
    private const string Delimiter = ",";

    private static string AsCsvFriendly(this string val)
    {
        return val?.Replace(",", ";") ?? string.Empty;
    }

    private static string AddDelimiterIfRequired(bool withoutDelimiter)
    {
        return withoutDelimiter ? string.Empty : Delimiter;
    }

    public static void AppendCsvField(this StringBuilder stringBuilder, string value, bool withoutDelimiter = false)
    {
        stringBuilder.Append($"{value.AsCsvFriendly()}{AddDelimiterIfRequired(withoutDelimiter)}");
    }

    public static void AppendCsvHeader(this StringBuilder stringBuilder, string value, bool withoutDelimiter = false)
    {
        stringBuilder.Append($"{value.AsCsvFriendly()}{AddDelimiterIfRequired(withoutDelimiter)}");
    }

    public static void AppendCsvHeader<TIn, TOut>(this StringBuilder stringBuilder, Expression<Func<TIn, TOut>> f, bool withoutDelimiter = false)
    {
        stringBuilder.Append($"{(f.Body as MemberExpression)?.Member.Name}{AddDelimiterIfRequired(withoutDelimiter)}");
    }
}
0 голосов
/ 02 октября 2019

Это, вероятно, лучшее, что вы можете сделать с CsvHelper для ваших требований.

using (var writer = new StreamWriter("output.csv"))
{
    using (var csv = new CsvHelper.CsvWriter(writer))
    {
        var columns = GetColumns();

        // Write header
        csv.WriteField("TimeStamp");

        foreach (var column in columns)
        {
            csv.WriteField(column.ColName);
        }

        csv.NextRecord();

        // Write rows
        for (int i = 0; i < columns[0].ItemList.Count; i++)
        {
            csv.WriteField(columns[0].ItemList[i].TimeStamp.ToString("yyyy/MM/dd HH:mm:ss"));

            foreach (var column in columns)
            {
                csv.WriteField(column.ItemList[i].Value);
            }

            csv.NextRecord();
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...