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

Cinchoo ETL - FixedLength 读取器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2017年1月23日

CPOL

29分钟阅读

viewsIcon

20883

.NET 的简单固定长度文件读取器

目录

  1. 引言
  2. 要求
  3. “Hello World!”示例
    1. 快速加载 - 数据优先方法
    2. 代码优先方法
    3. 配置优先方法
    4. 带声明式配置的代码优先方法
  4. 读取所有记录
  5. 手动读取记录
  6. 自定义 FixedLength 记录
  7. 自定义 FixedLength 头部
  8. 自定义 FixedLength 字段
    1. 默认值
    2. ChoFallbackValue
    3. 类型转换器
      1. 声明式方法
      2. 配置方法
    4. 验证
  9. 回调机制
    1. BeginLoad
    2. EndLoad
    3. BeforeRecordLoad
    4. AfterRecordLoad
    5. RecordLoadError
    6. BeforeRecordFieldLoad
    7. AfterRecordFieldLoad
    8. RecordLoadFieldError
  10. 自定义
  11. AsDataReader 辅助方法
  12. AsDataTable 辅助方法
  13. 使用动态对象
    1. 默认值
    2. ChoFallbackValue
    3. 字段类型
    4. 类型转换器
    5. 验证
  14. 使用密封的 POCO 对象
  15. 异常
  16. 技巧
    1. 多行 FixedLength 列值
    2. 带单引号的 FixedLength 列值
  17. 使用 MetadataType 注解
  18. 配置选项
    1. 手动配置
    2. 自动映射配置
    3. 附加 MetadataType 类
  19. LoadText 辅助方法
  20. 高级主题 (Advanced Topics)
    1. 覆盖转换器格式规范
    2. 货币支持
    3. 枚举支持
    4. 布尔值支持
    5. 日期时间支持
  21. Fluent API
    1. WithRecordLength
    2. WithFirstLineHeader
    3. WithField
    4. QuoteAllFields
  22. 历史

1. 引言

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

本文讨论了使用 ChoETL 框架提供的 FixedLengthReader 组件。它是一个简单的实用类,用于从文件/源中抽取 FixedLength 数据。

特点

  • 遵循 FixedLength 标准文件规则。优雅地处理包含逗号和换行符的数据字段。
  • 公开对象的 IEnumarable 列表 - 通常与 LINQ 查询一起用于投影、聚合和过滤等操作
  • 支持延迟读取
  • 支持处理具有特定文化背景的日期、货币和数字格式的文件。
  • 支持不同的字符编码
  • 读取文件时识别各种日期、货币、枚举、布尔值和数字格式
  • 在写入文件时提供对日期、货币、枚举、布尔值、数字格式的精细控制
  • 详细且强大的错误处理,让您能够快速查找和修复问题
  • 缩短您的开发时间

2. 要求

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

3. “Hello World!” 示例

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

让我们先来看一个简单的例子,读取一个记录长度为 18 个字符、包含 2 列(Id:8 个字符长,Name:10 个字符长)的 FixedLength 文件。

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

Id      Name      
1       Carl      
2       Mark      

您可以通过多种方式以最少的设置开始 FixedLength 文件解析。

3.1. 快速加载 - 数据优先方法

这是快速加载 FixedLength 文件的方法。不需要 POCO 对象。下面的示例代码展示了如何加载文件。

清单 3.1.1 使用迭代器加载 FixedLength 文件

static void QuickDynamicLoadTestUsingIterator()
{
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    {
        writer.WriteLine("Id      Name      ");
        writer.WriteLine("1       Carl      ");
        writer.WriteLine("2       Mark      ");
        writer.Flush();
        stream.Position = 0;
 
        foreach (dynamic row in new ChoFixedLengthReader(reader).WithFirstLineHeader())
        {
            Console.WriteLine(String.Format("Id: {0}", row.Id));
            Console.WriteLine(String.Format("Name: {0}", row.Name));
        }
    }
}

清单 3.1.2 使用循环加载 FixedLength 文件

static void QuickDynamicLoadTest()
{
    dynamic row = null;
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthReader(reader).WithFirstLineHeader())
    {
        writer.WriteLine("Id      Name      ");
        writer.WriteLine("1       Carl      ");
        writer.WriteLine("2       Mark      ");
        writer.Flush();
        stream.Position = 0;
 
        while ((row = parser.Read()) != null)
        {
            Console.WriteLine(String.Format("Id: {0}", row.Id));
            Console.WriteLine(String.Format("Name: {0}", row.Name));
        }
    }
}

此模型使用通用的 FixedLengthReader 对象来解析文件。FixedLengthReader 会自动从第一行自动检测文件布局模式,仅当第一行的列值之间有空格时。如果自动检测导致文件布局模式不正确,您可以通过配置自行指定模式。稍后会讨论。

如果输入源没有头部,FixedLengthReader 会在动态对象模型中自动将列命名为 Column1, Column2 ...

3.2. 代码优先方法

这是使用 POCO 类解析和加载 FixedLength 文件的另一种方法。首先,定义一个简单的数据类来匹配底层 FixedLength 文件布局。

图 3.2.1 简单的 POCO 实体类

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

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

清单 3.2.2 加载 FixedLength 文件

static void CodeFirstApproach()
{
    EmployeeRecSimple row = null;
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthReader<EmployeeRecSimple>
          (reader).WithFirstLineHeader())
    {
        writer.WriteLine("Id      Name      ");
        writer.WriteLine("1       Carl      ");
        writer.WriteLine("2       Mark      ");
        writer.Flush();
        stream.Position = 0;
 
        while ((row = parser.Read()) != null)
        {
            Console.WriteLine(String.Format("Id: {0}", row.Id));
            Console.WriteLine(String.Format("Name: {0}", row.Name));
        }
    }
}

如果头部行的列值之间有空格,FixedLengthReader 会自动从第一行检测文件布局模式。如果自动检测导致文件布局模式不正确,您可以通过配置自行指定模式。

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) 
{ FieldType = typeof(string) });

在上面,我们定义了与示例 FixedLength 文件模板匹配的配置。

清单 3.3.2 不带 POCO 对象加载 FixedLength 文件

static void ConfigFirstApproachReadAsDynamicRecords()
{
    ChoFixedLengthRecordConfiguration config = new ChoFixedLengthRecordConfiguration();
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Id", 0, 8) 
    { FieldType = typeof(int) });
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Name", 8, 10) 
    { FieldType = typeof(string) });
 
    dynamic row = null;
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthReader(reader, config).WithFirstLineHeader())
    {
        writer.WriteLine("Id      Name      ");
        writer.WriteLine("1       Carl      ");
        writer.WriteLine("2       Mark      ");
        writer.Flush();
        stream.Position = 0;
 
        while ((row = parser.Read()) != null)
        {
            Console.WriteLine(String.Format("Id: {0}", row.Id));
            Console.WriteLine(String.Format("Name: {0}", row.Name));
        }
    }
}

