如何使用CsvHelper导出指定列并保留属性定义的配置?
问题
现有一个通过特性定义配置的类SomeClass,以及一个可导出指定字段到CSV的方法GetAsCsvByteArray<T>。当传入的fields参数为null时,导出格式正确;但传入指定属性名数组(如["SomeInteger", "SomeBoolean"])时,属性上的特性配置丢失,导出格式异常。请问如何保留属性定义的配置?
相关代码
定义配置的类
public class SomeClass { public int SomeInteger { get; set; } public string SomeString { get; set; } public float SomeFloat { get; set; } [BooleanFalseValues("No")] [BooleanTrueValues("Yes")] public bool SomeBoolean { get; set; } [Format(@"#\%")] public decimal SomeDecimal { get; set; } [Format("dd-MM-yyyy HH:mm:ss")] public DateTime SomeDateTime { get; set; } }
导出方法
public static byte[] GetAsCsvByteArray<T>(IEnumerable<T> data, string[] fields) { var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture) { Delimiter = ";", ShouldQuote = _ => true }; using var memoryStream = new MemoryStream(); using var writer = new StreamWriter(memoryStream); using var csv = new CsvWriter(writer, csvConfig); if (fields?.Length > 0) { var csvMap = new DefaultClassMap<T>(); foreach (var field in fields) { var property = typeof(T).GetProperty(field); csvMap.Map(typeof(T), property); } csv.Context.RegisterClassMap(csvMap); } csv.WriteRecords(data); return memoryStream.ToArray(); }
解决方案
问题出在手动创建DefaultClassMap<T>并逐个添加属性映射的逻辑上——这种方式下CsvHelper不会自动读取属性上的特性配置,导致格式规则丢失。以下是两种可行的解决方式:
方案一:自动映射后筛选指定字段(推荐)
先让CsvHelper自动生成包含所有特性配置的映射规则,再移除不需要的字段,只保留指定的fields:
public static byte[] GetAsCsvByteArray<T>(IEnumerable<T> data, string[] fields) { var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture) { Delimiter = ";", ShouldQuote = _ => true }; using var memoryStream = new MemoryStream(); using var writer = new StreamWriter(memoryStream); using var csv = new CsvWriter(writer, csvConfig); if (fields?.Length > 0) { // 自动生成包含所有特性配置的映射 var csvMap = csv.Context.AutoMap<T>(); // 移除不在指定字段列表中的映射项 var unwantedMembers = csvMap.Members.Where(m => !fields.Contains(m.Member.Name)).ToList(); foreach (var member in unwantedMembers) { csvMap.Members.Remove(member); } // 重新注册修改后的映射 csv.Context.RegisterClassMap(csvMap); } csv.WriteRecords(data); return memoryStream.ToArray(); }
这种方式无需手动处理每种特性,AutoMap会自动识别并应用所有CsvHelper特性,同时能精准保留指定字段。
方案二:手动映射时逐个应用特性
如果需要更精细的控制,可以在手动添加属性映射后,读取属性上的特性并逐个配置到映射规则中:
public static byte[] GetAsCsvByteArray<T>(IEnumerable<T> data, string[] fields) { var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture) { Delimiter = ";", ShouldQuote = _ => true }; using var memoryStream = new MemoryStream(); using var writer = new StreamWriter(memoryStream); using var csv = new CsvWriter(writer, csvConfig); if (fields?.Length > 0) { var csvMap = new DefaultClassMap<T>(); foreach (var field in fields) { var property = typeof(T).GetProperty(field); if (property == null) continue; var memberMap = csvMap.Map(typeof(T), property); // 应用BooleanFalseValues特性 var falseAttr = property.GetCustomAttribute<BooleanFalseValuesAttribute>(); if (falseAttr != null) { memberMap.TypeConverterOption.BooleanFalseValues(falseAttr.Values); } // 应用BooleanTrueValues特性 var trueAttr = property.GetCustomAttribute<BooleanTrueValuesAttribute>(); if (trueAttr != null) { memberMap.TypeConverterOption.BooleanTrueValues(trueAttr.Values); } // 应用Format特性 var formatAttr = property.GetCustomAttribute<FormatAttribute>(); if (formatAttr != null) { memberMap.TypeConverterOption.Format(formatAttr.Format); } // 其他特性可按需添加 } csv.Context.RegisterClassMap(csvMap); } csv.WriteRecords(data); return memoryStream.ToArray(); }
这种方式需要针对每种特性编写处理逻辑,适合需要自定义部分规则的场景。
内容的提问来源于stack exchange,提问作者Niels R.
相关产品推荐
相关产品推荐

