65.9K
CodeProject 正在变化。 阅读更多。
Home

Cinchoo ETL - CSV 写入器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (12投票s)

2016 年 12 月 20 日

CPOL

29分钟阅读

viewsIcon

61920

.NET 的简单 CSV 文件写入器

目录

  1. 引言
  2. 要求
  3. “Hello World!”示例
    1. 快速写入 - 数据优先方法
    2. 代码优先方法
    3. 配置优先方法
  4. 写入所有记录
  5. 手动写入记录
  6. 自定义 CSV 记录
  7. 自定义 CSV 标题
  8. 自定义 CSV 字段
    1. 默认值
    2. ChoFallbackValue
    3. 类型转换器
    4. 验证
  9. Excel 字段分隔符
  10. 回调机制
    1. BeginWrite
    2. EndWrite
    3. BeforeRecordWrite
    4. AfterRecordWrite
    5. RecordWriteError
    6. BeforeRecordFieldWrite
    7. AfterRecordFieldWrite
    8. RecordWriteFieldError
  11. 自定义
  12. 使用动态对象
  13. 异常
  14. 技巧
  15. 使用 MetadataType 注解
  16. 配置选项
    1. 手动配置
    2. 自动映射配置
    3. 附加 MetadataType 类
  17. ToText 辅助方法
  18. Writing DataReader 辅助方法
  19. Writing DataTable 辅助方法
  20. 高级主题 (Advanced Topics)
    1. 覆盖转换器格式规范
    2. 货币支持
    3. 枚举支持
    4. 布尔值支持
    5. 日期时间支持
  21. Fluent API
    1. WithDelimiter
    2. WithFirstLineHeader
    3. WithFields
    4. WithField
    5. QuoteAllFields
  22. 历史

1. 引言

ChoETL 是一个开源的 .NET ETL (提取、转换和加载) 框架。它是一个基于代码的库,用于从多个源提取数据,并在 .NET 环境中将其转换为您的数据仓库。您可以快速地将数据加载到数据仓库中。

本文介绍了使用 ChoETL 框架提供的 CSVRWriter 组件。它是一个简单的实用类,用于将 CSV 数据保存到文件中。

相应的 CSVReader 文章可以在此处找到。

特点

  • 遵循 CSV 标准文件规则。优雅地处理包含逗号和换行符的数据字段。
  • 除了逗号,还可以使用大多数分隔字符,包括制表符分隔的字段。
  • 生成文件时支持特定文化的日期、货币和数字格式。
  • 支持不同的字符编码。
  • 在写入文件时提供对日期、货币、枚举、布尔值、数字格式的精细控制。
  • 详细而健壮的错误处理,让您能够快速查找和修复问题。
  • 缩短您的开发时间。

2. 要求

此框架库使用 C# 编写,基于 .NET 4.5 Framework / .NET Core 2.x。

3. “Hello World!” 示例

  • 打开 VS.NET 2017 或更高版本。
  • 创建一个示例 VS.NET (.NET Framework 4.x / .NET Core 2.x) 控制台应用程序项目。
  • 根据正在工作的 .NET 版本,使用 Nuget 命令通过程序包管理器控制台安装 ChoETL
    • Install-Package ChoETL
    • Install-Package ChoETL.NETStandard
  • 使用 ChoETL 命名空间。

让我们从生成下面这个包含 2 列的 CSV 文件的简单示例开始。

清单 3.1 示例 CSV 数据文件 (Emp.csv)

1,Tom
2,Carl
3,Mark

有多种方法可以以最少的设置创建 CSV 文件。

3.1. 快速写入 - 数据优先方法

这是零配置且最快的 CSV 文件创建方法。无需类型化的 POCO 对象。下面的示例代码演示了如何使用动态对象生成示例 CSV 文件。

列表 3.1.1 将对象列表写入 CSV 文件

List<ExpandoObject> objs = new List<ExpandoObject>();
dynamic rec1 = new ExpandoObject();
rec1.Id = 1;
rec1.Name = "Mark";
objs.Add(rec1);
 
dynamic rec2 = new ExpandoObject();
rec2.Id = 2;
rec2.Name = "Jason";
objs.Add(rec2);
 
using (var parser = new ChoCSVWriter("Emp.csv").WithFirstLineHeader())
{
    parser.Write(objs);
}

在上面的示例中,我们在一次传递中将动态对象列表提供给 CSVWriter,以便将它们写入 CSV 文件。

示例 fiddle: https://dotnetfiddle.net/PZLBAg 

列表 3.1.2 将每个对象写入 CSV 文件

using (var parser = new ChoCSVWriter("Emp.csv").WithFirstLineHeader())
{
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 1;
    rec1.Name = "Mark";
    parser.Write(rec1);

    dynamic rec2 = new ExpandoObject();
    rec2.Id = 2;
    rec2.Name = "Jason";
    parser.Write(rec2);
}

在上面的示例中,我们通过使用 Write 重载来控制构造和传递每个单独的动态记录给 CSVWriter 来生成 CSV 文件。

示例 fiddle: https://dotnetfiddle.net/zWnFtk

3.2. 代码优先方法

这是另一种零配置的 CSV 文件生成方法,使用类型化的 POCO 类。首先,定义一个简单的 POCO 类来匹配底层的 CSV 文件布局

清单 3.2.1 简单的 POCO 实体类

public partial class EmployeeRecSimple
{
    public int Id { get; set; }
    public string Name { get; set; } 
}

在上面,POCO 类定义了两个属性,与示例 CSV 文件模板匹配。

列表 3.2.2 保存到 CSV 文件

List<EmployeeRecSimple> objs = new List<EmployeeRecSimple>();

EmployeeRecSimple rec1 = new EmployeeRecSimple();
rec1.Id = 1;
rec1.Name = "Mark";
objs.Add(rec1);
 
EmployeeRecSimple rec2 = new EmployeeRecSimple();
rec2.Id = 2;
rec2.Name = "Jason";
objs.Add(rec2);
 
using (var parser = new ChoCSVWriter<EmployeeRecSimple>("Emp.csv").WithFirstLineHeader())
{
    parser.Write(objs);
}

上面的示例代码演示了如何从类型化的 POCO 类对象创建 CSV 文件。

示例 fiddle: https://dotnetfiddle.net/gQoQq4

3.3. 配置优先方法

在此模型中,我们定义了 CSV 配置以及所有必需的参数以及生成示例 CSV 文件所需的 CSV 列。

清单 3.3.1 定义 CSV 配置

ChoCSVRecordConfiguration config = new ChoCSVRecordConfiguration();
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Id", 1));
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name", 2));

在上面,该类定义了两个 CSV 属性,与示例 CSV 文件模板匹配。

列表 3.3.2 不带 POCO 对象生成 CSV 文件

List<ExpandoObject> objs = new List<ExpandoObject>();

dynamic rec1 = new ExpandoObject();
rec1.Id = 1;
rec1.Name = "Mark";
objs.Add(rec1);
 
dynamic rec2 = new ExpandoObject();
rec2.Id = 2;
rec2.Name = "Tom";
objs.Add(rec2);
 
using (var parser = new ChoCSVWriter("Emp.csv", config)WithFirstLineHeader())
{
    parser.Write(objs);
}

上面的示例代码展示了如何使用预定义的 CSV 配置设置从动态对象列表中生成 CSV 文件。在 CSVWriter 构造函数中,我们指定了 CSV 配置对象,以便在创建文件时遵守 CSV 布局模式。如果 CSV 列的名称或计数有任何不匹配,它将被报告为错误并停止写入过程。

示例 fiddle: https://dotnetfiddle.net/xeC0ww

列表 3.3.3 使用 POCO 对象保存 CSV 文件

List<EmployeeRecSimple> objs = new List<EmployeeRecSimple>();

EmployeeRecSimple rec1 = new EmployeeRecSimple();
rec1.Id = 1;
rec1.Name = "Mark";
objs.Add(rec1);
 
EmployeeRecSimple rec2 = new EmployeeRecSimple();
rec2.Id = 2;
rec2.Name = "Jason";
objs.Add(rec2);
 
using (var parser = new ChoCSVWriter<EmployeeRecSimple>("Emp.csv", config))
{
    parser.Write(objs);
}

上面的示例代码展示了如何使用 CSV 配置对象从 POCO 对象列表中生成 CSV 文件。在 CSVWriter 构造函数中,我们指定了 CSV 配置对象。