清单 3.3.3 带 POCO 对象加载 FixedLength 文件

static void ConfigFirstApproachReadAsTypedRecords()
{
    ChoFixedLengthRecordConfiguration config = new ChoFixedLengthRecordConfiguration();
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Id", 0, 8) 
                       { FieldType = typeof(int) });
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Name", 8, 10) 
                       { FieldType = typeof(string) });
 
    EmployeeRecSimple row = null;
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = 
    new ChoFixedLengthReader<EmployeeRecSimple>(reader, config).WithFirstLineHeader())
    {
        writer.WriteLine("Id      Name      ");
        writer.WriteLine("1       Carl      ");
        writer.WriteLine("2       Mark      ");
        writer.Flush();
        stream.Position = 0;
 
        while ((row = parser.Read()) != null)
        {
            Console.WriteLine(String.Format("Id: {0}", row.Id));
            Console.WriteLine(String.Format("Name: {0}", row.Name));
        }
    }
}

3.4. 代码优先声明式配置

这是定义 POCO 实体类以及声明式装饰的 FixedLength 配置参数的组合方法。

清单 3.4.1 定义 POCO 对象

public partial class EmployeeRec
{
    [ChoFixedLengthRecordField(0, 8)]
    public int Id { get; set; }
    [ChoFixedLengthRecordField(8, 10)]
    public string Name { get; set; }
}

上面的代码说明了如何定义 POCO 对象来承载输入文件中每条记录行的值。首先,使用 ChoFixedLengthRecordFieldAttribute 为每个记录字段定义属性,以符合 FixedLength 记录映射。每个属性必须指定 StartIndex, Size 才能映射到 FixedLength 列。StartIndex 是从 0 开始的。

它非常简单,并且可以立即提取 FixedLength 数据。

清单 3.4.2 主方法

static void CodeFirstWithDeclarativeApproachRead()
{
    EmployeeRec row = null;
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = 
           new ChoFixedLengthReader<EmployeeRec>(reader).WithFirstLineHeader())
    {
        writer.WriteLine("Id      Name      ");
        writer.WriteLine("1       Carl      ");
        writer.WriteLine("2       Mark      ");
        writer.Flush();
        stream.Position = 0;
 
        while ((row = parser.Read()) != null)
        {
            Console.WriteLine(String.Format("Id: {0}", row.Id));
            Console.WriteLine(String.Format("Name: {0}", row.Name));
        }
    }
}

我们通过创建一个 ChoFixedLengthReader 对象的新实例开始。就是这样。解析器在后台完成了将 FixedLength 数据流解析和加载到对象中的所有繁重工作。

默认情况下,FixedLengthReader 在加载 FixedLength 文件时会自动检测并使用默认配置参数。这些可以根据您的需求进行覆盖。以下各节将详细介绍每个配置属性。

4. 读取所有记录

它就像设置与 FixedLength 文件结构匹配的 POCO 对象一样简单,您可以将整个文件作为可枚举模式读取。这是一种延迟执行模式,但在对其进行任何聚合操作时要格外小心。这会将整个文件的记录加载到内存中。

清单 4.1 读取 FixedLength 文件

foreach (var e in new ChoFixedLengthReader<EmployeeRec>("Emp.txt"))
{
    Console.WriteLine(String.Format("Id: {0}", e.Id));
    Console.WriteLine(String.Format("Name: {0}", e.Name));
}

清单 4.2 读取 FixedLength 文件流

foreach (var e in new ChoFixedLengthReader<EmployeeRec>(textReader))
{
    Console.WriteLine(String.Format("Id: {0}", e.Id));
    Console.WriteLine(String.Format("Name: {0}", e.Name));
}

此模型使您的代码优雅、简洁、易于阅读和维护。还可以利用 LINQ 扩展方法执行分组、连接、投影、聚合等操作。

清单 4.3 使用 LINQ

var list = (from o in new ChoFixedLengthReader<EmployeeRec>("Emp.txt")
           where o.Name != null && o.Name.StartsWith("R")
           select o).ToArray();
 
foreach (var e in list)
    Console.WriteLine(e.ToStringEx());

5. 手动读取记录

就像设置与 FixedLength 文件结构匹配的 POCO 对象一样简单,您可以将整个文件作为可枚举模式读取。

清单 5.1 读取 FixedLength 文件

var reader = new ChoFixedLengthReader<EmployeeRec>("Emp.txt");
var rec = (object)null;
 
while ((rec = reader.Read()) != null)
{
    Console.WriteLine(String.Format("Id: {0}", rec.Id));
    Console.WriteLine(String.Format("Name: {0}", rec.Name));
}

6. 自定义 FixedLength 记录

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

图 6.1 为每条记录自定义 POCO 对象

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

以下是用于定制 FixedLength 文件加载操作的可用属性。

  • RecordLength - 可选。如果未指定,则将从 POCO 类中定义的所有属性计算。
  • EOLDelimiter - 用于分隔 FixedLength 行的值。默认值为 \r\n (NewLine)。
  • CultureName - 用于读取和写入 FixedLength 数据的区域名称(例如,en-USen-GB)。
  • IgnoreEmptyLine - 一个标志,告知读取器在读取时是否应跳过空记录。如果所有字段都为空,则认为记录为空。
  • Comments - 用于表示被注释掉的行的值。可以指定多个注释。必须用逗号分隔。
  • QuoteChar - 用于转义包含分隔符、引号或行结尾的字段的值。
  • QuoteAllFields - 读取器不适用。
  • Encoding - FixedLength 文件的编码。
  • HasExcelSeperator - 读取器不适用。读取器会无缝识别 FixedLength 文件中指定的 Excel 分隔符,并将其用于解析。
  • ColumnCountStrict - 此标志指示在读取预期字段丢失时是否应抛出异常。
  • ColumnOrderStrict - 此标志指示在文件中的错误位置读取预期字段时是否应引发异常。仅当 ColumnCountStricttrue 时执行此检查。
  • BufferSize - 在从 StreamReader 读取时使用的内部缓冲区的大小。
  • ErrorMode - 此标志指示在读取时是否应抛出异常,并且预期的字段加载失败。这可以每隔属性覆盖。可能的值是
    • IgnoreAndContinue - 忽略错误,跳过记录并继续处理下一条。
    • ReportAndContinue - 如果 POCO 实体是 IChoNotifyRecordRead 类型,则向其报告错误
    • ThrowAndStop - 抛出错误并停止执行
  • IgnoreFieldValueMode - 一个标志,告知读取器在读取时是否应跳过空/null 的记录。这可以每隔属性覆盖。可能的值是
    • Null - N/A
    • DBNull - N/A
    • Empty - 如果记录值为空,则跳过
    • WhiteSpace - 如果记录值仅包含空格,则跳过
  • ObjectValidationMode - 一个标志,告知读取器关于记录对象要执行的验证类型。可能的值是
    • Off - 不执行对象验证
    • MemberLevel - 在加载每个 FixedLength 属性时执行验证
    • ObjectLevel - 在所有属性加载到 POCO 对象后执行验证

