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

Cinchoo ETL - FixedLength 写入器

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (2投票s)

2017年2月8日

CPOL

23分钟阅读

viewsIcon

13114

适用于 .NET 的简单 FixedLength 文件写入器

目录

  1. 引言
  2. 要求
  3. “Hello World!”示例
    1. 快速写入 - 数据优先方法
    2. 代码优先方法
    3. 配置优先方法
  4. 写入所有记录
  5. 手动写入记录
  6. 自定义 FixedLength 记录
  7. 自定义 FixedLength 标头
  8. 自定义 FixedLength 字段
    1. 默认值
    2. ChoFallbackValue
    3. 类型转换器
    4. 验证
  9. 保留
  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. 日期时间支持

1. 引言

ChoETL 是一个用于 .NET 的开源 ETL(提取、转换和加载)框架。它是一个基于代码的库,用于从多个来源提取数据、进行转换,并将其加载到您自己的 .NET 环境中的数据仓库中。您可以立即将数据导入您的数据仓库。

本文讨论了 ChoETL 框架提供的 FixedLengthRWriter 组件。它是一个简单的实用程序类,用于将 FixedLength 数据保存到文件中。

注意:相应的 FixedLengthReader 文章可在此处找到:此处

特点

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

2. 要求

此框架库是用 C# 编写的,使用 .NET 4.5 Framework。

3. “Hello World!” 示例

  • 打开 VS.NET 2013 或更高版本
  • 创建一个示例 VS.NET (.NET Framework 4.5) 控制台应用程序项目
  • 通过 包管理器控制台 使用 Nuget 命令安装 ChoETLInstall-Package ChoETL
  • 使用 ChoETL 命名空间

让我们从一个简单的示例开始,生成具有 2 列的以下 FixedLength 文件。

清单 3.1 示例 FixedLength 数据文件(记录长度:18 个字符)

Id      Name      
00000001Mark      
00000002Jason     

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

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

这是快速创建 FixedLength 文件的方法。不需要类型化的 POCO 对象。下面的示例代码展示了如何使用动态对象生成上述示例 FixedLength 文件。

清单 3.1.1 将对象列表写入 FixedLength 文件

static void QuickWriteTest()
{
    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 ChoFixedLengthWriter("Emp.txt").
        WithField("Id", 0, 8. fieldType: typeof(int)).
        WithField("Name", 8, 10))
    {
        parser.Write(objs);
    }
}

在上述示例中,使用流式 API 定义每个列的起始索引和大小,然后我们将对象列表一次性传递给 FixedLengthWriter,将它们写入 FixedLength 文件。

清单 3.1.2 将每个对象写入 FixedLength 文件

static void QuickWriteTest2()
{
    using (var parser = new ChoFixedLengthWriter("Emp.txt").
        WithField("Id", 0, 8, fieldType: typeof(int)).
        WithField("Name", 8, 10))
    {
        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);
    }
}

在上述示例中,我们控制构造并将每个单独的记录传递给 FixedLengthWriter,以使用 Write 重载生成 FixedLength 文件。

3.2. 代码优先方法

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

清单 3.2.1 简单的 POCO 实体类

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

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

清单 3.2.2 保存到 FixedLength 文件

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 ChoFixedLengthWriter<EmployeeRecSimple>("Emp.txt").
        WithField("Id", 0, 8, fieldType: typeof(int)).
        WithField("Name", 8, 10))
{
    parser.Write(objs);
}

上述示例展示了如何从类型化 POCO 类对象创建 FixedLength 文件。

3.3. 配置优先方法

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

清单 3.3.1 定义 FixedLength 配置

ChoFixedLengthRecordConfiguration config = new ChoFixedLengthRecordConfiguration();
config.FixedLengthRecordFieldConfigurations.Add
(new ChoFixedLengthRecordFieldConfiguration("Id", 0, 8, fieldType: typeof(int)));
config.FixedLengthRecordFieldConfigurations.Add
(new ChoFixedLengthRecordFieldConfiguration("Name", 8, 10));

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

清单 3.3.2 无 POCO 对象生成 FixedLength 文件

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 ChoFixedLengthWriter("Emp.txt", config))
{
    parser.Write(objs);
}

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

清单 3.3.3 使用 POCO 对象保存 FixedLength 文件

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 ChoFixedLengthWriter<EmployeeRecSimple>
("Emp.txt", config))
{
    parser.Write(objs);
}

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

