使用 csvHelper 动态创建列
Dynamic creation of columns using csvHelper
我有一个工作人员,其中包含从服务器获取的各种字段。我正在使用 CSVHelper 包将此 class 转换为 excel sheet。
工人有这样的字段:
class Worker
{
string name;
string phone;
string age;
Dictionary<string,object> customerField;
}
我可以映射名字,phone,数字像
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 sheet,以便键(字符串)是另一个列名,值(对象)是该列的值。
CSVHelper是不是帮我们在运行时做的。我查看了文档。找不到适合我的东西。
我认为目前不支持编写字典。一方面,CsvHelper 很难知道 headers 要写什么。幸运的是,手动使用 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
字典键在列表中不一致,那么代码会稍微复杂一些,但仍然可以解决。
不支持字典,但支持 ExpandoObject。
如果您遵循上面的第一个 link,您会发现第 50 和 57 行使用了 WriteDynamicHeader 方法。
在扩展方法的帮助下,我为每条记录创建了一个 ExpandoObject,并使用 CsvHelper 编写了名为 document
的 object.The Dictionary<string, object>
参数,这是我希望创建的 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();
然后,根据上面 link 中 Josh 的测试,我们拥有了与 CsvHelper 相当无缝地使用字典所需的一切。我不认为这是比 Michael 更好的解决方案,只是一种不同的方法。
功劳归功于字典代码中的基本 ExpandoObject 来自这里(那里有更多的解释!)https://www.oreilly.com/learning/building-c-objects-dynamically
最近遇到了同样的问题,为了完整起见,发布了另一个答案。我们有相当复杂的 ClassMap<Worker>
像 class 并且确实想要松开它。此外,我们需要 CSV 写入和读取,因此还需要 robs 答案中的 ExpandoObject
。最终,这种方法就像迈克尔理查森和抢劫答案的结合,应该带来两全其美。
最重要的是,为了在读取过程中区分 CSV 文件中的字典字段,最好在它们前面加上类似 "customerField."
.
的前缀。
首先我们需要转换字典to/from 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 写入可以同时使用 ClassMap
和 ExpandoObject
,如下所示:
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读取也可以结合ClassMap
和ExpandoObject
:
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 包将此 class 转换为 excel sheet。 工人有这样的字段:
class Worker
{
string name;
string phone;
string age;
Dictionary<string,object> customerField;
}
我可以映射名字,phone,数字像
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 sheet,以便键(字符串)是另一个列名,值(对象)是该列的值。
CSVHelper是不是帮我们在运行时做的。我查看了文档。找不到适合我的东西。
我认为目前不支持编写字典。一方面,CsvHelper 很难知道 headers 要写什么。幸运的是,手动使用 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
字典键在列表中不一致,那么代码会稍微复杂一些,但仍然可以解决。
不支持字典,但支持 ExpandoObject。
如果您遵循上面的第一个 link,您会发现第 50 和 57 行使用了 WriteDynamicHeader 方法。
在扩展方法的帮助下,我为每条记录创建了一个 ExpandoObject,并使用 CsvHelper 编写了名为 document
的 object.The Dictionary<string, object>
参数,这是我希望创建的 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();
然后,根据上面 link 中 Josh 的测试,我们拥有了与 CsvHelper 相当无缝地使用字典所需的一切。我不认为这是比 Michael 更好的解决方案,只是一种不同的方法。
功劳归功于字典代码中的基本 ExpandoObject 来自这里(那里有更多的解释!)https://www.oreilly.com/learning/building-c-objects-dynamically
最近遇到了同样的问题,为了完整起见,发布了另一个答案。我们有相当复杂的 ClassMap<Worker>
像 class 并且确实想要松开它。此外,我们需要 CSV 写入和读取,因此还需要 robs 答案中的 ExpandoObject
。最终,这种方法就像迈克尔理查森和抢劫答案的结合,应该带来两全其美。
最重要的是,为了在读取过程中区分 CSV 文件中的字典字段,最好在它们前面加上类似 "customerField."
.
首先我们需要转换字典to/from 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 写入可以同时使用 ClassMap
和 ExpandoObject
,如下所示:
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读取也可以结合ClassMap
和ExpandoObject
:
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
.