7. 自定义 FixedLength 头部

如果 FixedLength 文件包含头部,您可以使用 ChoFixedLengthFileHeaderAttribute 来指示 POCO 实体。

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

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

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

  • FillChar - 对读取器无效
  • Justification - 对读取器无效
  • TrimOption - 此标志告诉读取器在读取 FixedLength 列头部时修剪开头和结尾的空格。可能的值包括 TrimTrimStartTrimEnd
  • Truncate - 对读取器无效

8. 自定义 FixedLength 字段

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

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

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

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

  • StartIndex - 固定长度行中列的零基起始字符位置。
  • Size - 列中的字符数。
  • FieldName - 按名称映射时,您需要指定要用于该属性的 FixedLength 列的名称。为此,FixedLength 文件必须有头部记录。您指定的名称必须与头部记录的名称匹配。
  • FillChar - 对读取器无效。
  • FieldValueJustification - 对读取器无效。
  • FieldValueTrimOption - 此标志指示读取器在读取时修剪字段值开头和结尾的空白。可能的值是 TrimTrimStartTrimEnd
  • Truncate - 读取器不适用。
  • Size - 读取器不适用。
  • QuoteField - 一个标志,告诉读取器 FixedLength 列值被引号包围。
  • ErrorMode - 此标志指示在读取时是否应抛出异常,并且预期的字段加载失败。可能的值是
    • IgnoreAndContinue - 忽略错误,继续加载记录的其他属性。
    • ReportAndContinue - 如果 POCO 实体是 IChoRecord 类型,则向其报告错误。
    • ThrowAndStop - 抛出错误并停止执行。
  • IgnoreFieldValueMode - 一个标志,告知读取器在读取时是否应跳过空/null 的记录。可能的值是
    • Null - N/A
    • DBNull - N/A
    • Empty - 如果记录值为空,则跳过
    • WhiteSpace - 如果记录值仅包含空格,则跳过

8.1. DefaultValue

FixedLength 值为空或为空白时,用于并设置为属性的值(通过 IgnoreFieldValueMode 控制)。

可以使用 System.ComponentModel.DefaultValueAttribute 为任何 POCO 实体属性指定默认值。

8.2. ChoFallbackValue

FixedLength 值未能设置时,用于并设置为属性的值。Fallback 值仅在 ErrorModeIgnoreAndContinueReportAndContinue 时设置。

可以使用 ChoETL.ChoFallbackValueAttribute 为任何 POCO 实体属性指定回退值。

8.3. 类型转换器

大多数基本类型会自动转换并设置为属性。如果 FixedLength 字段的值无法自动转换为属性的类型,您可以指定自定义/内置 .NET 转换器来转换值。这些可以是 IValueConverter* 或 TypeConverter* 转换器。

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

  • 声明式方法
  • 配置方法

8.3.1. 声明式方法

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

清单 8.3.1.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.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)
    {
        return value;
    }
}

在上面的示例中,我们定义了自定义 IntConverter 类。并展示了如何将其用于 'Id'* FixedLength 属性。

8.3.2. 配置式方法

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

清单 8.3.2.2 指定 TypeConverters

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

ChoFixedLengthRecordFieldConfiguration idConfig = 
        new ChoFixedLengthRecordFieldConfiguration("Id", 0, 8);
idConfig.AddConverter(new IntConverter());
config.FixedLengthRecordFieldConfigurations.Add(idConfig);

config.FixedLengthRecordFieldConfigurations.Add
       (new ChoFixedLengthRecordFieldConfiguration("Name", 8, 10));

在上面,我们通过 ChoFixedLengthRecordFieldConfiguration 对象中的 AddConverter 辅助方法构造并附加了 IntConverter 到 'Id' 字段。

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

8.4. 验证

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

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

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

在上面的示例中,我们为 Id* 属性使用了 Range 验证属性。为 Name* 属性使用了 Required* 验证属性。FixedLengthReader 在加载期间根据 Configuration.ObjectValidationMode 设置为 ChoObjectValidationMode.MemberLevelChoObjectValidationMode.ObjectLevel 执行验证。

有时,您可能希望覆盖 POCO 类附带的已定义的声明式验证行为,您可以通过 Cinchoo ETL 的配置方法来实现。下面的示例展示了如何覆盖它们。

static void ValidationOverridePOCOTest()
{
    ChoFixedLengthRecordConfiguration config = new ChoFixedLengthRecordConfiguration();
    var idConfig = new ChoFixedLengthRecordFieldConfiguration("Id", 0, 8);
    idConfig.Validators = new ValidationAttribute[] { new RequiredAttribute() };
    config.FixedLengthRecordFieldConfigurations.Add(idConfig);
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Name", 8, 10));
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Salary", 8, 10) 
                             { FieldType = typeof(ChoCurrency) });
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = 
           new ChoFixedLengthReader<EmployeeRecWithCurrency>(reader, config))
    {
        writer.WriteLine(",Carl,$100000");
        writer.WriteLine("2,Mark,$50000");
        writer.WriteLine("3,Tom,1000");
 
        writer.Flush();
        stream.Position = 0;
 
        object rec;
        while ((rec = parser.Read()) != null)
        {
            Console.WriteLine(String.Format("Id: {0}", rec.Id));
            Console.WriteLine(String.Format("Name: {0}", rec.Name));
            Console.WriteLine(String.Format("Salary: {0}", rec.Salary));
        }
    }
}
 
public class EmployeeRecWithCurrency
{
    public int? Id { get; set; }
    public string Name { get; set; }
    public ChoCurrency Salary { get; set; }
}

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

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

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

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

IChoValidatable 接口公开以下方法

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

9. 回调机制

FixedLengthReader 提供开箱即用的行业标准 FixedLength 解析功能,以满足大多数解析需求。* 如果解析不能满足任何需求,您可以使用 FixedLengthReader 提供的回调机制来处理这种情况。为了参与回调机制,POCO 实体对象或 DataAnnotationMetadataType 类型对象必须继承 IChoNotifyRecordRead 接口。

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

IChoNotifyRecordRead 公开了以下方法

  • BeginLoad - 在 FixedLength 文件加载开始时调用
  • EndLoad - 在 FixedLength 文件加载结束时调用
  • BeforeRecordLoad - 在 FixedLength 记录加载之前引发
  • AfterRecordLoad - 在 FixedLength 记录加载之后引发
  • RecordLoadError - 在 FixedLength 记录加载失败时引发
  • BeforeRecordFieldLoad - 在 FixedLength 列值加载之前引发
  • AfterRecordFieldLoad - 在 FixedLength 列值加载之后引发
  • RecordFieldLoadError - 在 FixedLength 列值加载失败时引发

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