3.4. 代码优先声明式配置

这是结合定义 POCO 实体类和声明性附加 FixedLength 配置参数的方法。 id 是一个必需列,name 是一个可选值列,默认值为 "XXXX"。如果 name 不存在,它将取默认值。

清单 3.4.1 定义 POCO 对象

public class EmployeeRec
{
    [ChoFixedLengthRecordField(0, 8)]
    [Required]
    public int? Id
    {
        get;
        set;
    }

    [ChoFixedLengthRecordField(8, 10)]
    [DefaultValue("XXXX")]
    public string Name
    {
        get;
        set;
    }

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

上面的代码说明了如何使用生成 FixedLength 文件所需的必要属性定义 POCO 对象。首先,为每个记录字段定义属性,并使用 ChoFixedLengthRecordFieldAttribute 来符合 FixedLength 记录映射。每个属性必须指定字段的 StartIndexSize 才能映射到 FixedLength 列。 StartIndex 是从 0 开始的。 Id 是一个必需属性。我们用 RequiredAttribute 装饰它。 Name 使用 DefaultValueAttribute 赋予默认值。这意味着如果对象中未设置 Name 值,FixedLengthWriter 将使用默认值 'XXXX' 写入文件。

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

清单 3.4.2 使用 POCO 对象保存 FixedLength 文件

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 ChoFixedLengthWriter<EmployeeRec>("Emp.txt"))
{
    parser.Write(objs);
}

我们首先创建一个 ChoFixedLengthWriter 对象的新实例。就是这样。所有从对象生成 FixedLength 数据的繁重工作都由写入器在底层完成。

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

4. 写入所有记录

它就像设置 POCO 对象以匹配 FixedLength 文件结构、构造对象列表并将其传递给 FixedLengthWriterWrite 方法一样简单。这将一次性将整个对象列表写入 FixedLength 文件。

清单 4.1 写入 FixedLength 文件

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

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

清单 4.2 写入 FixedLength 文件流

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

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

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

5. 手动写入记录

这是在 POCO 对象以非连接方式构造时,将每个单独的记录写入 FixedLength 文件的替代方法。

清单 5.1 写入 FixedLength 文件

var writer = new ChoFixedLengthWriter<EmployeeRec>("Emp.txt");

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

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

writer.Write(rec2);

6. 自定义 FixedLength 记录

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

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

