使用 csvHelper 动态创建列

Posted

技术标签:

【中文标题】使用 csvHelper 动态创建列【英文标题】:Dynamic creation of columns using csvHelper 【发布时间】:2017-06-27 07:52:35 【问题描述】:

我有一个工作人员,其中包含从服务器获取的各种字段。我正在使用 CSVHelper 包将此类转换为 Excel 工作表。 工人有这样的领域:

class Worker
 
    string name;
    string phone;
    string age;
    Dictionary<string,object> customerField;

我可以映射姓名、电话、号码等

class WorkerMap : CsvClassMap<Worker>

    public WorkerMap()
    
        Map(m => m.name);
        Map(m => m.phone);
        Map(m => m.age);
    

我通过以下方式生成地图:

csv.Configuration.RegisterClassMap<WorkerMap>();

按以下方式编写工人列表:

csv.WriteRecords(workerList);

如何将 customerField 字典映射到 Excel 工作表,以便 Key(字符串)是另一个列名,而 value(对象)是该列的值。

CSVHelper 是否帮助我们在运行时执行此操作。我浏览了文档。找不到任何对我有用的东西。

【问题讨论】:

也请参阅此主题以获得一些不错的解决方案:github.com/JoshClose/CsvHelper/issues/719。 【参考方案1】:

我认为目前不支持编写字典。一方面,CsvHelper 很难知道要写什么标题。幸运的是,手动使用 CsvWriter 并不太复杂,一次写入一个字段。如果我们假设每个 Worker 在customerField 中都有相同的键,那么您的代码可能看起来像这样。

var firstWorker = workerList.First();
var keys = firstWorker.customerField.Keys.ToList();

var headers = new [] "name", "phone", "age".Concat(keys).ToList();
var csv = new CsvWriter( textWriter );

// Write the headers
foreach( var header in headers )

    csv.WriteField(header);

csv.NextRecord();

// Write the rows
foreach( var item in workerList)

    csv.WriteField(item.name);
    csv.WriteField(item.phone);
    csv.WriteField(item.age);
    var dict = worker.customerField;
    foreach (var key in keys)
    
        csv.WriteField(dict[key]);
    
    csv.NextRecord();

此代码未经测试,但应该可以让您非常接近所需的行为。如果customerField 字典键在列表中不一致,那么代码会稍微复杂一些,但仍然可以解决。

【讨论】:

谢谢迈克尔。这引导我找到了我需要的答案。【参考方案2】:

不支持字典,但支持 ExpandoObject。

https://github.com/JoshClose/CsvHelper/blob/48e70742e06007dae3a635c418b7e3358f667c4f/src/CsvHelper.Tests/Writing/MultipleHeadersTest.cs

https://github.com/JoshClose/CsvHelper/blob/b74a2f95a101158f4cdedd25fae6e8392b58855b/src/CsvHelper.Tests/Writing/DynamicTests.cs

如果您点击上面的第一个链接,您会发现第 50 和 57 行正在使用 WriteDynamicHeader 方法。

借助扩展方法,我为每条记录创建一个 ExpandoObject 并使用 CsvHelper 编写该对象。名为 documentDictionary&lt;string, object&gt; 参数是我希望从中创建 CSV 记录的参数。

public static class DictionaryCsvExtentions

    public static dynamic BuildCsvObject(this Dictionary<string, object> document)
    
        dynamic csvObj = new ExpandoObject();

        foreach (var p in document)
        
            AddProperty(csvObj, p.Key, p.Value);
        

        return csvObj;
    

    private static void AddProperty(ExpandoObject expando, string propertyName, object propertyValue)
    
        var expandoDict = expando as IDictionary<string, object>;
        if (expandoDict.ContainsKey(propertyName))
        
            expandoDict[propertyName] = propertyValue;
        
        else
        
            expandoDict.Add(propertyName, propertyValue);
        
    

现在我可以像这样从我的字典中创建一个 ExpandoObject

var csvObj = myDictonary.BuildCsvObject();

因此,在上面链接中的 Josh 测试之后,我们拥有了与 CsvHelper 相当无缝地使用字典所需的一切。我认为这不是 Michael 的更好解决方案,只是一种不同的方法。

credit where credit is due to the basic ExpandoObject from dictionary code is from here(这里有更多解释!)https://www.oreilly.com/learning/building-c-objects-dynamically

【讨论】:

【参考方案3】:

最近遇到了同样的问题,为了完整起见,发布另一个答案。我们有相当复杂的ClassMap&lt;Worker&gt; 类,并且确实想放松它。此外,我们需要 CSV 写入和读取,因此也需要来自 rob 答案的ExpandoObject。最终,这种方法就像 Michael Richardson 和 rob 答案的结合,应该会带来两全其美的效果。

最重要的是,为了在读取过程中区分 CSV 文件中的字典字段,最好在它们前面加上 "customerField." 之类的前缀。

首先我们需要字典与 Worker.customerField 之间的转换:

public static class WorkerExtensions

    const string CustomerFieldPrefix = nameof(Worker.customerField) + ".";

    public static dynamic GetCustomerFieldExpando(this Worker worker)
    
        var expando = new ExpandoObject() as IDictionary<string, object>;

        foreach (var fieldPair in worker.customerField)
        
            expando[CustomerFieldPrefix + fieldPair.Key] = fieldPair.Value ?? "";
        

        return expando;
    

    public static void SetCustomerField(this Worker worker, ExpandoObject expando)
    
        var columnsToValues = expando as IDictionary<string, object>;

        foreach (var columnValuePair in columnsToValues)
        
            if (columnValuePair.Key.StartsWith(CustomerFieldPrefix)
                && columnValuePair.Key.Length > CustomerFieldPrefix.Length)
            
                string key = columnValuePair.Key.Substring(CustomerFieldPrefix.Length);
                worker.customerField[key] = columnValuePair.Value;
            
        
    

接下来,CSV 写入可以同时使用ClassMapExpandoObject,如下所示:

        csv.Configuration.HasHeaderRecord = true;
        csv.Configuration.RegisterClassMap<WorkerMap>();

        csv.WriteHeader<Worker>();
        (workers.First().GetCustomerFieldExpando() as IDictionary<string, object>)
            .Keys.ToList().ForEach(key => csv.WriteField(key));
        csv.NextRecord();

        foreach (var worker in workers)
        
            csv.WriteRecord(worker);
            csv.WriteRecord(worker.GetCustomerFieldExpando());
            csv.NextRecord();
        

最后,CSV读取也可以结合ClassMapExpandoObject

        List<Worker> workers = new List<Worker>();

        csv.Configuration.HasHeaderRecord = true;
        csv.Configuration.RegisterClassMap<WorkerMap>();

        csv.Read();
        csv.ReadHeader();
        var columns = csv.Context.HeaderRecord;

        while (csv.Read())
        
            var worker = csv.GetRecord<Worker>();
            workers.Add(worker);

            ExpandoObject expando = csv.GetRecord<dynamic>();
            worker.SetCustomerField(expando);
        

在 CSV 读取的情况下,如果您想将真实类型读入字典值(不仅仅是字符串),事情会变得更加复杂。我们需要字典键和数据类型之间的预定义关联,以便能够从ExpandoObject 转换为正确的类型。

【讨论】:

以上是关于使用 csvHelper 动态创建列的主要内容,如果未能解决你的问题,请参考以下文章

使用 CsvHelper 将 CSV 文件中的编号列映射到数组

如何使用列设置动态创建角剑道网格列?

如何使用 codeigniter 动态创建列

如何为 Handsontable 创建动态列?

使用动态 sql 为 select 语句创建列

使用多维数组构建对象以动态创建列