[ChoFixedLengthFileHeader]
[ChoFixedLengthRecordObject(18)]
public partial class EmployeeRec : IChoNotifyRecordRead
{
    [ChoFixedLengthRecordField(0, 8, FieldName = "id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
    
    [ChoFixedLengthRecordField(8, 10, FieldName = "Name", QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
 
    public bool AfterRecordFieldLoad(object target, int index, 
                                     string propName, object value)
    {
        throw new NotImplementedException();
    }
 
    public bool AfterRecordLoad(object target, int index, object source)
    {
        throw new NotImplementedException();
    }
 
    public bool BeforeRecordFieldLoad(object target, int index, 
                                      string propName, ref object value)
    {
        throw new NotImplementedException();
    }
 
    public bool BeforeRecordLoad(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }
 
    public bool BeginLoad(object source)
    {
        throw new NotImplementedException();
    }
 
    public void EndLoad(object source)
    {
        throw new NotImplementedException();
    }
 
    public bool RecordFieldLoadError(object target, int index, 
                string propName, object value, Exception ex)
    {
        throw new NotImplementedException();
    }
 
    public bool RecordLoadError
    (object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }
}

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

[ChoFixedLengthFileHeader]
[ChoFixedLengthRecordObject(18)]
public class EmployeeRecMeta : IChoNotifyRecordRead
{
    [ChoFixedLengthRecordField(0, 8, FieldName = "id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }

    [ChoFixedLengthRecordField(8, 10, FieldName = "Name", QuoteField = true)]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
 
    public bool AfterRecordFieldLoad(object target, int index, 
                                     string propName, object value)
    {
        throw new NotImplementedException();
    }
 
    public bool AfterRecordLoad(object target, int index, object source)
    {
        throw new NotImplementedException();
    }
 
    public bool BeforeRecordFieldLoad(object target, int index, 
                                      string propName, ref object value)
    {
        throw new NotImplementedException();
    }
 
    public bool BeforeRecordLoad(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }
 
    public bool BeginLoad(object source)
    {
        throw new NotImplementedException();
    }
 
    public void EndLoad(object source)
    {
        throw new NotImplementedException();
    }
 
    public bool RecordFieldLoadError(object target, int index, 
                string propName, object value, Exception ex)
    {
        throw new NotImplementedException();
    }
 
    public bool RecordLoadError
    (object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }
} 

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

9.1 BeginLoad

此回调在 FixedLength 文件加载的**开始**时调用一次。source* 是 FixedLength 文件流对象。在这里,您可以检查流,返回 true 以继续 FixedLength 加载。返回 false 以停止解析。

清单 9.1.1 BeginLoad 回调示例

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

9.2 EndLoad

此回调在 FixedLength 文件加载的**结束**时调用一次。sourceFixedLength 文件流对象。在这里,您可以检查流,对流执行任何后续步骤。

清单 9.2.1 EndLoad 回调示例

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

9.3 BeforeRecordLoad

此回调在 FixedLength 文件中的每条记录行加载**之前**调用。target 是 POCO 记录对象的实例。index 是文件中的行索引。sourceFixedLength 记录行。在这里,您可以检查行,并根据需要用新行覆盖它。

**提示**:如果您想跳过该行不加载,请将源设置为 null

**提示**:如果您想自己控制记录属性的解析和加载,请将源设置为 String.Empty

 

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

清单 9.3.1 BeforeRecordLoad 回调示例

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

9.4 AfterRecordLoad

此回调在 FixedLength 文件中的每条记录行加载**之后**调用。target 是 POCO 记录对象的实例。index 是文件中的行索引。sourceFixedLength 记录行。在这里,您可以对记录行执行任何后续操作。

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

清单 9.4.1 AfterRecordLoad 回调示例

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

9.5 RecordLoadError

在加载记录行时遇到**错误**时调用此回调。target 是 POCO 记录对象的实例。index 是文件中的行索引。sourceFixedLength 记录行。ex 是异常对象。在这里,您可以处理异常。仅当 Configuration.ErrorModeReportAndContinue 时才调用此方法。

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

清单 9.5.1 RecordLoadError 回调示例

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

9.6 BeforeRecordFieldLoad

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

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

清单 10.6.1 BeforeRecordFieldLoad 回调示例

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

10.7 AfterRecordFieldLoad

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

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

清单 10.7.1 AfterRecordFieldLoad 回调示例

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

9.8 RecordLoadFieldError

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

  • FixedLengthReader 查找每个 FixedLength 属性的 FallbackValue 值。如果存在,它会尝试将其值赋给它。
  • 如果 FallbackValue 值不存在且 Configuration.ErrorMode 指定为 ReportAndContinue,则将执行此回调。

返回 true 以继续 load 过程,否则返回 false 以停止过程。

清单 9.8.1 RecordFieldLoadError 回调示例

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

10. 定制

FixedLengthReader 自动检测并加载 POCO 实体中的配置设置。在运行时,您可以在 FixedLength 解析之前自定义和调整这些参数。FixedLengthReader 暴露 Configuration 属性,它是一个 ChoFixedLengthRecordConfiguration* 对象。使用此属性,您可以自定义它们。

清单 10.1 运行时自定义 FixedLengthReader

class Program
{
    static void Main(string[] args)
    {
        using (var stream = new MemoryStream())
        using (var reader = new StreamReader(stream))
        using (var writer = new StreamWriter(stream))
        using (var parser = new ChoFixedLengthReader<EmployeeRec>(reader))
        {
            writer.WriteLine("1       Carl      ");
            writer.WriteLine("2       Mark      ");

            writer.Flush();
            stream.Position = 0;
 
            object row = null;
  
            parser.Configuration.ColumnCountStrict = true;
            while ((row = parser.Read()) != null)
            {
                Console.WriteLine(String.Format("Id: {0}", row.Id));
                Console.WriteLine(String.Format("Name: {0}", row.Name));
            }
        }
    }

11. AsDataReader 辅助方法

FixedLengthReader 暴露 AsDataReader 辅助方法,以 .NET datareader 对象的形式检索 FixedLength 记录。DataReader 是快速前向数据流。此 datareader 可用于一些地方,例如使用 SqlBulkCopy 将数据批量复制到数据库,加载不连接的 DataTable 等。

清单 11.1 作为 DataReader 读取示例

static void AsDataReaderTest()
{
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthReader<EmployeeRec>(reader))
    {
        writer.WriteLine("1       Carl      ");
        writer.WriteLine("2       Mark      ");
 
        writer.Flush();
        stream.Position = 0;
 
        IDataReader dr = parser.AsDataReader();
        while (dr.Read())
        {
            Console.WriteLine("Id: {0}, Name: {1}", dr[0], dr[1]);
        }
    }
}

12. AsDataTable 辅助方法

FixedLengthReader 暴露 AsDataTable 辅助方法,以 .NET DataTable 对象的形式检索 FixedLength 记录。然后可以将其持久化到磁盘,显示在网格/控件中,或像任何其他对象一样存储在内存中。

清单 12.1 作为 DataTable 读取示例

static void AsDataTableTest()
{
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthReader<EmployeeRec>(reader))
    {
        writer.WriteLine("Id      Name      ");
        writer.WriteLine("1       Carl      ");
 
        writer.Flush();
        stream.Position = 0;
 
        DataTable dt = parser.AsDataTable();
        foreach (DataRow dr in dt.Rows)
        {
            Console.WriteLine("Id: {0}, Name: {1}", dr[0], dr[1]);
        }
    }
}

13. 使用动态对象

到目前为止,本文介绍了使用 POCO 对象进行 FixedLengthReaderFixedLengthReader 还支持加载没有 POCO 对象的 FixedLength 文件。它利用 .NET 动态特性。下面的示例展示了如何在没有 POCO 对象的情况下读取 FixedLength 流。

如果您有 FixedLength 文件,您可以以最少的/零配置解析和加载文件。如果 FixedLength 文件没有头部记录行,解析器会自动将列命名为 Column1Column2 等。

下面的示例展示了这一点

清单 13.1 加载没有头部的 FixedLength 文件示例

class Program
{
    static void Main(string[] args)
    {
        dynamic row;
        using (var stream = new MemoryStream())
        using (var reader = new StreamReader(stream))
        using (var writer = new StreamWriter(stream))
        using (var parser = new ChoFixedLengthReader(reader))
        {
            writer.WriteLine("1       Carl      ");
            writer.WriteLine("2       Mark      ");
            writer.Flush();
            stream.Position = 0;
 
            while ((row = parser.Read()) != null)
            {
                Console.WriteLine(row.Column1);
            }
        }
    }
}

如果 FixedLength 文件有头部,您可以将其在配置中声明为 HasHeaderRecordtrue,然后像下面这样简单地解析文件。

清单 13.2 加载带有头部的 FixedLength 文件示例

class Program
{
    static void Main(string[] args)
    {
        ChoFixedLengthRecordConfiguration config = 
                      new ChoFixedLengthRecordConfiguration();
        config.FixedLengthFileHeaderConfiguration.HasHeaderRecord = true;

        dynamic row;
        using (var stream = new MemoryStream())
        using (var reader = new StreamReader(stream))
        using (var writer = new StreamWriter(stream))
        using (var parser = new ChoFixedLengthReader(reader. config))
        {
            writer.WriteLine("Id      Name      ");
            writer.WriteLine("1       Carl      ");
            writer.WriteLine("2       Mark      ");
            writer.Flush();
            stream.Position = 0;
 
            while ((row = parser.Read()) != null)
            {
                Console.WriteLine(row.Name);
            }
        }
    }
}

上面的示例会自动检测头部中的 FixedLength 列并解析文件。

您可以通过手动添加字段配置并将其传递给 FixedLengthReader 进行文件解析,来覆盖自动检测列的默认行为。

示例展示了如何操作。

清单 13.3 加载带有配置的 FixedLength 文件

class Program
{
    static void Main(string[] args)
    {
        ChoFixedLengthRecordConfiguration config = 
                            new ChoFixedLengthRecordConfiguration();
        config.FixedLengthFileHeaderConfiguration.HasHeaderRecord = true;
        config.FixedLengthRecordFieldConfigurations.Add
               (new ChoFixedLengthRecordFieldConfiguration("Id", 0, 8));
        config.FixedLengthRecordFieldConfigurations.Add
               (new ChoFixedLengthRecordFieldConfiguration("Name", 8, 10));

        dynamic row;
        using (var stream = new MemoryStream())
        using (var reader = new StreamReader(stream))
        using (var writer = new StreamWriter(stream))
        using (var parser = new ChoFixedLengthReader(reader. config))
        {
            writer.WriteLine("Id      Name      ");
            writer.WriteLine("1       Carl      ");
            writer.WriteLine("2       Mark      ");
            writer.Flush();
            stream.Position = 0;
 
            while ((row = parser.Read()) != null)
            {
                Console.WriteLine(row.Name);
            }
        }
    }
}

要完全关闭自动列检测,您需要将 ChoFixedLengthRecordConfiguration.AutoDiscoverColumns* 设置为 false

13.1. DefaultValue

FixedLength 值为空或为空白时,用于并设置为属性的值(通过 IgnoreFieldValueMode 控制)。

可以使用 System.ComponentModel.DefaultValueAttribute 为任何 POCO 实体属性指定默认值。

对于动态对象成员或覆盖声明式 POCO 对象成员的默认值规范,您可以通过配置来实现,如下所示:

ChoFixedLengthRecordConfiguration config = new ChoFixedLengthRecordConfiguration();
config.FixedLengthRecordFieldConfigurations.Add
(new ChoFixedLengthRecordFieldConfiguration("Id", 0, 8));
config.FixedLengthRecordFieldConfigurations.Add
(new ChoFixedLengthRecordFieldConfiguration("Name", 8, 10) 
{ DefaultValue = "NoName" })

13.2. ChoFallbackValue

FixedLength 值未能设置时,用于并设置为属性的值。Fallback 值仅在 ErrorModeIgnoreAndContinueReportAndContinue 时设置。

可以使用 ChoETL.ChoFallbackValueAttribute 为任何 POCO 实体属性指定回退值。

对于动态对象成员或覆盖声明式 POCO 对象成员的回退值,您可以通过配置来实现,如下所示:

ChoFixedLengthRecordConfiguration config = new ChoFixedLengthRecordConfiguration();
config.FixedLengthRecordFieldConfigurations.Add
       (new ChoFixedLengthRecordFieldConfiguration("Id", 0, 8));
config.FixedLengthRecordFieldConfigurations.Add
       (new ChoFixedLengthRecordFieldConfiguration
("Name", 8, 10) { FallbackValue = "Tom" });

13.3. FieldType

在无类型动态对象模型中,读取器读取单个字段值并以 'string' 值填充到动态对象成员中。如果您想强制执行类型并在加载时进行额外的类型检查,可以通过声明字段类型来完成。这可以在字段配置中完成。

清单 8.5.1 定义 FieldType

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

上面的示例展示了为 'Id'* 字段定义字段类型为 'int'。这指示 FixedLengthReader 在分配之前解析并将值转换为整数。这种额外的类型安全性可以防止在解析时加载不正确的值到对象中。

13.4. 类型转换器

大多数基本类型会被 FixedLengthReader 自动转换并设置为属性。如果 FixedLength 字段的值无法自动转换为属性的类型,您可以指定自定义/内置 .NET 转换器来转换值。这些可以是 IValueConverter* 或 TypeConverter* 转换器。

在动态对象模型中,您可以通过配置指定这些转换器。请参阅下面的示例,了解指定 FixedLength 列类型转换器的方法。

清单 13.4.1 指定 TypeConverters

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

ChoFixedLengthRecordFieldConfiguration idConfig = 
        new ChoFixedLengthRecordFieldConfiguration("Id", 0, 8);
idConfig.AddConverter(new IntConverter());
config.FixedLengthRecordFieldConfigurations.Add(idConfig);

config.FixedLengthRecordFieldConfigurations.Add
(new ChoFixedLengthRecordFieldConfiguration("Name", 8, 10));
config.FixedLengthRecordFieldConfigurations.Add
(new ChoFixedLengthRecordFieldConfiguration("Name1", 18, 10));

在上面,我们通过 ChoFixedLengthRecordFieldConfiguration 对象中的 AddConverter 辅助方法构造并附加了 IntConverter 到 'Id' 字段。

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

13.5. 验证

FixedLengthReader 利用 System.ComponentModel.DataAnnotationsValidation Block 验证属性来为各个 FixedLength 字段指定验证规则。有关可用 DataAnnotation 验证属性列表,请参阅 MSDN 站点。

清单 13.5.1 指定验证

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

ChoFixedLengthRecordFieldConfiguration idConfig = 
        new ChoFixedLengthRecordFieldConfiguration("Id", 0, 8);
idConfig.Validators = new ValidationAttribute[] { new RangeAttribute(0, 100) };
config.FixedLengthRecordFieldConfigurations.Add(idConfig);

config.FixedLengthRecordFieldConfigurations.Add
(new ChoFixedLengthRecordFieldConfiguration("Name", 8, 10));
config.FixedLengthRecordFieldConfigurations.Add
(new ChoFixedLengthRecordFieldConfiguration("Name1", 18, 10));

在上面的示例中,我们为 Id* 属性使用了 Range 验证属性。FixedLengthReader 在加载期间根据 Configuration.ObjectValidationMode 设置为 ChoObjectValidationMode.MemberLevelChoObjectValidationMode.ObjectLevel 执行验证。

注意:动态对象模型不支持自验证

14. 处理密封 POCO 对象

如果您已经拥有现有的密封 POCO 对象,或者该对象在第三方库中,我们可以使用它们与 FixedLengthReader。您需要的是带有头部的 FixedLength 文件。

清单 14.1 现有的已密封 POCO 对象

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

清单 14.2 使用 FixedLength 文件

class Program
{
    static void Main(string[] args)
    {
        using (var stream = new MemoryStream())
        using (var reader = new StreamReader(stream))
        using (var writer = new StreamWriter(stream))
        using (var parser = new ChoFixedLengthReader<ThirdPartyRec>
              (reader).WithFirstLineHeader())
        {
            writer.WriteLine("Id      Name      ");
            writer.WriteLine("1       Carl      ");
            writer.WriteLine("2       Mark      ");

            writer.Flush();
            stream.Position = 0;
 
            object row = null;
 
            while ((row = parser.Read()) != null)
            {
                Console.WriteLine(String.Format("Id: {0}", row.Id));
                Console.WriteLine(String.Format("Name: {0}", row.Name));
            }
        }
    }
}

在这种情况下,FixedLengthReaderFixedLength 文件中反向发现 FixedLength 列,并将数据加载到 POCO 对象中。如果 FixedLength 文件结构和 POCO 对象匹配,加载将成功,并将所有相应的数据填充到其属性中。如果任何 FixedLength 列缺少属性,FixedLengthReader 会静默忽略它们,并继续处理其余的。

您可以通过将 ChoFixedLengthRecordConfiguration.ThrowAndStopOnMissingField 属性设置为 false 来覆盖此行为。在这种情况下,如果 FixedLength 列缺少属性,FixedLengthReader 将引发 ChoMissingRecordFieldException 异常。

15. 异常

FixedLengthReader 在不同情况下引发不同类型的异常。

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

16. 技巧

16.1 多行 FixedLength 列值

如果 FixedLength 文件包含带有换行符的列值,ChoFixedLengthReader 可以通过将其包围在引号中来处理。

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

Id      Name      
1       "Carl
's"
2       Mark      

在上例中,Id (1) 的名称是多行并用引号包围的。ChoFixedLengthReader 识别这种情况,并正确加载它们。

16.4 带单引号的 FixedLength 列值

ChoFixedLengthReader 可以无缝地读取带有单引号的 FixedLength 列值。不需要额外的包围引号。

清单 16.3.1 带单引号的 FixedLength 列值

Id,Name
1,Tom Cassawaw
2,Carl'Malcolm
3,Mark

在上例中,Id (2) 的名称包含单引号 (')。ChoFixedLengthReader 识别这种情况,并成功加载这些值。

17. 使用 MetadataType 注解

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

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

清单 17.1 MetadataType 注解用法示例

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

[ChoFixedLengthFileHeader]
[ChoFixedLengthRecordObject(18)]
public class EmployeeRecMeta : IChoNotifyRecordRead, 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 AfterRecordFieldLoad(object target, int index, 
                                     string propName, object value)
    {
        throw new NotImplementedException();
    }
 
    public bool AfterRecordLoad(object target, int index, object source)
    {
        throw new NotImplementedException();
    }
 
    public bool BeforeRecordFieldLoad(object target, int index, 
                                      string propName, ref object value)
    {
        throw new NotImplementedException();
    }
 
    public bool BeforeRecordLoad(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }
 
    public bool BeginLoad(object source)
    {
        throw new NotImplementedException();
    }
 
    public void EndLoad(object source)
    {
        throw new NotImplementedException();
    }
 
    public bool RecordFieldLoadError(object target, int index, 
                string propName, object value, Exception ex)
    {
        throw new NotImplementedException();
    }
 
    public bool RecordLoadError(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 中。

18. 配置选项

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

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

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

清单 18.1 密封 POCO 实体类

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

18.1 手动配置

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

清单 18.1.1 手动配置

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

18.2 自动映射配置

这是一种替代方法,而且是一种不太容易出错的方法,可以为 POCO 实体类自动映射 FixedLength 列。

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

清单 18.2.1 自动映射类

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 列。

清单 18.2.2 使用自动映射配置

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

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

18.3 附加 MetadataType 类

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

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

清单 18.3.1 定义 MetadataType 类

[ChoFixedLengthFileHeader()]
[ChoFixedLengthRecordObject(18)]
public class EmployeeRecMeta : IChoNotifyRecordRead, IChoValidatable
{
    [ChoFixedLengthRecordField(0, 8, FieldName = "id", 
                         ErrorMode = ChoErrorMode.ReportAndContinue )]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, 1, ErrorMessage = "Id must be > 0.")]
    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 AfterRecordFieldLoad(object target, int index, 
                                     string propName, object value)
    {
        throw new NotImplementedException();
    }
 
    public bool AfterRecordLoad(object target, int index, object source)
    {
        throw new NotImplementedException();
    }
 
    public bool BeforeRecordFieldLoad(object target, int index, 
                                      string propName, ref object value)
    {
        throw new NotImplementedException();
    }
 
    public bool BeforeRecordLoad(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }
 
    public bool BeginLoad(object source)
    {
        throw new NotImplementedException();
    }
 
    public void EndLoad(object source)
    {
        throw new NotImplementedException();
    }
 
    public bool RecordFieldLoadError(object target, int index, 
                string propName, object value, Exception ex)
    {
        return true;
    }
 
    public bool RecordLoadError
    (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)
    {
    }
}

清单 18.3.2 附加 MetadataType 类

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

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

19. LoadText 辅助方法

这是一个小巧的辅助方法,用于将 FixedLength 文本字符串解析并加载到对象中。

清单 19.1 使用 LoadText 方法

static void LoadTextTest()
{
    string txt = "Id      Name      \r\n1       Carl      \r\n2       Mark      ";
 
    foreach (dynamic e in ChoFixedLengthReader.LoadText(txt).WithFirstLineHeader())
    {
        Console.WriteLine(String.Format("Id: {0}", e.Id));
        Console.WriteLine(String.Format("Name: {0}", e.Name));
    }
}

20. 高级主题

20.1 覆盖转换器格式规范

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

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

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

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

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

对于每种固有类型,都提供了两组格式说明符成员,一组用于加载,另一组用于写入值,但 BooleanEnumDataTime 类型除外。这些类型只有一组成员用于加载和写入操作。

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

NumberStyles (可选) 用于从 FixedLength 流加载值,而 Format 字符串用于将值写入 FixedLength 流。

在本文中,我将简要介绍如何使用 NumberStyles 从流中加载 FixedLength 数据。这些值是可选的。它在解析 FixedLength 文件时确定允许的每种类型的样式。系统会自动确定如何从基础 Culture 解析和加载值。在特殊情况下,您可能需要覆盖并按您想要的方式设置样式,以成功加载文件。有关 NumberStyles 及其值的更多信息,请参阅 MSDN。

清单 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 UsingFormatSpecs()
{
    ChoFixedLengthRecordConfiguration config = new ChoFixedLengthRecordConfiguration();
    config.Culture = new System.Globalization.CultureInfo("se-SE");
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Id", 0, 8) { FieldType = typeof(int) });
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Name", 8, 10));
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Salary", 18, 20) 
                             { FieldType = typeof(ChoCurrency) });
 
    ChoTypeConverterFormatSpec.Instance.IntNumberStyle = NumberStyles.AllowParentheses;
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthReader(reader, config).WithFirstLineHeader())
    {
        writer.WriteLine("Id      Name      Salary              ");
        writer.WriteLine("1       Carl      12.345679 kr        ");
        writer.WriteLine("2       Mark      50000 kr            ");
 
        writer.Flush();
        stream.Position = 0;
 
        dynamic row = null;
 
        while ((row = parser.Read()) != null)
        {
            Console.WriteLine(String.Format("Id: {0}", row.Id));
            Console.WriteLine(String.Format("Name: {0}", row.Name));
            Console.WriteLine(String.Format("Salary: {0}", row.Salary));
        }
    }
}

20.2 货币支持

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

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

static void CurrencyDynamicTest()
{
    ChoFixedLengthRecordConfiguration config = new ChoFixedLengthRecordConfiguration();
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Id", 0, 8));
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Name", 8, 10));
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Salary", 18, 20) 
    { FieldType = typeof(ChoCurrency) });
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthReader(reader, config))
    {
        writer.WriteLine("Id      Name      Salary              ");
        writer.WriteLine("1       Carl      100000              ");
        writer.WriteLine("2       Mark      250000              ");
 
        writer.Flush();
        stream.Position = 0;
 
        dynamic row;
        while ((row = parser.Read()) != null)
        {
            Console.WriteLine(String.Format("Id: {0}", row.Id));
            Console.WriteLine(String.Format("Name: {0}", row.Name));
            Console.WriteLine(String.Format("Salary: {0}", row.Salary));
        }
    }
}

上面的示例展示了如何使用动态对象模型加载货币值。默认情况下,动态对象的所有成员都被视为 string 类型,除非通过 ChoFixedLengthFieldConfiguration.FieldType 明确指定。通过将 'Salary' FixedLength 字段的字段类型指定为 ChoCurrencyFixedLengthReader 将它们加载为货币对象。

**附注**:货币值的格式由 FixedLengthReader 通过 ChoRecordConfiguration.CultureChoTypeConverterFormatSpec.CurrencyNumberStyle 确定。

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

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

public class EmployeeRecWithCurrency
{
    [ChoFixedLengthRecordField(0, 8)]
    public int Id { get; set; }
    [ChoFixedLengthRecordField(8, 10)]
    public string Name { get; set; }
    [ChoFixedLengthRecordField(18, 28)]
    public ChoCurrency Salary { get; set; }
}
 
static void CurrencyTest()
{
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthReader<EmployeeRecWithCurrency>(reader))
    {
        writer.WriteLine("Id      Name      Salary              ");
        writer.WriteLine("1       Carl      100000              ");
        writer.WriteLine("2       Mark      250000              ");
 
        writer.Flush();
        stream.Position = 0;
 
        dynamic row;
        while ((row = parser.Read()) != null)
        {
            Console.WriteLine(String.Format("Id: {0}", row.Id));
            Console.WriteLine(String.Format("Name: {0}", row.Name));
            Console.WriteLine(String.Format("Salary: {0}", row.Salary));
        }
    }
}