[ChoFixedLengthRecordObject(Encoding = "Encoding.UTF32", 
ErrorMode = ChoErrorMode.IgnoreAndContinue, IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All)]
public class EmployeeRec
{
    [ChoFixedLengthRecordField(0, 8)]
    public int Id { get; set; }
    [ChoFixedLengthRecordField(8, 10, QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}

以下是用于对文件执行 FixedLength 加载操作的可用属性

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

7. 自定义 FixedLength 标头

通过将 ChoFixedLengthFileHeaderAttribute 声明性地附加到 POCO 实体对象,您可以在创建 FixedLength 文件时影响写入器生成 FixedLength 标头。

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

[ChoFixedLengthFileHeader]
public class EmployeeRec
{
    [ChoFixedLengthRecordField(0, 8)]
    public int Id { get; set; }
    [ChoFixedLengthRecordField(8, 10, QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}

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

  • FillChar - 当 FixedLength 列标头的大小小于列大小(ChoFixedLengthRecordFieldAttribute.SizeChoFixedLengthRecordFieldConfiguration.Size)时使用的填充字符。默认值为 '\0',不进行填充
  • Justification - 列标头对齐方式。默认值为 Left
  • TrimOption - 不适用
  • Truncate - 此标志告诉写入器,如果 FixedLength 列标头值超出列大小,则截断该值。默认值为 false

8. 自定义 FixedLength 字段

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

清单 6.1 自定义 POCO 对象的 FixedLength 列

[ChoFixedLengthFileHeader]
public class EmployeeRec
{
    [ChoFixedLengthRecordField(0, 8)]
    public int Id { get; set; }
    [ChoFixedLengthRecordField(8, 10, QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}

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

  • StartIndex - 列在行中的从零开始的起始字符位置
  • FieldName - FixedLength 列名标头。如果未指定,将使用 POCO 对象属性名称作为列标头
  • FillChar - 当 FixedLength 列值的大小小于列大小时使用的填充字符。默认值为 '\0',不进行填充
  • FieldValueJustification - 列值对齐方式。默认值为 Left
  • FieldValueTrimOption - 不适用
  • Truncate - 此标志告诉写入器,如果 FixedLength 列值超出列大小,则截断该值。默认值为 false
  • Size - FixedLength 列值的大小
  • QuoteField - 一个标志,告诉写入器 FixedLength 列值是否用引号包围。
  • ErrorMode - 此标志指示在写入过程中是否应抛出异常,并且预期的字段未能转换和写入。可能的值是
    • IgnoreAndContinue - 忽略错误并继续加载记录的其他属性
    • ReportAndContinue - 如果 POCO 实体是 IChoRecord 类型,则向其报告错误
    • ThrowAndStop - 抛出错误并停止执行
  • IgnoreFieldValueMode - 不适用

8.1. DefaultValue

任何 POCO 实体属性都可以使用 System.ComponentModel.DefaultValueAttribute 指定默认值。当 FixedLength 值为 null 时,用于写入的值(通过 IgnoreFieldValueMode 控制)。

8.2. ChoFallbackValue

任何 POCO 实体属性都可以使用 ChoETL.ChoFallbackValueAttribute 指定回退值。当 FixedLength 值无法转换为文本时,该值将用于设置属性。 Fallback 值仅在 ErrorModeIgnoreAndContinueReportAndContinue 时设置。

8.3. 类型转换器

大多数基本类型会自动转换为 string/文本并保存到 FixedLength 文件中。如果 FixedLength 字段的值不能自动转换为文本值,您可以指定自定义/内置的 .NET 转换器将值转换为文本。这些转换器可以是 IValueConverterTypeConverter 转换器。

用于将属性值转换为文本的方法是 IValueConverter.ConvertBack()TypeConvert.ConvertTo()

清单 8.3.1 指定类型转换器

[ChoFixedLengthFileHeader]
public class EmployeeRec
{
    [ChoFixedLengthRecordField(0, 8)]
    [ChoTypeConverter(typeof(IntConverter))]
    public int Id { get; set; }
    [ChoFixedLengthRecordField(8, 10, QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}

清单 8.3.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' FixedLength 属性。

8.4. 验证

FixedLengthWriter 利用 System.ComponentModel.DataAnnotationsValidation Block 验证属性为 POCO 实体的各个字段指定验证规则。请参阅 MSDN 网站以获取可用 DataAnnotations 验证属性的列表。

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

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

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

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

清单 8.4.2 对 POCO 实体进行手动验证

[ChoFixedLengthFileHeader]
[ChoFixedLengthRecordObject
(Encoding = "Encoding.UTF32", ErrorMode = ChoErrorMode.IgnoreAndContinue,
       IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, ThrowAndStopOnMissingField = false)]
public partial class EmployeeRec : IChoValidatable
{
    [ChoFixedLengthRecordField(0, 8)]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
 
    [ChoFixedLengthRecordField(8, 10)]
    [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;
    }
 
    public void Validate(object target)
    {
    }
 
    public void ValidateFor(object target, string memberName)
    {
    }
}

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

IChoValidatable 接口公开以下方法

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

10. 回调机制

FixedLengthWriter 提供开箱即用的行业标准 FixedLength 数据文件生成功能,以满足大多数需求。如果生成过程未能满足您的任何需求,您可以使用 FixedLengthWriter 提供的回调机制来处理此类情况。为了参与回调机制,POCO 实体对象或 DataAnnotationMetadataType 类型对象必须继承 IChoNotifyRecordWrite 接口。

提示:这些接口方法中抛出的任何异常都将被忽略。

IChoNotifyRecordWrite 公开了以下方法

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

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

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

    public bool AfterRecordFieldWrite(object target, int index, string propName, object value)
    {
        throw new NotImplementedException();
    }

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

    public bool BeforeRecordFieldWrite(object target, int index, string propName, ref object value)
    {
        throw new NotImplementedException();
    }

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

    public bool BeginWrite(object source)
    {
        throw new NotImplementedException();
    }

    public void EndWrite(object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordFieldWriteError
        (object target, int index, string propName, object value, Exception ex)
    {
        throw new NotImplementedException();
    }

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

清单 10.2 基于 MetaDataType 的回调机制实现

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

    [ChoFixedLengthRecordField(8. 10, QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
 
    public bool AfterRecordFieldWrite(object target, int index, string propName, object value)
    {
        throw new NotImplementedException();
    }

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

    public bool BeforeRecordFieldWrite(object target, int index, string propName, ref object value)
    {
        throw new NotImplementedException();
    }

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

    public bool BeginWrite(object source)
    {
        throw new NotImplementedException();
    }

    public void EndWrite(object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordFieldWriteError
       (object target, int index, string propName, object value, Exception ex)
    {
        throw new NotImplementedException();
    }

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

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

10.1 BeginWrite

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

列表 10.1.1 BeginWrite 回调示例

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

10.2 EndWrite

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

列表 10.2.1 EndWrite 回调示例

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

10.3 BeforeRecordWrite

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

提示:如果要跳过写入记录,请将 source 设置为 null。

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

返回 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 记录对象写入 FixedLength 文件之后调用。 target 是 POCO 记录对象的实例。 index 是文件中的行索引。 sourceFixedLength 记录行。在这里,您有机会对记录行执行任何后续操作。

返回 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 是文件中的行索引。 sourceFixedLength 记录行。 ex 是异常对象。在这里,您有机会处理异常。此方法仅在 Configuration.ErrorModeReportAndContinue 时调用。

返回 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

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

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

清单 10.6.1 BeforeRecordFieldWrite 回调示例

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

10.7 AfterRecordFieldWrite

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

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

清单 10.7.1 AfterRecordFieldWrite 回调示例

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

10.8 RecordWriteFieldError

此回调在写入 FixedLength 记录列值时遇到错误时调用。 target 是 POCO 记录对象的实例。 index 是文件中的行索引。 propNameFixedLength 记录属性名称。 valueFixedLength 列值。 ex 是异常对象。在这里,您有机会处理异常。此方法仅在 FixedLengthReader 执行以下两个步骤序列后调用

  • FixedLengthWriter 查找每个 FixedLength 属性的 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. 自定义

FixedLengthWriter 自动检测并从 POCO 实体加载配置设置。在运行时,您可以在 FixedLength 生成之前自定义和调整这些参数。 FixedLengthWriter 暴露 Configuration 属性,它是 ChoFixedLengthRecordConfiguration 对象。使用此属性,您可以执行自定义。

清单 11.1 运行时自定义 FixedLengthWriter

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 ChoFixedLengthWriter("Emp.txt"))
        {
            parser.Configuration.ColumnCountStrict = true;
            parser.Write(objs);
        }
    }
}

12. 使用动态对象

到目前为止,本文解释了如何将 FixedLengthWriter 与 POCO 对象一起使用。 FixedLengthWriter 还支持在没有 POCO 实体对象的情况下生成 FixedLength 文件。它利用 .NET 动态特性。下面的示例展示了如何使用动态对象生成 FixedLength 流。 FixedLength 模式是从第一个对象确定的。如果在动态对象成员值中发现不匹配,将引发错误并停止生成过程。

下面的示例展示了这一点

清单 12.1 从动态对象生成 FixedLength 文件

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 ChoFixedLengthWriter("Emp.txt"))
        {
            parser.Configuration.ColumnCountStrict = true;
            parser.Write(objs);
        }
    }
}

13. 异常

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

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

14. 提示

如果 POCO 对象属性包含换行符/单引号/字段分隔符,则必须将 QuoteField 指定为 true 以优雅地处理这种情况。否则,将引发错误并停止处理。

清单 14.1.1 FixedLength 文件中的多行列值

[ChoFixedLengthFileHeader]
[ChoFixedLengthRecordObject]
public class EmployeeRec
{
    [ChoFixedLengthRecordField(0, 8)]
    [Required]
    [ChoFallbackValue(100)]
    [Range(100, 10000)]
    public int? Id
    {
        get;
        set;
    }
    [ChoFixedLengthRecordField(8, 10, 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; }
}

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

    [ChoFixedLengthRecordField(8, 10, QuoteField = true)]
    [StringLength(1)]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
    public bool AfterRecordFieldWrite(object target, int index, string propName, object value)
    {
        throw new NotImplementedException();
    }

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

    public bool BeforeRecordFieldWrite
    (object target, int index, string propName, ref object value)
    {
        throw new NotImplementedException();
    }

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

    public bool BeginWrite(object source)
    {
        throw new NotImplementedException();
    }

    public void EndWrite(object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordFieldWriteError
      (object target, int index, string propName, object value, Exception ex)
    {
        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;
    }
 
    public void Validate(object target)
    {
    }
 
    public void ValidateFor(object target, string memberName)
    {
    }
}

在上述 EmployeeRec 中是数据类。它仅包含特定于领域的属性和操作。将其标记为一个非常简单的类以供查看。

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

16. 配置选项

如果 POCO 实体类是自动生成的类或通过库公开,或者它是一个 sealed 类,则会限制您以声明方式附加 FixedLength 模式定义。在这种情况下,您可以选择以下选项之一来指定 FixedLength 布局配置

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

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

清单 16.1 密封 POCO 实体类

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

16.1 手动配置

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

图 16.1.1 手动配置

ChoFixedLengthRecordConfiguration config = new ChoFixedLengthRecordConfiguration();
config.FixedLengthFileHeaderConfiguration.HasHeaderRecord = true;
config.ThrowAndStopOnMissingField = true;
config.FixedLengthRecordFieldConfigurations.Add
(new ChoFixedLengthRecordFieldConfiguration("Id", 0, 8, fieldType: typeof(int)));
config.FixedLengthRecordFieldConfigurations.Add
(new ChoFixedLengthRecordFieldConfiguration("Name", 8, 10));

16.2 自动映射配置

这是一种替代方法,并且是错误率较低的方法,用于自动映射 POCO 实体类的 FixedLength 列。

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

清单 16.2.1 Auto Map 类

public class EmployeeRecMap
{
    [ChoFixedLengthRecordField(0, 8, FieldName = "id")]
    public int Id { get; set; }
 
    [ChoFixedLengthRecordField(8, 10, FieldName = "Name")]
    public string Name { get; set; } 
}

然后,您可以使用它通过 ChoFixedLengthRecordConfiguration.MapRecordFields 方法自动映射 FixedLength 列。

清单 16.2.2 使用 Auto Map 配置

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

foreach (var e in new ChoFixedLengthReader<EmployeeRec>("Emp.txt", config)) 
    Console.WriteLine(e.ToString());

16.3 附加 MetadataType 类

这是为 POCO 实体对象附加 MetadataType 类的另一种方法。以前的方法只关心 FixedLength 列的自动映射。其他配置属性,如属性转换器、解析器参数、默认/回退值等,均未考虑。

此模型通过定义 MetadataType 类并声明性地指定 FixedLength 配置参数来考虑所有因素。当您的 POCO 实体是 sealed不是 partial 类时,这很有用。此外,它是配置 POCO 实体 FixedLength 解析的有利且错误率较低的方法之一。

清单 16.3.1 定义 MetadataType 类

[ChoFixedLengthFileHeader()]
[ChoFixedLengthRecordObject
(Encoding = "Encoding.UTF32", ErrorMode = ChoErrorMode.ReportAndContinue,
IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, ThrowAndStopOnMissingField = false, 
    ObjectValidationMode = ChoObjectValidationMode.MemberLevel)]
public class EmployeeRecMeta : IChoNotifyRecordWrite, IChoValidatable
{
    [ChoFixedLengthRecordField
    (0, 8, FieldName = "id", ErrorMode = ChoErrorMode.ReportAndContinue )]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, 1, ErrorMessage = "Id must be > 0.")]
    //[ChoFallbackValue(1)]
    public int Id { get; set; }
    [ChoFixedLengthRecordField(8, 10, FieldName = "Name", QuoteField = true)]
    [StringLength(1)]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
    public bool AfterRecordFieldWrite(object target, int index, string propName, object value)
    {
        throw new NotImplementedException();
    }

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

    public bool BeforeRecordFieldWrite
    (object target, int index, string propName, ref object value)
    {
        throw new NotImplementedException();
    }

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

    public bool BeginWrite(object source)
    {
        throw new NotImplementedException();
    }

    public void EndWrite(object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordFieldWriteError
         (object target, int index, string propName, object value, Exception ex)
    {
        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;
    }
 
    public void Validate(object target)
    {
    }
 
    public void ValidateFor(object target, string memberName)
    {
    }
}

清单 16.3.2 附加 MetadataType 类

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

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

17. ToText 辅助方法

这是一个巧妙的辅助方法,用于从对象列表中生成 FixedLength 格式化输出。它帮助您在测试环境中快速运行和尝试不同的选项以查看 FixedLength 输出。

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(ChoFixedLengthWriter.ToText(objs));
}

18. Writing DataReader 辅助方法

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

static void WriteDataReaderTest()
{
    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);
    IDataReader dr = cmd.ExecuteReader();
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthWriter(writer, config))
    {
        parser.Write(dr);
 
        writer.Flush();
        stream.Position = 0;
 
        Console.WriteLine(reader.ReadToEnd());
    }
}

19. Writing DataTable 辅助方法

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

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);
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthWriter(writer, config))
    {
        parser.Write(dt);
 
        writer.Flush();
        stream.Position = 0;
 
        Console.WriteLine(reader.ReadToEnd());
    }
}

