我正在使用优秀的CsvHelper库(当前版本为v12.2.2)生成CSV文件,并尝试添加自定义属性来直接指定类中的特殊格式。
我要写入的记录看起来像这样(但必须按照集成所需的200个数字字段):
class PayrollRecord {
public int EmployeeID { get; set; }
public decimal RegularPay { get; set; }
public decimal RegularHours { get; set; }
public decimal RegularRate { get; set; }
public decimal OvertimePay { get; set; }
public decimal OvertimeHours { get; set; }
public decimal OvertimeRate { get; set; }
// many many more
}
我需要确保“Pay”显示为两位小数,“hours”显示为三位小数,“pay rate”显示为四位小数,这是所需的集成要求。
现有工作内容
我创建了一个十进制转换器并将其附加到类映射中:
using CsvHelper;
using CsvHelper.TypeConversion;
// convert decimal to the given number of places, and zeros are
// emitted as blank.
public abstract class MyDecimalConverter : DefaultTypeConverter
{
protected virtual string getFormat() => "";
public override string ConvertToString(object value, IWriterRow row, MemberMapData memberMapData)
{
if (value is decimal d)
return (d == 0) ? string.Empty : string.Format(getFormat(), d);
return base.ConvertToString(value, row, memberMapData);
}
}
public class DollarsConverter : MyDecimalConverter
{
protected override string getFormat() => "{0:0.00}"; // 2 decimal places
}
public class HoursConverter : MyDecimalConverter
{
protected override string getFormat() => "{0:0.000}"; // 3 decimal places
}
public class PayRateConverter : MyDecimalConverter
{
protected override string getFormat() => "{0:0.0000}"; // 4 decimal places
}
然后我在创建写入器时应用这些内容:
CsvWriter Writer = new CsvWriter( /* stuff */ );
var classMap = new DefaultClassMap<PayrollRecord>();
classMap.AutoMap();
classMap.Map(m => m.RegularPay).TypeConverter<DollarsConverter>();
classMap.Map(m => m.RegularHours).TypeConverter<HoursConverter>();
classMap.Map(m => m.RegularRate).TypeConverter<PayRateConverter>();
classMap.Map(m => m.OvertimePay).TypeConverter<DollarsConverter>();
classMap.Map(m => m.OvertimeHours).TypeConverter<HoursConverter>();
classMap.Map(m => m.OvertimeRate).TypeConverter<PayRateConverter>();
// many more
Writer.Configuration.RegisterClassMap(classMap);
...
这个程序可以正确地执行所有操作,但是它不太具备可扩展性:当字段数量达到200个时,同步映射信息和实际字段定义将会成为挑战,而且我非常希望记录结构在我们确定集成之前会发生变化。
顺便说一句:可以使用[Format("..")]
属性为每个字段注释,但为了获得我想要的零压缩效果,格式字符串需要有三部分,容易出错且非常繁琐。
我想要的是什么
我想创建自己的自定义属性,可以应用于每个字段成员以指定此内容,因此它将看起来像:
// custom attribute
public enum NumericType { Dollars, Hours, PayRate };
public class DecimalFormatAttribute : System.Attribute
{
public NumericType Type { get; }
public DecimalFormatAttribute(NumericType t) => Type = t;
}
// then later
class PayrollRecord {
[DecimalFormat(NumericType.Dollars)] public decimal RegularPay { get; set; }
[DecimalFormat(NumericType.Hours)] public decimal RegularHours { get; set; }
[DecimalFormat(NumericType.PayRate)] public decimal RegularRate { get; set; }
// etc.
}
我卡住的地方是如何将我的自定义属性粘贴到类映射中,我认为代码应该像这样:
var classMap = new DefaultClassMap<PayrollRecord>();
classMap.AutoMap();
foreach (var prop in typeof(PayrollRecord).GetProperties())
{
var myattr = (DecimalFormatAttribute)prop.GetCustomAttribute(typeof(DecimalFormatAttribute));
if (myattr != null)
{
// prop.Name is the base name of the field
// WHAT GOES HERE?
}
}
我已经花费了几个小时来探索这个问题,但是仍然不知道如何完成它。
CsvHelper.Configuration.Attributes.TypeConverterAttribute
应用于您的模型,而不是使用自定义属性?请参见 https://dotnetfiddle.net/m2nORw。 - dbc[Name("Regular Pay")]
属性来完成它们。 - Steve Friedl