20.3 枚举支持

Cinchoo ETL 会自动处理从 FixedLength 文件中解析 enum 列值。如果您想精细控制这些值的解析,可以通过 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.Value;
 
    ChoFixedLengthRecordConfiguration config = new ChoFixedLengthRecordConfiguration();
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Id", 0, 8) 
         { FieldType = typeof(int) });
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Name", 8, 10));
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Salary", 18, 20) 
         { FieldType = typeof(ChoCurrency) });
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("ET", 38, 2) 
         { FieldType = typeof(EmployeeType) });
 
    ChoTypeConverterFormatSpec.Instance.IntNumberStyle = NumberStyles.AllowParentheses;
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthReader(reader, config))
    {
        writer.WriteLine("Id      Name      Salary              ET");
        writer.WriteLine("1       Carl      100000              0");
        writer.WriteLine("2       Mark      250000              2");
 
        writer.Flush();
        stream.Position = 0;
 
        dynamic row = null;
 
        while ((row = parser.Read()) != null)
        {
            Console.WriteLine(String.Format("Id: {0}", row.Id));
            Console.WriteLine(String.Format("Name: {0}", row.Name));
            Console.WriteLine(String.Format("Salary: {0}", row.Salary));
        }
    }
}

20.4 布尔值支持

Cinchoo ETL 会自动处理从 FixedLength 文件中解析布尔值 FixedLength 列值。如果您想精细控制这些值的解析,可以通过 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 解析期间指定 Boolean 格式说明符

