2

我有一个类对象列表,其中又包含另一个类对象的列表。它们看起来像这样:

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. 所有列中的LengthofList<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文件,格式如下。

在此处输入图像描述

我觉得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);
        }
    }
}
4

3 回答 3

1

这可能是您使用 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();
        }
    }
}
于 2019-10-02T13:16:02.580 回答
0

这是一个使用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);
    }
}
于 2019-10-02T16:43:11.390 回答
0

这是使用 StringBuilder 处理 CSV 写入的方法。此代码适用于您只需要将对象转换为 csv 的情况。但是如果您需要使用 csv 作为持久性机制(无论如何这很疯狂,请改用 mongoDb 或 Sqlite),您将不得不为这些扩展方法添加更多功能。

分隔符:文件类型为 CSV - 逗号分隔值。所以这段代码使用“,”。如果您愿意,也可以通过对此代码进行一些简单的修改来创建制表符分隔。

用法:

var columns = GetColumns();

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

CSV 扩展;

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)}");
    }
}
于 2019-10-03T22:56:44.937 回答