3.4. 代码优先声明式配置

这是将 POCO 实体类与 CSV 配置参数声明式附加的组合方法。id 是必需的列,name 是可选的值列,默认值为 "XXXX"。 如果 name 不存在,它将取默认值。

清单 3.4.1 定义 POCO 对象

public class EmployeeRec
{
    [ChoCSVRecordField(1)]
    [Required]
    public int? Id
    {
        get;
        set;
    }

    [ChoCSVRecordField(2)]
    [DefaultValue("XXXX")]
    public string Name
    {
        get;
        set;
    }

    public override string ToString()
    {
        return "{0}. {1}.".FormatString(Id, Name);
    }
}

上面的代码说明了使用 nessasary 属性定义 POCO 对象以生成 CSV 文件。第一件事是为每个记录字段定义属性,并使用 ChoCSVRecordFieldAttribute 来限定 CSV 记录映射。每个属性都必须指定position才能映射到 CSV 列。位置是从 1 开始的。Id 是一个必需的属性。我们用RequiredAttribute装饰了它。Name 使用DefaultValueAttribute 赋予了默认值。这意味着如果对象中未设置Name值,CSVWriter会将默认值 'XXXX' 写入文件。

它非常简单,可以立即保存 CSV 数据。

列表 3.4.2 使用 POCO 对象保存 CSV 文件

List<EmployeeRec> objs = new List<EmployeeRec>();

EmployeeRec rec1 = new EmployeeRec();
rec1.Id = 10;
rec1.Name = "Mark";
objs.Add(rec1);
 
EmployeeRec rec2 = new EmployeeRec();
rec2.Id = 200;
rec2.Name = "Lou";
objs.Add(rec2);
 
using (var parser = new ChoCSVWriter<EmployeeRec>("Emp.csv"))
{
    parser.Write(objs);
}

我们首先创建一个 ChoCSVWriter 对象的新实例。仅此而已! 生成对象 CSV 数据的所有繁重工作都由 writer 在后台完成。

默认情况下,CSVWriter 在保存 CSV 文件时会发现并使用默认配置参数。这些可以根据您的需要进行覆盖。以下部分将为您提供每个配置属性的详细信息。

示例 fiddle: https://dotnetfiddle.net/3iOhib

4. 写入所有记录

这就像设置与 CSV 文件结构匹配的 POCO 对象,构造对象列表,然后将其传递给 CSVWriterWrite 方法一样简单。这将在一次调用中将整个对象列表写入 CSV 文件。

列表 4.1 写入 CSV 文件

List<EmployeeRec> objs = new List<EmployeeRec>();
//Construct and attach objects to this list
...

using (var parser = new ChoCSVWriter<EmployeeRec>("Emp.csv"))
{
    parser.Write(objs);
}

列表 4.2 写入 CSV 文件流

List<EmployeeRec> objs = new List<EmployeeRec>();
//Construct and attach objects to this list
...

using (var tx = File.OpenWrite("Emp.csv"))
{
    using (var parser = new ChoCSVWriter<EmployeeRec>(tx))
    {
        parser.Write(objs);
    }
}

这种模型使您的代码优雅、整洁、易于阅读和维护。

5. 手动写入记录

在 POCO 对象以断开连接的方式构造的情况下,这是将每个单独的记录写入 CSV 文件的另一种方法。

列表 5.1 写入 CSV 文件

var writer = new ChoCSVWriter<EmployeeRec>("Emp.csv");

EmployeeRec rec1 = new EmployeeRec();
rec1.Id = 10;
rec1.Name = "Mark";
 
writer.Write(rec1);

EmployeeRec rec2 = new EmployeeRec();
rec2.Id = 11;
rec2.Name = "Tom"; 

writer.Write(rec2);

示例 fiddle: https://dotnetfiddle.net/fgTV2y

5a. 写入自定义记录

在极少数情况下,您可能希望将自定义行写入 CSV 文件(例如,页脚行)。这可以通过使用 WriteFields() 方法来完成。

using (var w = new ChoCSVWriter(Console.Out)
    .WithFirstLineHeader()
    .WithField("Id")
    .WithField("Name")
    )
{

    dynamic rec = new ExpandoObject();
    rec.Id = 10;
    rec.Name = "Mark";

    w.Write(rec);
    w.WriteFields("RecordCount", 3); //Write CSV footer
}

在上面的示例中,在将所有记录写入 CSV 文件后,它使用 WriteFields() 方法将 CSV 页脚行(RecordCount, 1)写入文件末尾。

示例 fiddle: https://dotnetfiddle.net/1QG21M

5b. 写入注释

通常,所有 CSV 文件都是以数据为中心的。很少的 CSV 文件也可能包含注释。CSVWriter 提供了使用 WriteComment() 方法写入注释的方法。要使用它,您必须使用配置设置注释字符才能写入。否则,调用将引发异常。

using (var w = new ChoCSVWriter(Console.Out)
    .WithFirstLineHeader()
    .WithField("Id")
    .WithField("Name")
    .Configure(c => c.Comment = "#")
    )
{
    w.WriteComment("CSV Comment Line");
    w.Write(rec);
}

示例 fiddle: https://dotnetfiddle.net/EnZey3

6. 自定义 CSV 记录

使用 ChoCSVRecordObjectAttribute,您可以声明性地自定义 POCO 实体对象。

清单 6.1 为每个记录自定义 POCO 对象

[ChoCSVRecordObject(Encoding = "Encoding.UTF32", 
ErrorMode = ChoErrorMode.IgnoreAndContinue, 
            IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All)]