static void BoolTest()
{
    ChoTypeConverterFormatSpec.Instance.BooleanFormat = ChoBooleanFormatSpec.ZeroOrOne;
 
    ChoFixedLengthRecordConfiguration config = new ChoFixedLengthRecordConfiguration();
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Id", 0, 8) 
    { FieldType = typeof(int) });
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Name", 8, 10));
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Salary", 18, 20) 
                             { FieldType = typeof(ChoCurrency) });
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("AT", 38, 40) 
                             { FieldType = typeof(bool) });
 
    ChoTypeConverterFormatSpec.Instance.IntNumberStyle = NumberStyles.AllowParentheses;
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthReader(reader, config))
    {
        writer.WriteLine("Id      Name      Salary              AT");
        writer.WriteLine("1       Carl      100000              0");
        writer.WriteLine("2       Mark      250000              1");
 
        writer.Flush();
        stream.Position = 0;
 
        dynamic row = null;
 
        while ((row = parser.Read()) != null)
        {
            Console.WriteLine(String.Format("Id: {0}", row.Id));
            Console.WriteLine(String.Format("Name: {0}", row.Name));
            Console.WriteLine(String.Format("Salary: {0}", row.Salary));
            Console.WriteLine(String.Format("AT: {0}", row.AT));
        }
    }
}