20. 高级主题

20.1 覆盖转换器格式规范

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

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

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

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

ChoTypeConverterFormatSpec 是一个单例类,其实例通过 'Instance' static 成员公开。它是线程本地的,这意味着每个线程都会保留一个独立的实例副本。

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

通过 ChoTypeConverterFormatSpec 指定每个内置数据类型格式说明符将影响系统范围,即,将 ChoTypeConverterFormatSpec.IntNumberStyle = NumberStyles.AllowParentheses 将影响 FixedLength 对象的所有整数成员以允许括号。如果您想覆盖此行为并控制特定的 FixedLength 数据成员以处理其自己的从全局系统范围设置解析 FixedLength 值的独特方式,可以通过在 FixedLength 字段成员级别指定 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; }
}

下面的示例展示了如何使用 FixedLengthReader 加载具有 'se-SE'(瑞典)特定文化数据的 FixedLength 数据流。此外,输入提要附带包含括号的 '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);
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthWriter(writer).WithFirstLineHeader())
    {
        parser.Write(objs);
 
        writer.Flush();
        stream.Position = 0;
 
        Console.WriteLine(reader.ReadToEnd());
    }
}

20.2 货币支持

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

清单 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);
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = 
    new ChoFixedLengthWriter(writer).WithFirstLineHeader().QuoteAllFields())
    {
        parser.Write(objs);
 
        writer.Flush();
        stream.Position = 0;
 
        Console.WriteLine(reader.ReadToEnd());
    }
}