public class EmployeeRec
{
    [ChoCSVRecordField(1, FieldName = "id")]
    public int Id { get; set; }
    [ChoCSVRecordField(2, FieldName ="Name", QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}

以下是用于执行 CSV 加载操作定制的可用属性。

  • Delimiter - CSV 行中用于分隔字段的值。默认为 Culture.TextInfo.ListSeparator 用法。
  • EOLDelimiter - 用于分隔 CSV 行的值。默认为 \r\n (NewLine)。
  • Culture - 用于读取和写入的文化信息。
  • IgnoreEmptyLine - 不适用
  • Comments - 不适用
  • QuoteChar - 用于转义包含分隔符、引号或行结尾的字段的值。
  • QuoteAllFields - 一个标志,告诉 writer 是否应在所有写入的字段周围加上引号;无论字段是否包含任何需要转义的内容。
  • Encoding - CSV 文件的编码。
  • HasExcelSeperator - 一个标志,告诉 writer 在输出文件中输出 Excel 分隔符信息。
  • ColumnCountStrict - 此标志指示是否应在 CSV 字段配置与数据对象成员不匹配时抛出异常。
  • ColumnOrderStrict - 不适用
  • BufferSize - 当 reader 来自 StreamWriter 时使用的内部缓冲区大小。
  • NullValue- 特殊的 null 值文本,期望在记录级别被视为 CSV 文件中的 null 值。
  • ErrorMode - 此标志指示在写入过程中是否应抛出异常,并且预期的字段未能写入。可以按属性覆盖。可能的值是
    • IgnoreAndContinue - 忽略错误,跳过记录并继续处理下一条。
    • ReportAndContinue - 如果 POCO 实体是 IChoNotifyRecordWrite 类型,则向其报告错误
    • ThrowAndStop - 抛出错误并停止执行
  • IgnoreFieldValueMode - 不适用
  • ObjectValidationMode - 一个标志,告知读取器关于记录对象要执行的验证类型。可能的值是
    • Off - 不执行对象验证。(默认)
    • MemberLevel - 在每个 CSV 属性写入文件之前执行验证。
    • ObjectLevel - 在所有 POCO 属性写入文件之前执行验证。

7. 自定义 CSV 标题

通过声明式地将 ChoCSVFileHeaderAttribute 附加到 POCO 实体对象,您可以在创建 CSV 文件时影响 writer 生成 CSV 标题。

清单 6.1 为文件标题自定义 POCO 对象

[ChoCSVFileHeader]
public class EmployeeRec
{
    [ChoCSVRecordField(1, FieldName = "id")]
    public int Id { get; set; }
    [ChoCSVRecordField(2, FieldName ="Name", QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}

以下是您可以根据需要添加一些自定义的可用成员。

  • FillChar - 当 CSV 列标题的大小小于列大小 (ChoCSVRecordFieldAttribute.SizeChoCSVRecordFieldConfiguration.Size) 时使用的填充字符。默认是 '\0',将关闭填充。
  • Justification - 列标题对齐。默认是 Left
  • TrimOption - 不适用
  • Truncate - 此标志指示 writer 是否应截断 CSV 列标题值,如果它超过列大小。默认是 false

7.1 覆盖 CSV 标题

通常,CSVWriter 会自动从对象生成标题。很少情况下,您可能希望用自己的文本覆盖标题。ChoCSVWriter 公开了回调事件,您可以在其中订阅以按照您想要的方式生成 CSV 标题。示例文代码如下所示

using (var w = new ChoCSVWriter<Site>(new StringWriter(csv))
    .WithFirstLineHeader()
    .Setup(s => s.FileHeaderWrite += (o, e) =>
    {
        e.HeaderText = "ID, House";
    })
    )
{
    w.Write(site);
}

这里还有一种简单的方法可以通过使用 WriteHeader() 方法将标题写入 CSV 文件

using (var w = new ChoCSVWriter<Site>(new StringWriter(csv))
    .WithFirstLineHeader()
    )
{
    w.WriteHeader("ID", "House");
    w.Write(site);
}

8. 自定义 CSV 字段

对于每个 CSV 列,您可以使用 ChoCSVRecordFieldAttribute 在 POCO 实体属性中指定映射。

清单 6.1 为 CSV 列自定义 POCO 对象

[ChoCSVFileHeader]
public class EmployeeRec
{
    [ChoCSVRecordField(1, FieldName = "id")]
    public int Id { get; set; }
    [ChoCSVRecordField(2, FieldName ="Name", QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}

以下是您可以为每个属性添加自定义的可用成员

  • FieldPosition - 按位置映射时,指定要用于该属性的 CSV 列的索引。它是从 1 开始的。
  • FieldName - CSV 列名标题。如果未指定,则使用 POCO 对象属性名作为列标题。
  • FillChar - 当 CSV 列值的大小小于列大小时使用的填充字符。默认是 '\0',将关闭填充。
  • FieldValueJustification - 列值对齐。默认是 Left
  • FieldValueTrimOption - 不适用
  • Truncate - 此标志指示 writer 是否应截断 CSV 列值,如果它超过列大小时。默认是 false
  • Size - CSV 列值的大小。
  • QuoteField - 一个标志,告诉 writer CSV 列值被引号包围。
  • NullValue- 特殊的 null 值文本,期望在字段级别被视为 CSV 文件中的 null 值。
  • ErrorMode - 此标志指示在写入过程中是否应抛出异常,并且预期的字段未能转换和写入。可能的值是
    • IgnoreAndContinue - 忽略错误,继续加载记录的其他属性。
    • ReportAndContinue - 如果 POCO 实体是 IChoRecord 类型,则向其报告错误。
    • ThrowAndStop - 抛出错误并停止执行。
  • IgnoreFieldValueMode - 不适用

8.1. DefaultValue

任何 POCO 实体属性都可以使用 System.ComponentModel.DefaultValueAttribute 指定默认值。这是在 CSV 值 null (由 IgnoreFieldValueMode 控制) 时用于写入的值。

8.2. ChoFallbackValue

任何 POCO 实体属性都可以使用 ChoETL.ChoFallbackValueAttribute 指定备用值。这是在属性写入 CSV 失败时使用的值。Fallback 值仅在 ErrorModeIgnoreAndContinueReportAndContinue 时设置。

8.3. 类型转换器

大多数基本类型会自动转换为字符串/文本并保存到 CSV 文件。如果 CSV 字段的值未自动转换为文本值,您可以指定自定义/内置 .NET 转换器将值转换为文本。这些可以是 IValueConverter, IChoValueConverterTypeConverter 转换器。

有几种方法可以为每个字段指定转换器

  • 声明式方法
  • 配置方法

8.3.1. 声明式方法

此模型适用于 POCO 实体对象。如果您有 POCO 类,您可以为每个属性指定转换器来执行必要的转换。下面的示例展示了如何进行操作。

清单 8.3.1.1 指定类型转换器

[ChoCSVFileHeader]
public class EmployeeRec
{
    [ChoCSVRecordField(1, FieldName = "id")]
    [ChoTypeConverter(typeof(IntConverter))]
    public int Id { get; set; }
    [ChoCSVRecordField(2, FieldName ="Name", QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}

列表 8.3.1.2 IntConverter 实现

public class IntConverter : IValueConverter
{
    public object Convert
    (object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }
 
    public object ConvertBack
    (object value, Type targetType, object parameter, CultureInfo culture)
    {
        int intValue = (int)value;
        return intValue.ToString("D4");
    }
}

在上面的示例中,我们定义了自定义 IntConverter 类。并展示了如何使用前导零格式化 'Id' CSV 属性。

8.3.2. 配置式方法

此模型适用于动态和 POCO 实体对象。这使得可以在运行时将转换器附加到每个属性。这优先于 POCO 类上的声明式转换器。

列表 8.3.2.1 指定 TypeConverters

ChoCSVRecordConfiguration config = new ChoCSVRecordConfiguration();
config.FileHeaderConfiguration.HasHeaderRecord = true;
config.ThrowAndStopOnMissingField = false;

ChoCSVRecordFieldConfiguration idConfig = new ChoCSVRecordFieldConfiguration("Id", 1);
idConfig.AddConverter(new IntConverter());
config.CSVRecordFieldConfigurations.Add(idConfig);

config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name", 2));
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name1", 2));

在上面,我们使用 AddConverter 辅助方法在 ChoCSVRecordFieldConfiguration 对象中构造并附加 IntConverter 到 'Id' 字段。

同样,如果您想从中删除任何转换器,您可以使用 ChoCSVRecordFieldConfiguration 对象上的 RemoveConverter

8.3.3. 自定义值转换器方法

此方法允许使用 Fluent API 将值转换器附加到每个 CSV 成员。这是一种快速处理任何奇怪转换过程并避免创建值转换器类的方法。

列表 8.3.3.1 POCO 类

[ChoCSVFileHeader]
public class EmployeeRec
{
    [ChoCSVRecordField(1, FieldName = "id")]
    public int Id { get; set; }
    [ChoCSVRecordField(2, FieldName ="Name", QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}

使用 Fluent API,下面的示例展示了如何将值转换器附加到 Id 列。

列表 8.3.3.2 附加值转换器

using (var dr = new ChoCSVWriter<EmployeeRec>(@"Test.csv")
    .WithFirstLineHeader()
    .WithField(c => c.Id, valueConverter: (v) => 
     ((int)value).ToString("C3", CultureInfo.CurrentCulture))
    )
{
    Console.WriteLine(rec);
}

8.4. 验证

CSVWriter 利用 System.ComponentModel.DataAnnotationsValidation Block 验证属性来指定 POCO 实体各个字段的验证规则。有关可用 DataAnnotations 验证属性的列表,请参阅 MSDN 网站。

清单 8.4.1 在 POCO 实体中使用验证属性

[ChoCSVFileHeader]
[ChoCSVRecordObject(Encoding = "Encoding.UTF32", ErrorMode = ChoErrorMode.IgnoreAndContinue,
       IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, 
       ThrowAndStopOnMissingField = false)]
public partial class EmployeeRec
{
    [ChoCSVRecordField(1, FieldName = "id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
 
    [ChoCSVRecordField(2, FieldName = "Name")]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
}

在上面的示例中,对 Id 属性使用了 Range 验证属性。对 Name 属性使用了 Required 验证属性。当 Configuration.ObjectValidationMode 设置为 ChoObjectValidationMode.MemberLevelChoObjectValidationMode.ObjectLevel 时,CSVWriter 在将数据保存到文件之前会对其进行验证。

在某些情况下,您可能希望自己控制并在 POCO 实体类中执行手动 **自验证**。这可以通过继承 POCO 对象实现 IChoValidatable 接口来完成。

列表 8.4.2 POCO 实体的手动验证

[ChoCSVFileHeader]
[ChoCSVRecordObject(Encoding = "Encoding.UTF32", 
                    ErrorMode = ChoErrorMode.IgnoreAndContinue,
                    IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, 
                    ThrowAndStopOnMissingField = false)]
public partial class EmployeeRec : IChoValidatable
{
    [ChoCSVRecordField(1, FieldName = "id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
 
    [ChoCSVRecordField(2, FieldName = "Name")]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
 
    public bool TryValidate(object target, 
                            ICollection<ValidationResult> validationResults)
    {
        return true;
    }
 
    public bool TryValidateFor
    (object target, string memberName, ICollection<ValidationResult> validationResults)
    {
        return true;
    }
}

上面的示例展示了如何在 POCO 对象中实现自定义自验证。

IChoValidatable 接口公开了以下方法

  • TryValidate - 验证整个对象,如果所有验证都通过则返回 true。否则返回 false
  • TryValidateFor - 验证对象的特定属性,如果所有验证都通过则返回 true。否则,返回 false

8.5. ChoIgnoreMember

如果您想在 OptOut 模式下忽略 POCO 类成员的 CSV 解析,请用 ChoIgnoreMemberAttribute 装饰它们。下面的示例显示 Title 成员被忽略了 CSV 加载过程。

清单 8.5.1 忽略成员

[ChoCSVFileHeader]
public class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; }
    [ChoIgnoreMember]
    public string Title { get; set; }
}

8.6. StringLength

OptOut 模式下,您可以使用 System.ComponentModel.DataAnnotations.StringLengthAttribute 来指定 CSV 列的大小。

清单 8.6.1 指定 CSV 成员的大小

[ChoCSVFileHeader]
public class EmployeeRec
{
    public int Id { get; set; }
    [StringLength(25)]
    public string Name { get; set; }
    [ChoIgnoreMember]
    public string Title { get; set; }
}

8.6. 显示

OptOut 模式下,您可以使用 System.ComponentModel.DataAnnotations.DisplayAttribute 来指定映射到成员的 CSV 列的名称。

列表 8.6.1 指定 CSV 列的名称

[ChoCSVFileHeader]
public class EmployeeRec
{
    public int Id { get; set; }
    [Display(Name="FullName")]
    [StringLength(25)]
    public string Name { get; set; }
    [ChoIgnoreMember]
    public string Title { get; set; }
}

8.7. DisplayName

OptOut 模式下,您可以使用 System.ComponentModel.DataAnnotations.DisplayNameAttribute. 来指定映射到成员的 CSV 列的名称。

清单 8.7.1 指定 CSV 列的名称

[ChoCSVFileHeader]
public class EmployeeRec
{
    public int Id { get; set; }
    [Display(Name="FullName")]
    [StringLength(25)]
    public string Name { get; set; }
    [ChoIgnoreMember]
    public string Title { get; set; }
}

9. Excel 字段分隔符

通过声明式地在 POCO 对象上设置 HasExcelSeperator 或将 ChoCSVRecordConfiguration.HasExcelSeperator 设置为 true,可以在数据文件中生成 Excel 字段分隔符。

列表 9.1 声明式地将 HasExcelSeperator 指定给 POCO 对象

[ChoCSVFileHeader]
[ChoCSVRecordObject(HasExcelSeparator = true)]
public class EmployeeRec
{
    [ChoCSVRecordField(1)]
    [Required]
    [ChoFallbackValue(100)]
    [Range(100, 10000)]
    public int? Id
    {
        get;
        set;
    }
    [ChoCSVRecordField(2)]
    [DefaultValue("XXXX")]
    public string Name
    {
        get;
        set;
    }
 
    public override string ToString()
    {
        return "{0}. {1}.".FormatString(Id, Name);
    }
}

列表 9.2 通过配置指定 HasExcelSeperator

ChoCSVRecordConfiguration config = new ChoCSVRecordConfiguration();
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Id", 1));
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name", 2));
config.HasExcelSeparator = true;
 
List<EmployeeRecSimple> objs = new List<EmployeeRecSimple>();
EmployeeRecSimple rec1 = new EmployeeRecSimple();
rec1.Id = 1;
rec1.Name = "Mark";
objs.Add(rec1);
 
EmployeeRecSimple rec2 = new EmployeeRecSimple();
rec2.Id = 2;
rec2.Name = "Jason";
objs.Add(rec2);
 
using (var parser = new ChoCSVWriter<EmployeeRecSimple>("Emp.csv", config))
{
    parser.Write(objs);
}

列表 9.3 带有 Excel 字段分隔符的示例 CSV 文件

sep=,
1,Mark 
2,Jason

10. 回调机制

CSVWriter 提供开箱即用的行业标准 CSV 数据文件生成功能,以满足大多数需求。 如果生成过程不满足您的任何需求,您可以使用 CSVWriter 提供的回调机制来处理这种情况。为了参与回调机制,您可以使用以下任一模型

  • 使用 CSVWriter 通过 IChoWriter 接口公开的事件处理程序。
  • 使 POCO 实体对象继承自 IChoNotifyRecordWrite / IChoNotifyFileWrite / IChoNotifyRecordFieldWrite 接口
  • 使 DataAnnotationMetadataType 类型对象继承 IChoNotifyRecordWrite / IChoNotifyFileWrite / IChoNotifyRecordFieldWrite 接口。

为了参与回调机制,POCO 实体对象或 DataAnnotationMetadataType 类型对象必须继承 IChoNotifyRecordWrite 接口。

提示:从此接口方法中引发的任何异常都将被忽略。

IChoRecorder 公开以下方法

  • BeginWrite - 在 CSV 文件写入开始时调用
  • EndWrite - 在 CSV 文件写入结束时调用
  • BeforeRecordWrite - 在写入 CSV 记录之前触发
  • AfterRecordWrite - 在写入 CSV 记录之后触发
  • RecordWriteError - 在写入 CSV 记录时发生错误时触发
  • BeforeRecordFieldWrite - 在写入 CSV 列值之前触发
  • AfterRecordFieldWrite - 在写入 CSV 列值之后触发
  • RecordFieldWriteError - 在写入 CSV 列值时发生错误时触发

IChoNotifyRecordWrite 公开以下方法

  • BeforeRecordWrite - 在写入 CSV 记录之前触发
  • AfterRecordWrite - 在写入 CSV 记录之后触发
  • RecordWriteError - 在写入 CSV 记录时发生错误时触发

IChoNotifyFileWrite 公开以下方法

  • BeginWrite - 在 CSV 文件写入开始时调用
  • EndWrite - 在 CSV 文件写入结束时调用

IChoNotifyRecordFieldWrite 公开以下方法

  • BeforeRecordFieldWrite - 在写入 CSV 列值之前触发
  • AfterRecordFieldWrite - 在写入 CSV 列值之后触发
  • RecordFieldWriteError - 在写入 CSV 列值时发生错误时触发

IChoNotifyFileHeaderArrange 公开以下方法

  • FileHeaderArrange - 在将 CSV 文件标题写入文件之前触发,有机会重新排列 CSV 列

IChoNotifyFileHeaderWrite 公开以下方法

  • FileHeaderWrite - 在将 CSV 文件标题写入文件之前触发,有机会自定义标题。

10.1 使用 CSVWriter 事件

这是最直接、最简单的方法来订阅回调事件并处理 CSV 文件解析中的特殊情况。缺点是代码无法像通过使用 POCO 记录对象实现 IChoNotifyRecordRead 那样可重用。

下面的示例展示了如何使用 BeforeRecordLoad 回调方法来跳过以 '%' 字符开头的行。

列表 10.1.1 使用 CSVWriter 回调事件

static void IgnoreLineTest()
{
    using (var parser = new ChoCSVWriter("IgnoreLineFile.csv").WithFirstLineHeader())
    {
        parser.Configuration.Encoding = Encoding.BigEndianUnicode;
 
        parser.BeforeRecordWrite += (o, e) =>
        {
            if (e.Source != null)
            {
                e.Skip = ((string)e.Source).StartsWith("%");
            }
        };
        
        parser.Write(rec);
    }
}

同样,您也可以使用 CSVWriter 的其他回调方法。

10.2 实现 IChoNotifyRecordWrite 接口

下面的示例演示了如何实现 IChoNotifyRecordWrite 接口来直接处理 POCO 类。

清单 10.2.1 直接 POCO 回调机制实现

[ChoCSVFileHeader]
[ChoCSVRecordObject(Encoding = "Encoding.UTF32", ErrorMode = ChoErrorMode.IgnoreAndContinue,
       IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, ThrowAndStopOnMissingField = false)]
public partial class EmployeeRec : IChoNotifyrRecordWrite
{
    [ChoCSVRecordField(1, FieldName = "id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
    
    [ChoCSVRecordField(2, FieldName = "Name", QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }

    public bool AfterRecordWrite(object target, int index, object source)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordWrite(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordWriteError(object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }
}

下面的示例展示了如何通过在 POCO 类上使用 MetadataTypeAttribute 来附加 Metadata 类。

列表 10.2.2 基于 MetaDataType 的回调机制实现

[ChoCSVFileHeader]
[ChoCSVRecordObject(Encoding = "Encoding.UTF32", ErrorMode = ChoErrorMode.IgnoreAndContinue,
       IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, ThrowAndStopOnMissingField = false)]
public class EmployeeRecMeta : IChoNotifyRecordWrite
{
    [ChoCSVRecordField(1, FieldName = "id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }

    [ChoCSVRecordField(2, FieldName = "Name", QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }

    public bool AfterRecordWrite(object target, int index, object source)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordWrite(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordWriteError(object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }
} 

[MetadataType(typeof(EmployeeRecMeta))]
public partial class EmployeeRec
{
    [ChoCSVRecordField(1, FieldName = "id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
    
    [ChoCSVRecordField(2, FieldName = "Name", QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
}

下面的示例展示了如何通过在 POCO 类上使用 ChoMetaDataRefTypeAttribute 来为密封类或第三方 POCO 类附加 Metadata 类。

列表 10.2.2 基于 MetaDataType 的回调机制实现

[ChoMetadataRefType(typeof(EmployeeRec))]
[ChoCSVFileHeader]
[ChoCSVRecordObject(Encoding = "Encoding.UTF32", ErrorMode = ChoErrorMode.IgnoreAndContinue,
       IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, ThrowAndStopOnMissingField = false)]
public class EmployeeRecMeta : IChoNotifyRecordWrite
{
    [ChoCSVRecordField(1, FieldName = "id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }

    [ChoCSVRecordField(2, FieldName = "Name", QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }

    public bool AfterRecordWrite(object target, int index, object source)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordWrite(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordWriteError(object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }
} 

public partial class EmployeeRec
{
    [ChoCSVRecordField(1, FieldName = "id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
    
    [ChoCSVRecordField(2, FieldName = "Name", QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
}

10.1 BeginWrite

此回调在 CSV 文件写入的开始时调用一次。source 是 CSV 文件流对象。在这里,您有机会检查流,返回 true 以继续 CSV 生成。返回 false 以停止生成。

列表 10.1.1 BeginWrite 回调示例

public bool BeginWrite(object source)
{
    StreamReader sr = source as StreamReader;
    return true;
}

10.2 EndWrite

此回调在 CSV 文件生成结束时调用一次。source 是 CSV 文件流对象。在这里,您有机会检查流,对流执行任何后续步骤。

列表 10.2.1 EndWrite 回调示例

public void EndWrite(object source)
{
    StreamReader sr = source as StreamReader;
}

10.3 BeforeRecordWrite

此回调在每个 POCO 记录对象写入 CSV 文件之前调用。target 是 POCO 记录对象的实例。index 是文件中的行索引。source 是 CSV 记录行。在这里,您有机会检查 POCO 对象,并在需要时生成 CSV 记录行。

提示:如果您想跳过记录而不写入,请将 source 设置为 null

提示:如果您想控制 CSV 记录行的生成,请将 source 设置为有效的 CSV 记录行文本。

返回 true 继续加载过程,否则返回 false 停止过程。

列表 10.3.1 BeforeRecordWrite 回调示例

public bool BeforeRecordWrite(object target, int index, ref object source)
{
    source = "1,Raj";
    return true;
}

10.4 AfterRecordWrite

此回调在每个 POCO 记录对象写入 CSV 文件之后调用。target 是 POCO 记录对象的实例。index 是文件中的行索引。source 是 CSV 记录行。在这里,您有机会与记录行执行任何后续操作。

返回 true 继续加载过程,否则返回 false 停止过程。

列表 10.4.1 AfterRecordWrite 回调示例

public bool AfterRecordWrite(object target, int index, object source)
{
    string line = source as string;
    return true;
}

10.5 RecordWriteError

在写入 POCO 记录对象时发生错误时调用此回调。target 是 POCO 记录对象的实例。index 是文件中的行索引。source 是 CSV 记录行。ex 是异常对象。在这里,您有机会处理异常。仅当 Configuration.ErrorMode 设置为 ReportAndContinue 时才会调用此方法。

返回 true 继续加载过程,否则返回 false 停止过程。

列表 10.5.1 RecordWriteError 回调示例

public bool RecordLoadError(object target, int index, object source, Exception ex)
{
    string line = source as string;
    return true;
}

10.6 BeforeRecordFieldWrite

此回调在每个 CSV 记录列写入 CSV 文件之前调用。target 是 POCO 记录对象的实例。index 是文件中的行索引。propName 是 CSV 记录属性名。value 是 CSV 列值。在这里,您有机会检查 CSV 记录属性值并执行任何自定义验证等。

返回 true 继续加载过程,否则返回 false 停止过程。

列表 10.6.1 BeforeRecordFieldWrite 回调示例

public bool BeforeRecordFieldWrite(object target, int index, string propName, ref object value)
{
    return true;
}

10.7 AfterRecordFieldWrite

此回调在每个 CSV 记录列值写入 CSV 文件之后调用。target 是 POCO 记录对象的实例。index 是文件中的行索引。propName 是 CSV 记录属性名。value 是 CSV 列值。可以在此处执行任何字段后续操作,例如计算其他属性、验证等。

返回 true 继续加载过程,否则返回 false 停止过程。

列表 10.7.1 AfterRecordFieldWrite 回调示例

public bool AfterRecordFieldWrite(object target, int index, string propName, object value)
{
    return true;
}

10.8 RecordWriteFieldError

在写入 CSV 记录列值时发生错误时调用此回调。target 是 POCO 记录对象的实例。index 是文件中的行索引。propName 是 CSV 记录属性名。value 是 CSV 列值。ex 是异常对象。在这里,您有机会处理异常。在 CSVWriter 执行以下两个序列的步骤后,将调用此方法。

  • CSVWriter 查找每个 CSV 属性的 FallbackValue 值。如果存在,它将尝试使用它进行写入。
  • 如果 FallbackValue 值不存在,并且 Configuration.ErrorMode 指定为 ReportAndContinue,则将执行此回调。

返回 true 继续加载过程,否则返回 false 停止过程。

列表 10.8.1 RecordFieldWriteError 回调示例

public bool RecordFieldWriteError
(object target, int index, string propName, object value, Exception ex)
{
    return true;
}

11. 自定义

CSVWriter 自动检测并从 POCO 实体加载配置设置。在运行时,您可以在 CSV 生成之前自定义和调整这些参数。CSVWriter 公开 Configuration 属性,它属于 ChoCSVRecordConfiguration 对象。使用此属性,您可以执行自定义。

列表 11.1 运行时自定义 CSVWriter

class Program
{
    static void Main(string[] args)
    {        
        List<ExpandoObject> objs = new List<ExpandoObject>();
        dynamic rec1 = new ExpandoObject();
        rec1.Id = 1;
        rec1.Name = "Mark";
        objs.Add(rec1);

        dynamic rec2 = new ExpandoObject();
        rec2.Id = 2;
        rec2.Name = "Jason";
        objs.Add(rec2);

        using (var parser = new ChoCSVWriter("Emp.csv"))
        {
            parser.Configuration.ColumnCountStrict = true;
            parser.Write(objs);
        }
    }
}

12. 使用动态对象

到目前为止,本文介绍了使用 POCO 对象使用 CSVWriterCSVWriter 还支持在没有 POCO 实体对象的情况下生成 CSV 文件。它利用 .NET 动态功能。下面的示例演示了如何使用动态对象生成 CSV 流。CSV 模式从第一个对象确定。如果动态对象成员值存在不匹配,则会引发错误并停止生成过程。

下面的示例展示了这一点。

列表 12.1 从动态对象生成 CSV 文件

class Program
{
    static void Main(string[] args)
    {        
        List<ExpandoObject> objs = new List<ExpandoObject>();
        dynamic rec1 = new ExpandoObject();
        rec1.Id = 1;
        rec1.Name = "Mark";
        objs.Add(rec1);

        dynamic rec2 = new ExpandoObject();
        rec2.Id = 2;
        rec2.Name = "Jason";
        objs.Add(rec2);

        using (var parser = new ChoCSVWriter("Emp.csv"))
        {
            parser.Configuration.ColumnCountStrict = true;
            parser.Write(objs);
        }
    }
}

13. 异常

CSVWriter 在不同情况下抛出不同类型的异常

  • ChoParserException - CSV 文件损坏,解析器无法恢复。
  • ChoRecordConfigurationException - 指定了任何无效的配置设置,将引发此异常。
  • ChoMissingRecordFieldException - CSV 列缺少属性,将引发此异常。

14. 技巧

当满足以下任一条件时,CSVWriter 会自动为列值加引号

  • 值包含换行符/分隔符字符
  • 值包含引号字符
  • 值包含前导或尾随空格

其他情况,如果您想在值周围添加引号,可以在 QuoteField 参数中指定为 true

列表 14.1.1 CSV 文件中的多行列值

[ChoCSVFileHeader]
[ChoCSVRecordObject(HasExcelSeparator = true)]
public class EmployeeRec
{
    [ChoCSVRecordField(1, FieldName = "NewId")]
    [Required]
    [ChoFallbackValue(100)]
    [Range(100, 10000)]
    public int? Id
    {
        get;
        set;
    }
    [ChoCSVRecordField(2, QuoteField = true)]
    [DefaultValue("XXXX")]
    public string Name
    {
        get;
        set;
    }
 
    public override string ToString()
    {
        return "{0}. {1}.".FormatString(Id, Name);
    }
}

15. 使用 MetadataType 注解

Cinchoo ETL 与数据注释的 MetadataType 模型配合得更好。这是一种将 MetaData 类附加到数据模型类的方法。在此关联类中,您提供数据模型中不存在的附加元数据信息。它的作用是向类添加属性,而无需修改该类。您可以将一个参数的属性添加到将包含所有属性的类中。当 POCO 类由自动工具(例如 Entity Framework、MVC 等)自动生成时,这非常有用。这就是第二个类发挥作用的原因。您可以添加新内容而不更改生成的文件。此外,这通过将关注点分离到多个类来促进模块化。

有关更多信息,请在 MSDN 中搜索。

清单 15.1 MetadataType 注释用法示例

[MetadataType(typeof(EmployeeRecMeta))]
public class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; }
}

[ChoCSVFileHeader]
[ChoCSVRecordObject(Encoding = "Encoding.UTF32", ErrorMode = ChoErrorMode.ThrowAndStop,
IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, ThrowAndStopOnMissingField = false, 
    ObjectValidationMode = ChoObjectValidationMode.MemberLevel)]
public class EmployeeRecMeta : IChoNotifyRecordWrite, IChoValidatable
{
    [ChoCSVRecordField(1, FieldName = "id", ErrorMode = ChoErrorMode.ReportAndContinue )]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, 1, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }

    [ChoCSVRecordField(2, FieldName = "Name", QuoteField = true)]
    [StringLength(1)]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }

    public bool AfterRecordWrite(object target, int index, object source)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordWrite(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordWriteError(object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }
 
    public bool TryValidate(object target, ICollection<ValidationResult> validationResults)
    {
        return true;
    }
 
    public bool TryValidateFor
    (object target, string memberName, ICollection<ValidationResult> validationResults)
    {
        return true;
    }
}

在上面,EmployeeRec 是数据类。仅包含领域特定属性和操作。使其成为一个非常简单的类。

我们将验证、回调机制、配置等分离到元数据类型类 EmployeeRecMeta 中。

16. 配置选项

如果 POCO 实体类是自动生成的类,或者通过库公开,或者是密封类,则无法声明式地将 CSV 架构定义附加到它。在这种情况下,您可以选择以下选项之一来指定 CSV 布局配置

  • 手动配置
  • 自动映射配置
  • 附加 MetadataType

我将向您展示如何为每种方法配置以下 POCO 实体类。

清单 16.1 密封 POCO 实体类

public sealed class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; }
}

16.1 手动配置

从头开始定义一个全新的配置对象,并将所有必需的 CSV 字段添加到 ChoCSVConfiguration.CSVRecordFieldConfigurations 集合属性。此选项为您提供了更大的灵活性来控制 CSV 解析的配置。但缺点是可能出错,如果 CSV 文件布局很大,则难以管理。

图 16.1.1 手动配置

ChoCSVRecordConfiguration config = new ChoCSVRecordConfiguration();
config.CSVFileHeaderConfiguration.HasHeaderRecord = true;
config.ThrowAndStopOnMissingField = true;
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Id", 1));
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name", 2));

16.2 自动映射配置

这是另一种方法,是自动映射 POCO 实体类的 CSV 列的、错误较少的方法。

首先,为 EmployeeRec POCO 实体类定义一个架构类,如下所示。

清单 16.2.1 Auto Map 类

public class EmployeeRecMap
{
    [ChoCSVRecordField(1, FieldName = "id")]
    public int Id { get; set; }
 
    [ChoCSVRecordField(2, FieldName = "Name")]
    public string Name { get; set; } 
}

然后,您可以使用 ChoCSVRecordConfiguration.MapRecordFields 方法来自动映射 CSV 列。

清单 16.2.2 使用 Auto Map 配置

ChoCSVRecordConfiguration config = new ChoCSVRecordConfiguration();
config.MapRecordFields<EmployeeRecMap>();

EmployeeRec rec1 = new EmployeeRec();
rec1.Id = 2;
rec1.Name = "Jason";

foreach (var e in new ChoCSVWriter<EmployeeRec>("Emp.csv", config)) 
    w.Write(rec1);

16.3 附加 MetadataType 类

这是将 MetadataType 类附加到 POCO 实体对象的另一种方法。以前的方法仅处理 CSV 列的自动映射。其他配置属性,如属性转换器、解析器参数、默认/备用值等,则不考虑。

此模型通过定义 MetadataType 类并声明式地指定 CSV 配置参数来考虑所有内容。当您的 POCO 实体是密封类且不是部分类时,这很有用。此外,它是配置 POCO 实体 CSV 解析的有利且错误较少的方法之一。

清单 16.3.1 定义 MetadataType 类

[ChoCSVFileHeader()]
[ChoCSVRecordObject(Encoding = "Encoding.UTF32", ErrorMode = ChoErrorMode.ReportAndContinue,
IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, ThrowAndStopOnMissingField = false, 
    ObjectValidationMode = ChoObjectValidationMode.MemberLevel)]
public class EmployeeRecMeta : IChoNotifyRecordWrite, IChoValidatable
{
    [ChoCSVRecordField(1, FieldName = "id", ErrorMode = ChoErrorMode.ReportAndContinue )]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, 1, ErrorMessage = "Id must be > 0.")]
    //[ChoFallbackValue(1)]
    public int Id { get; set; }
    [ChoCSVRecordField(2, FieldName = "Name", QuoteField = true)]
    [StringLength(1)]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }

    public bool AfterRecordWrite(object target, int index, object source)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordWrite(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordWriteError(object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }
 
    public bool TryValidate(object target, ICollection<ValidationResult> validationResults)
    {
        return true;
    }
 
    public bool TryValidateFor(object target, string memberName, 
                               ICollection<ValidationResult> validationResults)
    {
        return true;
    }
}

清单 16.3.2 附加 MetadataType 类

//Attach metadata 
ChoMetadataObjectCache.Default.Attach<EmployeeRec>(new EmployeeRecMeta());

using (var tx = File.OpenWrite("Emp.csv"))
{
    using (var parser = new ChoCSVWriter<EmployeeRec>(tx))
    {
        parser.Write(objs);
    }
}

17. ToTextAll 辅助方法

这是一个非常方便的辅助方法,可以从对象列表中生成 CSV 格式的输出。它有助于您运行和尝试不同的选项,以便在测试环境中快速查看 CSV 输出。

static void ToTextTest()
{
    List<EmployeeRec> objs = new List<EmployeeRec>();
    EmployeeRec rec1 = new EmployeeRec();
    rec1.Id = 10;
    rec1.Name = "Mark";
    objs.Add(rec1);
 
    EmployeeRec rec2 = new EmployeeRec();
    rec2.Id = 200;
    rec2.Name = "Lou";
    objs.Add(rec2);
 
    Console.WriteLine(ChoCSVWriter.ToTextAll(objs));
}

17a. ToText 辅助方法

这是一个非常方便的辅助方法,可以从对象生成 CSV 格式的输出。它有助于您运行和尝试不同的选项,以便在测试环境中快速查看 CSV 输出。

static void ToTextTest()
{
    EmployeeRec rec1 = new EmployeeRec();
    rec1.Id = 10;
    rec1.Name = "Mark";
    objs.Add(rec1);
 
    Console.WriteLine(ChoCSVWriter.ToText(rec1));
}

18. 写入 DataReader 辅助方法

此辅助方法允许您从 ADO.NET DataReader 创建 CSV 文件/流。

static void WriteDataReaderTest()
{
    SqlConnection conn = new SqlConnection(connString);
    conn.Open();
    SqlCommand cmd = new SqlCommand("SELECT * FROM Members", conn);
    IDataReader dr = cmd.ExecuteReader();

    StringBuilder csv = new StringBuilder();
    using (var parser = new ChoCSVWriter(csv))
    {
        parser.Write(dr);
    }
    Console.WriteLine(csv.ToString());
}

19. 写入 DataTable 辅助方法

此辅助方法允许您从 ADO.NET DataTable 创建 CSV 文件/流。

static void WriteDataTableTest()
{
    string connString = @"Data Source=(localdb)\v11.0;
                        Initial Catalog=TestDb;Integrated Security=True";

    SqlConnection conn = new SqlConnection(connString);
    conn.Open();
    SqlCommand cmd = new SqlCommand("SELECT * FROM Members", conn);
    SqlDataAdapter da = new SqlDataAdapter(cmd);
    DataTable dt = new DataTable();
    da.Fill(dt);
 
    StringBuilder csv = new StringBuilder();
    using (var parser = new ChoCSVWriter(csv)
        .WithFirstLineHeader()
        )
    {
        parser.Write(dt);
    }

    Console.WriteLine(csv.ToString());
}

20. 高级主题

20.1 覆盖转换器格式规范

Cinchoo ETL 会自动解析并将每个 CSV 列值无缝地转换为相应的 CSV 列的底层数据类型。大多数基本的 .NET 类型都自动处理,无需任何设置。

这是通过 ETL 系统中的两个关键设置实现的

  1. ChoCSVRecordConfiguration.CultureInfo - 表示特定文化的信息,包括文化的名称、书写系统和使用的日历,以及对常见操作(如格式化日期和排序字符串)提供信息的文化特定对象的访问。默认是 'en-US'。
  2. ChoTypeConverterFormatSpec - 这是一个全局格式说明符类,包含所有内在的 .NET 类型格式说明。

在本节中,我将讨论如何根据解析需求更改每个 .NET 内在数据类型的默认格式规范。

ChoTypeConverterFormatSpec 是一个单例类,实例通过 'Instance' static 成员暴露。它是线程本地的,意味着每个线程将有一个单独的实例副本。

为每个内在类型提供了两组格式说明成员,一组用于加载,另一组用于写入值,除了 BooleanEnumDataTime 类型。这些类型只有一对成员用于加载和写入操作。

通过 ChoTypeConverterFormatSpec 指定每个内在数据类型的格式说明将影响整个系统,即通过设置 ChoTypeConverterFormatSpec.IntNumberStyle = NumberStyles.AllowParentheses,将影响所有 CSV 对象的整数成员允许使用括号。如果您想覆盖此行为并控制特定 CSV 数据成员以处理其来自全局系统范围设置的独特 CSV 值解析,可以通过在 CSV 字段成员级别指定 TypeConverter 来实现。有关更多信息,请参阅第 13.4 节

清单 20.1.1 ChoTypeConverterFormatSpec 成员

public class ChoTypeConverterFormatSpec
{
    public static readonly ThreadLocal<ChoTypeConverterFormatSpec> 
    Instance = new ThreadLocal<ChoTypeConverterFormatSpec>
               (() => new ChoTypeConverterFormatSpec());
 
    public string DateTimeFormat { get; set; }
    public ChoBooleanFormatSpec BooleanFormat { get; set; }
    public ChoEnumFormatSpec EnumFormat { get; set; }
 
    public NumberStyles? CurrencyNumberStyle { get; set; }
    public string CurrencyFormat { get; set; }
 
    public NumberStyles? BigIntegerNumberStyle { get; set; }
    public string BigIntegerFormat { get; set; }
 
    public NumberStyles? ByteNumberStyle { get; set; }
    public string ByteFormat { get; set; }
 
    public NumberStyles? SByteNumberStyle { get; set; }
    public string SByteFormat { get; set; }
 
    public NumberStyles? DecimalNumberStyle { get; set; }
    public string DecimalFormat { get; set; }
 
    public NumberStyles? DoubleNumberStyle { get; set; }
    public string DoubleFormat { get; set; }
 
    public NumberStyles? FloatNumberStyle { get; set; }
    public string FloatFormat { get; set; }
 
    public string IntFormat { get; set; }
    public NumberStyles? IntNumberStyle { get; set; }
 
    public string UIntFormat { get; set; }
    public NumberStyles? UIntNumberStyle { get; set; }
 
    public NumberStyles? LongNumberStyle { get; set; }
    public string LongFormat { get; set; }
 
    public NumberStyles? ULongNumberStyle { get; set; }
    public string ULongFormat { get; set; }
 
    public NumberStyles? ShortNumberStyle { get; set; }
    public string ShortFormat { get; set; }
 
    public NumberStyles? UShortNumberStyle { get; set; }
    public string UShortFormat { get; set; }
}

下面的示例演示了如何使用 CSVWriter 加载包含 'se-SE' (瑞典语) 文化特定数据的 CSV 数据流。此外,输入源包含带括号的 'EmployeeNo' 值。为了使加载成功,我们必须将 ChoTypeConverterFormatSpec.IntNumberStyle 设置为 NumberStyles.AllowParenthesis

清单 20.1.2 在代码中使用 ChoTypeConverterFormatSpec

static void FormatSpecDynamicTest()
{
    ChoTypeConverterFormatSpec.Instance.DateTimeFormat = "d";
    ChoTypeConverterFormatSpec.Instance.BooleanFormat = ChoBooleanFormatSpec.YOrN;
 
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    objs.Add(rec2);
 
    StringBuilder csv = new StringBuilder();
    using (var parser = new ChoCSVWriter(csv)
        .WithFirstLineHeader()
        )
    {
        parser.Write(objs);
    }

    Console.WriteLine(csv.ToString());
}

20.2 货币支持

Cinchoo ETL 提供了 ChoCurrency 对象来读取和写入 CSV 文件中的货币值。ChoCurrency 是一个包装类,用于以 decimal 类型保存货币值,并支持在 CSV 加载期间将其序列化为文本格式。

清单 20.2.1 在动态模型中使用 Currency 成员

static void CurrencyDynamicTest()
{
    ChoTypeConverterFormatSpec.Instance.CurrencyFormat = "C2";
 
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);
 
    StringBuilder csv = new StringBuilder();
    using (var parser = new ChoCSVWriter(csv)
        .WithFirstLineHeader()
        )
    {
        parser.Write(objs);
    }

    Console.WriteLine(csv.ToString());
}

上面的示例展示了如何使用动态对象模型输出货币值。由于货币输出将包含千位分隔符,这将导致 CSV 文件生成失败。为了克服这个问题,我们指示 writer 对所有字段加引号。

附注:货币值的格式由 CSVWriter 通过 ChoRecordConfiguration.CultureChoTypeConverterFormatSpec.CurrencyFormat 来确定。

下面的示例展示了如何在 POCO 实体类中使用 ChoCurrency CSV 字段。

清单 20.2.2 在 POCO 模型中使用 Currency 成员

public class EmployeeRecWithCurrency
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ChoCurrency Salary { get; set; }
}
 
static void CurrencyPOCOTest()
{
    List<EmployeeRecWithCurrency> objs = new List<EmployeeRecWithCurrency>();
    EmployeeRecWithCurrency rec1 = new EmployeeRecWithCurrency();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    EmployeeRecWithCurrency rec2 = new EmployeeRecWithCurrency();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);
 
    StringBuilder csv = new StringBuilder();
    using (var parser = new ChoCSVWriter(csv)
        .WithFirstLineHeader()
        )
    {
        parser.Write(objs);
    }

    Console.WriteLine(csv.ToString());
}

20.3 枚举支持

Cinchoo ETL 会隐式处理 enum 列值在 CSV 文件中的解析/写入。如果您想精细控制这些值的解析,可以通过 ChoTypeConverterFormatSpec.EnumFormat 全局指定它们。默认值是 ChoEnumFormatSpec.Value

FYI,更改此值将影响整个系统。

可以使用以下三个可能的值

  1. ChoEnumFormatSpec.Value - Enum 值用于解析。
  2. ChoEnumFormatSpec.Name - Enum 键名用于解析。
  3. ChoEnumFormatSpec.Description - 如果每个 enum 键都用 DescriptionAttribute 修饰,则使用其值进行解析。

清单 20.3.1 在解析期间指定 Enum 格式规范

public enum EmployeeType
{
    [Description("Full Time Employee")]
    Permanent = 0,
    [Description("Temporary Employee")]
    Temporary = 1,
    [Description("Contract Employee")]
    Contract = 2
}
 
static void EnumTest()
{
    ChoTypeConverterFormatSpec.Instance.EnumFormat = ChoEnumFormatSpec.Description;
 
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    rec1.Status = EmployeeType.Permanent;
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    rec2.Status = EmployeeType.Contract;
    objs.Add(rec2);

    StringBuilder csv = new StringBuilder();
    using (var parser = new ChoCSVWriter(csv)
        .WithFirstLineHeader()
        )
    {
        parser.Write(objs);
    }

    Console.WriteLine(csv.ToString());
}

20.4 布尔值支持

Cinchoo ETL 会隐式处理 CSV 文件中布尔值列值的解析/写入。如果您想精细控制这些值的解析,可以通过 ChoTypeConverterFormatSpec.BooleanFormat 全局指定它们。默认值是 ChoBooleanFormatSpec.ZeroOrOne

FYI,更改此值将影响整个系统。

可以使用以下四种可能的值

  1. ChoBooleanFormatSpec.ZeroOrOne - '0' 表示 false。'1' 表示 true
  2. ChoBooleanFormatSpec.YOrN - 'Y' 表示 true,'N' 表示 false
  3. ChoBooleanFormatSpec.TrueOrFalse - 'True' 表示 true,'False' 表示 false
  4. ChoBooleanFormatSpec.YesOrNo - 'Yes' 表示 true,'No' 表示 false

清单 20.4.1 在解析期间指定布尔值格式规范

static void BoolTest()
{
    ChoTypeConverterFormatSpec.Instance.BooleanFormat = ChoBooleanFormatSpec.YOrN;
 
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    rec1.Status = EmployeeType.Permanent;
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    rec2.Status = EmployeeType.Contract;
    objs.Add(rec2);
 
    StringBuilder csv = new StringBuilder();
    using (var parser = new ChoCSVWriter(csv)
        .WithFirstLineHeader()
        )
    {
        parser.Write(objs);
    }

    Console.WriteLine(csv.ToString());
}

20.5 DateTime 支持

Cinchoo ETL 使用系统文化或自定义设置的文化隐式处理 datetime CSV 列值在 CSV 文件中的解析/写入。如果您想精细控制这些值的解析,可以通过 ChoTypeConverterFormatSpec.DateTimeFormat 全局指定它们。默认值是 'd'。

FYI,更改此值将影响整个系统。

您可以使用的任何有效标准自定义日期时间 .NET 格式规范来解析文件中的 datetime CSV 值。

清单 20.5.1 在解析期间指定 datetime 格式规范

static void DateTimeDynamicTest()
{
    ChoTypeConverterFormatSpec.Instance.DateTimeFormat = "MMM dd, yyyy";
 
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);
 
    StringBuilder csv = new StringBuilder();
    using (var parser = new ChoCSVWriter(csv)
        .WithFirstLineHeader()
        )
    {
        parser.Write(objs);
    }

    Console.WriteLine(csv.ToString());
}

上面的示例展示了如何将自定义 datetime 值生成到 CSV 文件。

注意:由于 datetime 值包含 CSV 分隔符,我们指示 writer 对所有字段加引号。

21. Fluent API

CSVWriter 通过 Fluent API 方法公开了一些常用的配置参数。这将使生成 CSV 文件的编程更快。

21.1 WithDelimiter

此 API 方法在 CSVWriter 上设置 CSV 字段分隔符。

static void QuickDynamicDelimiterTest()
{
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);
 
    StringBuilder csv = new StringBuilder();
    using (var parser = new ChoCSVWriter(csv)
        .WithFirstLineHeader()
        .WithDelimiter("|")
        )
    {
        parser.Write(objs);
    }

    Console.WriteLine(csv.ToString());
}

21.2 WithFirstLineHeader

此 API 方法标记 CSV 文件第一行是否为标题。可选的 bool 参数指定第一行是否为标题。默认是 true

static void QuickDynamicTest()
{
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);
 
    StringBuilder csv = new StringBuilder();
    using (var parser = new ChoCSVWriter(csv)
        .WithFirstLineHeader()
        .QuoteAllFields()
        )
    {
        parser.Write(objs);
    }

    Console.WriteLine(csv.ToString());
}

21.3 WithFields

此 API 方法指定要考虑用于写入 CSV 文件的 CSV 字段列表。其他字段将被丢弃。字段名不区分大小写。

static void QuickDynamicTest()
{
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);
 
    StringBuilder csv = new StringBuilder();
    using (var parser = new ChoCSVWriter(csv)
        .WithFirstLineHeader()
        .WithFields("Id", "Name")
        )
    {
        parser.Write(objs);
    }

    Console.WriteLine(csv.ToString());
}

21.4 WithField

此 API 方法用于添加具有特定日期类型、引号标志和/或引号字符的 CSV 列。此方法在动态对象模型中很有用,通过为每个 CSV 列指定适当的 datatype

static void QuickDynamicTest()
{
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);
 
    StringBuilder csv = new StringBuilder();
    using (var parser = new ChoCSVWriter(csv)
        .WithFirstLineHeader()
        .WithField("Id", typeof(int))
        .WithField("Name"))
        )
    {
        parser.Write(objs);
    }

    Console.WriteLine(csv.ToString());
}

21.5 QuoteAllFields

此 API 方法用于指定是否所有字段都应被引号包围。

static void QuickDynamicTest()
{
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);

    StringBuilder csv = new StringBuilder();
    using (var parser = new ChoCSVWriter(csv)
        .WithFirstLineHeader()
        .QuoteAllFields()
        )
    {
        parser.Write(objs);
    }

    Console.WriteLine(csv.ToString());
}

21.6 ColumnCountStrict

此 API 方法用于在写入 CSV 文件之前设置 CSVWriter 以执行列计数检查。

static void QuickDynamicTest()
{
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);
 
    StringBuilder csv = new StringBuilder();
    using (var parser = new ChoCSVWriter(csv)
        .WithFirstLineHeader()
        .ColumnCountStrict()
        )
    {
        parser.Write(objs);
    }

    Console.WriteLine(csv.ToString());
}

21.7. Configure

此 API 方法用于配置未通过 Fluent API 公开的所有配置参数。

static void ConfigureTest()
{
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    objs.Add(rec1);

    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    objs.Add(rec2);

    StringBuilder csv = new StringBuilder();
    using (var parser = new ChoCSVWriter(csv)
        .WithFirstLineHeader()
        .Configure(c => c.ErrorMode = ChoErrorMode.ThrowAndStop)
        )
    {
        parser.Write(objs);
    }

    Console.WriteLine(csv.ToString());
}

21.8. Setup

此 API 方法用于通过 Fluent API 设置 writer 的参数/事件。

static void SetupTest()
{
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    objs.Add(rec1);

    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    objs.Add(rec2);

    StringBuilder csv = new StringBuilder();
    using (var parser = new ChoCSVWriter(csv)
        .WithFirstLineHeader()
        .Setup(r => r.BeforeRecordWrite += (o, e) =>
        {
        })
        )
    {
        parser.Write(objs);
    }

    Console.WriteLine(csv.ToString());
}

有关 Cinchoo ETL 的更多信息,请访问其他 CodeProject 文章。

查找现有问题/在新问题中打开

历史

  • 2016 年 12 月 20 日:初始版本
  • 2020 年 4 月 11 日:文章已更新
© . All rights reserved.