20.5 DateTime 支持

Cinchoo ETL 会使用系统区域设置或自定义设置的区域设置,自动处理从 FixedLength 文件中解析日期时间 FixedLength 列值。如果您想精细控制这些值的解析,可以通过 ChoTypeConverterFormatSpec.DateTimeFormat 全局指定它们。默认值为 'd'。

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

您可以使用任何有效的 标准自定义 日期时间 .NET 格式说明符来解析文件中的日期时间 FixedLength 值。

清单 20.5.1 解析期间指定 Datetime 格式说明符

static void DateTimeTest()
{
    ChoTypeConverterFormatSpec.Instance.DateTimeFormat = "d";
 
    ChoFixedLengthRecordConfiguration config = new ChoFixedLengthRecordConfiguration();
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Id", 0, 8) { FieldType = typeof(int) });
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Name", 8, 10));
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("Salary", 18, 20) 
                        { FieldType = typeof(ChoCurrency) });
    config.FixedLengthRecordFieldConfigurations.Add
    (new ChoFixedLengthRecordFieldConfiguration("JoinedDate", 38, 10) 
                        { FieldType = typeof(DateTime) });
 
    ChoTypeConverterFormatSpec.Instance.IntNumberStyle = NumberStyles.AllowParentheses;
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthReader(reader, config))
    {
        writer.WriteLine("Id      Name      Salary              JoinedDate");
        writer.WriteLine("1       Carl      100000              01/01/2001");
        writer.WriteLine("2       Mark      250000              12/23/1996");
 
        writer.Flush();
        stream.Position = 0;
 
        dynamic row = null;
 
        while ((row = parser.Read()) != null)
        {
            Console.WriteLine(String.Format("Id: {0}", row.Id));
            Console.WriteLine(String.Format("Name: {0}", row.Name));
            Console.WriteLine(String.Format("Salary: {0}", row.Salary));
            Console.WriteLine(String.Format("JoinedDate: {0}", row.JoinedDate));
        }
    }
}