上面的示例展示了如何使用动态对象模型输出货币值。由于货币输出将具有千位逗号分隔符,这将导致无法生成 FixedLength 文件。为了解决这个问题,我们指定写入器引用所有字段。

PS:货币值的格式由 FixedLengthReader 通过 ChoRecordConfiguration.Culture 和 ChoTypeConverterFormatSpec.CurrencyFormat 确定。

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

清单 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);
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = 
    new ChoFixedLengthWriter<EmployeeRecWithCurrency>(writer).WithFirstLineHeader().
        WithField("Id", 0, 5).
        WithField("Name", 5, 20).
        WithField("Salary", 25, 10))
    {
        parser.Write(objs);
 
        writer.Flush();
        stream.Position = 0;
 
        Console.WriteLine(reader.ReadToEnd());
    }
}

20.3 枚举支持

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

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

有 3 种可能的值可以使用

  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);
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthWriter(writer).WithFirstLineHeader().
        WithField("Id", 0, 5).
        WithField("Name", 5, 20).
        WithField("JoinedDate", 25, 10).
        WithField("IsActive", 35, 1).
        WithField("Salary", 36, 10).
        WithField("Status", 46, 10)
        )
 
    {
        parser.Write(objs);
 
        writer.Flush();
        stream.Position = 0;
 
        Console.WriteLine(reader.ReadToEnd());
    }
}

20.4 布尔值支持

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

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

有 4 种可能的值可以使用

  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);
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = 
    new ChoFixedLengthWriter(writer).WithFirstLineHeader().WithField("Id", 0, 5).
        WithField("Name", 5, 20).
        WithField("JoinedDate", 25, 10).
        WithField("IsActive", 35, 1).
        WithField("Salary", 36, 10).
        WithField("Status", 46, 10)
        )
    {
        parser.Write(objs);
 
        writer.Flush();
        stream.Position = 0;
 
        Console.WriteLine(reader.ReadToEnd());
    }
}

20.5 DateTime 支持

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

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

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

清单 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);
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthWriter(writer).WithFirstLineHeader().
        WithField("Name", 5, 20).
        WithField("JoinedDate", 25, 10).
        WithField("IsActive", 35, 1).
        WithField("Salary", 36, 10).
        WithField("Status", 46, 10)
        )
    {
        parser.Write(objs);
 
        writer.Flush();
        stream.Position = 0;
 
        Console.WriteLine(reader.ReadToEnd());
    }
}

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

注意:由于 datetime 值包含 FixedLength 分隔符,我们指示写入器引用所有字段。

 

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

© . All rights reserved.