21. Fluent API

FixedLengthReader 通过 Fluent API 方法公开了几个常用的配置参数。这将使 FixedLength 文件解析的编程更快。

21.1 WithRecordLength

此 API 方法设置固定长度数据文件的记录长度。

static void QuickDynamicTest()
{
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthReader(reader).WithRecordLength(18))
    {
        writer.WriteLine("1       Carl      ");
        writer.WriteLine("2       Mark      ");
 
        writer.Flush();
        stream.Position = 0;
 
        dynamic row;
        while ((row = parser.Read()) != null)
        {
            Console.WriteLine(String.Format("Id: {0}", row.Id));
            Console.WriteLine(String.Format("Name: {0}", row.Name));
        }
    }
}

21.2 WithFirstLineHeader

此 API 方法标志着 FixedLength 文件是否包含第一行作为头部。可选的布尔参数指定第一行是头部还是非头部。默认值为 true

static void QuickDynamicTest()
{
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthReader(reader).WithFirstLineHeader())
    {
        writer.WriteLine("Id      Name      ");
        writer.WriteLine("1       Carl      ");
        writer.WriteLine("2       Mark      ");
 
        writer.Flush();
        stream.Position = 0;
 
        dynamic row;
        while ((row = parser.Read()) != null)
        {
            Console.WriteLine(String.Format("Id: {0}", row.Id));
            Console.WriteLine(String.Format("Name: {0}", row.Name));
        }
    }
}

21.3 WithField

此 API 方法用于添加 FixedLength 列,包含 StartIndexSize 和/或日期类型。此方法在动态对象模型中很有用,通过为每个单独的 CSV 列指定适当的 datatype

static void QuickDynamicTest()
{
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoFixedLengthReader(reader).WithFirstLineHeader().WithField
    ("Id", 0, 8, typeof(ini).WithField("Name", 8, 10, typeof(string))
    {
        writer.WriteLine("Id      Name      ");
        writer.WriteLine("1       Carl      ");
        writer.WriteLine("2       Mark      ");
 
        writer.Flush();
        stream.Position = 0;
 
        dynamic row;
        while ((row = parser.Read()) != null)
        {
            Console.WriteLine(String.Format("Id: {0}", row.Id));
            Console.WriteLine(String.Format("Name: {0}", row.Name));
        }
    }
}

21.4 QuoteAllFields

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

static void QuickDynamicTest()
{
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = 
    new ChoFixedLengthReader(reader).WithFirstLineHeader().QuoteAllFields())
    {
        writer.WriteLine("Id      Name      ");
        writer.WriteLine("1       Carl      ");
        writer.WriteLine("2       Mark      ");
 
        writer.Flush();
        stream.Position = 0;
 
        dynamic row;
        while ((row = parser.Read()) != null)
        {
            Console.WriteLine(String.Format("Id: {0}", row.Id));
            Console.WriteLine(String.Format("Name: {0}", row.Name));
        }
    }
}

22. 历史记录

  • 2017 年 1 月 23 日:初始版本
© . All rights reserved.