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

Cinchoo ETL - YAML 读取器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2020年7月1日

CPOL

34分钟阅读

viewsIcon

6904

.NET 的简单 YAML 读取器

目录

  1. 引言
  2. 要求
  3. “Hello World!”示例
    1. 快速加载 - 数据优先方法
    2. 代码优先方法
    3. 配置优先方法
    4. 带声明式配置的代码优先方法
  4. 读取所有记录
  5. 手动读取记录
  6. 自定义 YAML 记录
  7. 自定义 YAML 字段
    1. 默认值
    2. ChoFallbackValue
    3. 类型转换器
      1. 声明式方法
      2. 配置方法
    4. 验证
  8. 回调机制
    1. 使用 ChoYamlReader 事件
    2. 实现 IChoNotifyRecordRead 接口
    3. BeginLoad
    4. EndLoad
    5. BeforeRecordLoad
    6. AfterRecordLoad
    7. RecordLoadError
    8. BeforeRecordFieldLoad
    9. AfterRecordFieldLoad
    10. RecordLoadFieldError
    11. SkipUntil
    12. DoWhile
  9. 自定义
  10. AsDataReader 辅助方法
  11. AsDataTable 辅助方法
  12. 使用动态对象
    1. 默认值
    2. ChoFallbackValue
    3. 字段类型
    4. 类型转换器
    5. 验证
  13. 使用密封的 POCO 对象
  14. 异常
  15. 使用 MetadataType 注解
  16. 配置选项
    1. 手动配置
    2. 自动映射配置
    3. 附加 MetadataType 类
  17. LoadText 辅助方法
  18. 高级主题 (Advanced Topics)
    1. 覆盖转换器格式规范
    2. 货币支持
    3. 枚举支持
    4. 布尔值支持
    5. 日期时间支持
  19. Fluent API
    1. WithYamlPath
    2. WithFields
    3. WithField
    4. ColumnCountStrict
    5. NotifyAfter
    6. 配置
    7. 安装
  20. 常见问题解答
    1. 如何将字符串反序列化为枚举?
    2. 如何仅加载键值对 YAML 中的值?
    3. 如何仅加载键值对 YAML 中的键?
    4. 将 YAML 反序列化为动态对象?
    5. 如何将 YAML 转换为 XML?
    6. 如何从 YAML 中加载选定的节点?
    7. 如何从 YAML 中加载选定的子节点?
    8. 如何将 YAML 转换为 DataTable?
    9. 如何将 YAML 转换为 DataReader?
    10. 如何将 YAML 转换为 CSV?
    11. 如何反序列化对象?
    12. 如何反序列化集合?
    13. 如何反序列化字典?
    14. 如何从文件中反序列化?
    15. 如何使用自定义工厂进行反序列化?
    16. 读取整个文件 - 使用简单数据结构
    17. 读取整个文件 - 使用多个列表
    18. 使用泛型数据结构 (IDictionary)
    19. 使用泛型数据列表 (IList)
    20. 读取整个文件 - 动态结构
  21. 历史

1. 引言

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

YAML 是一种人类可读的数据序列化标准,可与所有编程语言结合使用,并常用于编写配置文件。

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

特点

  • 底层使用 SharpYaml 解析器,秒级解析 YAML 文件,并且可以处理大文件而没有内存问题
  • 基于流的解析器可提供极致的性能、低资源使用率以及几乎无限的通用性,可扩展到任何大小的数据文件,甚至数十或数百 GB
  • 基于事件的数据操作和验证允许在批量插入过程中完全控制数据流
  • 公开对象的 IEnumarable 列表 - 通常与 LINQ 查询一起用于投影、聚合和过滤等操作
  • 支持延迟读取
  • 支持处理具有特定日期、货币和数字格式的文化文件
  • 读取文件时识别各种日期、货币、枚举、布尔值和数字格式
  • 在写入文件时提供对日期、货币、枚举、布尔值、数字格式的精细控制
  • 详细且强大的错误处理,让您能够快速查找和修复问题

2. 要求

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

3. “Hello World!” 示例

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

让我们从一个读取包含三个字段的简单 YAML 文件的示例开始。

图 3.1 示例 YAML 数据文件 (emp.yaml)
emps:
    - id: 1
      name: Tom

    - id: 2
      name: Mark

有多种方法可以开始解析 YAML 文件,只需最少的设置。

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

这是 零配置、快速加载 YAML 文件的方式。不需要 POCO 对象。下面的示例代码显示了如何加载文件。

图 3.1.1 使用迭代器加载 YAML 文件
foreach (dynamic e in new ChoYamlReader("emp.yaml")
                .WithYamlPath("$.emps[*]"))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}
图 3.1.2 使用循环加载 YAML 文件
var reader = new ChoYamlReader("emp.yaml").WithYamlPath("$.emps[*]");
dynamic rec;
 
while ((rec = reader.Read()) != null)
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

3.2. 代码优先方法

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

图 3.2.1 简单的 POCO 实体类
public partial class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; } 
}

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

图 3.2.2 加载 YAML 文件
foreach (var e in new ChoYamlReader<EmployeeRec>("emp.yaml").WithYamlPath("$.emps[*]"))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

3.3. 配置优先方法

在此模型中,我们使用所有必要的解析参数以及与底层 YAML 文件匹配的 yaml 字段来定义 YAML 配置。

图 3.3.1 定义 YAML 配置
ChoYamlRecordConfiguration config = new ChoYamlRecordConfiguration();
config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration("Id"));
config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration("Name"));

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

图 3.3.2 不带 POCO 对象加载 YAML 文件
foreach (dynamic e in new ChoYamlReader("emp.yaml", config).WithYamlPath("$.emps[*]"))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}
图 3.3.3 带 POCO 对象加载 YAML 文件
foreach (var e in new ChoYamlReader<EmployeeRec>
        ("emp.yaml", config).WithYamlPath("$.emps[*]"))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

3.4. 代码优先声明式配置

这是结合了定义 POCO 实体类和 YAML 配置参数(以声明方式装饰)的方法。Id 是必需字段,Name 是可选值字段,默认值为*“XXXX”*。如果 Name 不存在,则使用默认值。

清单 3.4.1 定义 POCO 对象
public class EmployeeRec
{
    [ChoYamlRecordField]
    [Required]
    public int Id
    {
        get;
        set;
    }
    [ChoYamlRecordField]
    [DefaultValue("XXXX")]
    public string Name
    {
        get;
        set;
    }
 
    public override string ToString()
    {
        return "{0}. {1}".FormatString(Id, Name);
    }
}

上面的代码说明了如何定义 POCO 对象来存储输入文件中每条记录行的值。第一项是为每个记录字段定义属性,并使用 ChoYamlRecordFieldAttribute 来限定 YAML 记录映射。YamlPath 是一个可选属性。如果未指定,框架会自动发现并从 yaml 属性加载值。Id 使用 RequiredAttribute 装饰,如果值缺失,将抛出异常。Name 使用 DefaultValueAttribute 指定默认值。这意味着如果文件中的 Name yaml 字段为空,它将被默认设置为“XXXX”值。

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

清单 3.4.2 主方法
foreach (var e in new ChoYamlReader<EmployeeRec>("emp.yaml").WithYamlPath("$.emps[*]"))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

我们首先创建一个 ChoYamlReader 对象的新实例。就这样。解析器在后台完成了解析和将 YAML 数据流加载到对象中的繁重工作。

默认情况下,ChoYamlReader 在加载 YAML 文件时会发现并使用默认配置参数。可以根据您的需要覆盖这些参数。以下部分将详细介绍每个配置属性。

4. 读取所有记录

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

图 4.1 读取 YAML 文件
foreach (var e in new ChoYamlReader<EmployeeRec>("emp.yaml").WithYamlPath("$.emps[*]"))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

图 4.2 读取 YAML 文件流
foreach (var e in new ChoYamlReader<EmployeeRec>(textReader).WithYamlPath("$.emps[*]"))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

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

清单 4.3 使用 LINQ
var list = (from o in new ChoYamlReader<EmployeeRec>("emp.yaml").WithYamlPath("$.emps[*]")
           where o.Name != null && o.Name.StartsWith("R")
           select o).ToArray();
 
foreach (var e in list)
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

5. 手动读取记录

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

图 5.1 读取 YAML 文件
var reader = new ChoYamlReader<EmployeeRec>("emp.yaml").WithYamlPath("$.emps[*]");
var rec = (object)null;
 
while ((rec = reader.Read()) != null)
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

6. 自定义 YAML 记录

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

图 6.1 为每条记录自定义 POCO 对象
[ChoYamlRecordObject]
public class EmployeeRec
{
    [ChoYamlRecordField] 
    public int Id { get; set; }
    [ChoYamlRecordField] 
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}

以下是用于自定义文件上的 YAML 加载操作的可用属性。

  • YamlPath - 可选。用于选择要加载的元素的 YamlPath 表达式。如果未指定,则成员值将被自动发现并加载。
  • CultureName - 用于读取和写入 YAML 数据的区域性名称(例如,en-USen-GB)。
  • Encoding - YAML 文件的编码。
  • ColumnCountStrict - 此标志指示在读取预期字段丢失时是否应抛出异常。
  • ErrorMode - 此标志指示在读取时是否应抛出异常,并且预期的字段加载失败。这可以每隔属性覆盖。可能的值是
    • IgnoreAndContinue - 忽略错误,记录将被跳过并继续处理下一条。
    • ReportAndContinue - 如果 POCO 实体是 IChoNotifyRecordRead 类型,则向其报告错误
    • ThrowAndStop - 抛出错误并停止执行
  • IgnoreFieldValueMode - 一个标志,告知读取器在读取时是否应跳过空/null 的记录。这可以每隔属性覆盖。可能的值是
    • Null - 如果记录值为 null,则跳过
    • DBNull - N/A
    • Empty - 如果记录值为空,则跳过
    • WhiteSpace - 如果记录值仅包含空格,则跳过
  • ObjectValidationMode - 一个标志,告知读取器关于记录对象要执行的验证类型。可能的值是
    • Off - 不执行对象验证(默认)
    • MemberLevel - 在加载每个 YAML 属性的值时执行的验证。
    • ObjectLevel - 在所有属性加载到 POCO 对象后执行验证

7. 自定义 YAML 字段

对于每个 yaml 字段,您可以使用 ChoYamlRecordFieldAttribute 在 POCO 实体属性中指定映射。仅当您想使用自定义 YamlPath 映射到此字段时,才使用此属性。

图 7.1 为 YAML 字段自定义 POCO 对象
public class EmployeeRec
{
    [ChoYamlRecordField]
    public int Id { get; set; }
    [ChoYamlRecordField]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}

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

  • YamlPath - 可选。YamlPath 表达式使用路径表示法来寻址 YAML 文档的各个部分。如果未指定,ChoYamlReader 将自动发现并从匹配字段名称的 YAML 对象加载值。

7.1. 默认值

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

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

7.2. 后备值

当 YAML 值设置失败时,用于设置属性的值。Fallback 值仅在 ErrorModeIgnoreAndContinueReportAndContinue 时设置。

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

7.3. 类型转换器

大多数原始类型都会自动转换并设置到属性中。如果 yaml 字段的值无法自动转换为属性的类型,您可以指定自定义/内置 .NET 转换器来转换该值。这些可以是 IValueConverterTypeConverter 转换器。

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

  • 声明式方法
  • 配置方法

7.3.1. 声明式方法

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

图 7.3.1.1 指定类型转换器
public class EmployeeRec
{
    [ChoYamlRecordField]
    [ChoTypeConverter(typeof(IntConverter))]
    public int Id { get; set; }
    [ChoYamlRecordField]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}
图 7.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' YAML 属性一起使用。

7.3.2. 配置式方法

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

清单 7.3.2.2 指定类型转换器
ChoYamlRecordConfiguration config = new ChoYamlRecordConfiguration();

ChoYamlRecordFieldConfiguration idConfig = new ChoYamlRecordFieldConfiguration("Id");
idConfig.AddConverter(new IntConverter());
config.YamlRecordFieldConfigurations.Add(idConfig);

config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration("Name"));

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

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

7.4. 验证

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

图 7.4.1 在 POCO 实体中使用验证属性
[ChoYamlRecordObject]
public partial class EmployeeRec
{
    [ChoYamlRecordField(FieldName = "id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
 
    [ChoYamlRecordField(FieldName = "Name")]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
}

在上面的示例中,为 Id 属性使用了 Range 验证属性。为 Name 属性使用了 Required 验证属性。当 Configuration.ObjectValidationMode 设置为 ChoObjectValidationMode.MemberLevelChoObjectValidationMode.ObjectLevel 时,ChoYamlReader 会在加载期间执行验证。

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

static void ValidationOverridePOCOTest()
{
    ChoYamlRecordConfiguration config = new ChoYamlRecordConfiguration();
    var idConfig = new ChoYamlRecordFieldConfiguration("Id");
    idConfig.Validators = new ValidationAttribute[] { new RequiredAttribute() };
    config.YamlRecordFieldConfigurations.Add(idConfig);
    config.YamlRecordFieldConfigurations.Add
           (new ChoYamlRecordFieldConfiguration("Name"));
 
    using (var parser = new ChoYamlReader<EmployeeRec>("emp.yaml", config))
    {
        object rec;
        while ((rec = parser.Read()) != null)
        {
            Console.WriteLine(rec.ToStringEx());
        }
    }
}

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

图 7.4.2 在 POCO 实体上手动验证
[ChoYamlRecordObject]
public partial class EmployeeRec : IChoValidatable
{
    [ChoYamlRecordField(FieldName = "id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
 
    [ChoYamlRecordField(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 - 验证对象的特定属性,如果验证未通过则抛出异常。

8. 回调机制

ChoYamlReader 提供行业标准的 YAML 解析功能,可满足大多数解析需求。如果解析无法满足任何需求,您可以使用 ChoYamlReader 提供的回调机制来处理这种情况。为了参与回调机制,您可以使用以下任一模型:

  • 使用 ChoYamlReader 通过 IChoReader 接口公开的事件处理程序
  • 继承 POCO 实体对象,实现 IChoNotifyRecordRead / IChoNotifyFileRead / IChoNotifyRecordFieldRead 接口
  • 继承 DataAnnotationMetadataType 类型对象,并继承 IChoNotifyRecordRead / IChoNotifyFileRead / IChoNotifyRecordFieldRead 接口
  • 继承 IChoNotifyRecordFieldConfigurable / IChoNotifyRecordFieldConfigurable 配置接口

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

IChoReader 公开了以下事件

  • BeginLoad - 在 YAML 文件加载开始时调用
  • EndLoad - 在 YAML 文件加载结束时调用
  • BeforeRecordLoad - 在 YAML 记录加载之前触发
  • AfterRecordLoad - 在 YAML 记录加载之后触发
  • RecordLoadError - 在 YAML 记录加载出错时触发
  • BeforeRecordFieldLoad - 在 YAML 字段值加载之前触发
  • AfterRecordFieldLoad - 在 YAML 字段值加载之后触发
  • RecordFieldLoadError - 在 YAML 字段值加载出错时触发
  • SkipUntil - 在 YAML 解析开始之前触发,以添加自定义逻辑来跳过记录行。
  • DoWhile - 在 YAML 解析期间触发,您可以在其中添加自定义逻辑来停止解析。

IChoNotifyRecordRead 公开以下方法

  • BeforeRecordLoad - 在 YAML 记录加载之前触发
  • AfterRecordLoad - 在 YAML 记录加载之后触发
  • RecordLoadError - 在 YAML 记录加载出错时触发

IChoNotifyFileRead 公开以下方法

  • BeginLoad - 在 YAML 文件加载开始时调用
  • EndLoad - 在 YAML 文件加载结束时调用
  • SkipUntil - 在 YAML 解析开始之前触发,以添加自定义逻辑来跳过记录行
  • DoWhile - 在 YAML 解析期间触发,您可以在其中添加自定义逻辑来停止解析

IChoNotifyRecordFieldRead 公开以下方法

  • BeforeRecordFieldLoad - 在 yaml 字段值加载之前触发
  • AfterRecordFieldLoad - 在 yaml 字段值加载之后触发
  • RecordFieldLoadError - 在 yaml 字段值加载出错时触发

IChoNotifyRecordConfigurable 公开了以下方法

  • RecondConfigure - 在 YAML 记录配置时触发

IChoNotifyRecordFieldConfigurable 公开了以下方法

  • RecondFieldConfigure - 在每个 yaml 记录字段配置时触发

8.1. 使用 ChoYamlReader 事件

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

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

图 8.1.1 使用 ChoYamlReader 回调事件
static void IgnoreLineTest()
{
    using (var parser = new ChoYamlReader("emp.yaml").WithYamlPath("$.emps[*]"))
    {
        parser.BeforeRecordLoad += (o, e) =>
        {
            if (e.Source != null)
            {
                e.Skip = !((IDictionary<string, object>)e.Source).ContainsKey("Name");
            }
        };
        foreach (var e in parser)
            Console.WriteLine(e.Dump());
    }
}

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

8.2. 实现 IChoNotifyRecordRead 接口

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

图 8.2.1 直接 POCO 回调机制实现
[ChoYamlRecordObject]
public partial class EmployeeRec : IChoNotifyRecordRead
{
    [ChoYamlRecordField(FieldName = "Id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
    
    [ChoYamlRecordField(FieldName = "Name")]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
 
    public bool AfterRecordLoad(object target, int index, object source)
    {
        throw new NotImplementedException();
    }
 
    public bool BeforeRecordLoad(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }
 
    public bool RecordLoadError(object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }
}

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

图 8.2 MetaDataType 基于回调机制实现
[ChoYamlRecordObject]
public class EmployeeRecMeta : IChoNotifyRecordRead
{
    [ChoYamlRecordField(FieldName = "Id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }

    [ChoYamlRecordField(FieldName = "Name")]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
 
    public bool AfterRecordLoad(object target, int index, object source)
    {
        throw new NotImplementedException();
    }
 
    public bool BeforeRecordLoad(object target, int index, ref object source)
    {
        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; }
}

下面的示例显示了如何通过在 POCO 类上使用 ChoMetadataRefTypeAttribute 来附加 Metadata 类以用于密封类或第三方 POCO 类。

图 8.2.3 ChoMetaDataRefType 基于回调机制实现
[ChoMetadataRefType(typeof(EmployeeRec))]
[ChoYamlRecordObject]
public class EmployeeRecMeta : IChoNotifyRecordRead
{
    [ChoYamlRecordField(FieldName = "id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }

    [ChoYamlRecordField(FieldName = "Name")]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
 
    public bool AfterRecordLoad(object target, int index, object source)
    {
        throw new NotImplementedException();
    }
 
    public bool BeforeRecordLoad(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }
 
    public bool RecordLoadError(object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }
} 

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

8.3. BeginLoad

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

图 8.1.1 BeginLoad 回调示例
public bool BeginLoad(object source)
{
    StreamReader sr = source as StreamReader;
    return true;
}

8.4. EndLoad

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

图 8.2.1 EndLoad 回调示例
public void EndLoad(object source)
{
    StreamReader sr = source as StreamReader;
}

8.5. BeforeRecordLoad

此回调在 YAML 文件中的每个 YAML 节点加载之前调用。target 是 POCO 记录对象的实例。index 是文件中的 JObject 节点索引。source 是 YAML 记录对象。在这里,您可以检查对象,并在需要时用新值覆盖它。

提示:如果您想跳过 JObject 的加载,请将 source 设置为 null

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

图 8.5.1 BeforeRecordLoad 回调示例
public bool BeforeRecordLoad(object target, int index, ref object source)
{
    IDictionary<string, object> obj = source as IDictionary<string, object>;
    return true;
}

8.6. AfterRecordLoad

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

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

图 8.6.1 AfterRecordLoad 回调示例
public bool AfterRecordLoad(object target, int index, object source)
{
    IDictionary<string, object> obj = source as IDictionary<string, object>;
    return true;
}

8.7. RecordLoadError

当加载 JObject 节点时遇到错误,将调用此回调。target 是 POCO 记录对象的实例。index 是文件中的 JObject 节点索引。sourceJObject 节点。ex 是异常对象。在这里,您可以处理异常。此方法仅在 Configuration.ErrorMode 设置为 ReportAndContinue 时调用。

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

图 8.7.1 RecordLoadError 回调示例
public bool RecordLoadError(object target, int index, object source, Exception ex)
{
    IDictionary<string, object> obj = source as IDictionary<string, object>;
    return true;
}

8.8. BeforeRecordFieldLoad

此回调在每个 YAML 记录字段加载之前调用。target 是 POCO 记录对象的实例。index 是文件中的 JObject 节点索引。propName 是 YAML 记录属性名。value 是 YAML 字段值。在这里,您可以检查 YAML 记录属性值并执行任何自定义验证等。

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

图 8.8.1 BeforeRecordFieldLoad 回调示例
public bool BeforeRecordFieldLoad
(object target, int index, string propName, ref object value)
{
    return true;
}

8.9. AfterRecordFieldLoad

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

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

图 8.9.1 AfterRecordFieldLoad 回调示例
public bool AfterRecordFieldLoad
(object target, int index, string propName, object value)
{
    return true;
}

8.10. RecordLoadFieldError

当加载 YAML 记录字段值时遇到错误,将调用此回调。target 是 POCO 记录对象的实例。index 是文件中的 JObject 节点索引。propName 是 YAML 记录属性名。value 是 YAML 字段值。ex 是异常对象。在这里,您可以处理异常。此方法仅在 ChoYamlReader 执行了以下两个步骤序列之后才会被调用。

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

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

图 8.10.1 RecordFieldLoadError 回调示例
public bool RecordFieldLoadError
(object target, int index, string propName, object value, Exception ex)
{
    return true;
}

8.11. SkipUntil

此回调在 YAML 解析开始时调用,并带有自定义逻辑来跳过节点。index 是文件中的 JObject 节点索引。

返回 true 以跳过该行,否则返回 false

图 8.11.1 SkipUntil 回调示例
public bool SkipUntil(long index, object source)
{
    return false;
}

8.12. DoWhile

此回调在 YAML 解析开始时调用,并带有自定义逻辑来跳过节点。index 是文件中的 JObject 节点索引。

返回 true 以停止解析,否则返回 false

图 8.12.1 DoWhile 回调示例
public bool DoWhile(long index, object source)
{
    return false;
}

9. 自定义

ChoYamlReader 自动检测并从 POCO 实体加载配置的设置。在运行时,您可以在 YAML 解析之前自定义和调整这些参数。ChoYamlReader 公开了 Configuration 属性,它是 ChoYamlRecordConfiguration 对象。使用此属性,您可以自定义它们。

图 9.1 运行时自定义 ChoYamlReader
class Program
{
    static void Main(string[] args)
    {
        using (var parser = new ChoYamlReader<EmployeeRec>("emp.yaml"))
        {
            object row = null;
  
            parser.Configuration.YamlPath = "$.emps[*]";
            while ((row = parser.Read()) != null)
                Console.WriteLine(row.ToString());
        }
    }

10. AsDataReader 辅助方法

ChoYamlReader 公开了 AsDataReader 辅助方法,以在 .NET datareader 对象中检索 YAML 记录。DataReader 是快进式数据流。此 datareader 可用于一些地方,例如使用 SqlBulkCopy 将数据批量复制到数据库,加载断开连接的 DataTable 等。

图 10.1 读作 DataReader 示例
static void AsDataReaderTest()
{
    using (var parser = new ChoYamlReader<EmployeeRec>
          ("emp.yaml").WithYamlPath("$.emps[*]"))
    {
        IDataReader dr = parser.AsDataReader();
        while (dr.Read())
        {
            Console.WriteLine("Id: {0}, Name: {1}", dr[0], dr[1]);
        }
    }
}

11. AsDataTable 辅助方法

ChoYamlReader 公开了 AsDataTable 辅助方法,以在 .NET DataTable 对象中检索 YAML 记录。然后,它可以像任何其他对象一样持久化到磁盘、显示在网格/控件中或存储在内存中。

图 11.1 读作 DataTable 示例
static void AsDataTableTest()
{
    using (var parser = new ChoYamlReader<EmployeeRec>
          ("emp.yaml").WithYamlPath("$.emps[*]"))
    {
        DataTable dt = parser.AsDataTable();
        foreach (DataRow dr in dt.Rows)
        {
            Console.WriteLine("Id: {0}, Name: {1}", dr[0], dr[1]);
        }
    }
}

12. 使用动态对象

到目前为止,文章一直解释如何使用 POCO 对象来使用 ChoYamlReaderChoYamlReader 也支持在没有 POCO 对象的情况下加载 YAML 文件。它利用 .NET 动态功能。下面的示例展示了如何在没有 POCO 对象的情况下读取 YAML 流。

如果您有 YAML 文件,您可以以最少的配置/零配置来解析和加载该文件。

下面的示例展示了这一点

图 12.1 加载 YAML 文件
class Program
{
    static void Main(string[] args)
    {
        dynamic row;
        using (var parser = new ChoYamlReader("emp.yaml").WithYamlPath("$.emps[*]"))
        {
            while ((row = parser.Read()) != null)
            {
                Console.WriteLine(row.Id);
            }
        }
    }
}

上面的示例自动发现 YAML 对象成员并解析文件。

您可以手动添加字段配置并将其传递给 ChoYamlReader 进行文件解析,从而覆盖自动发现字段的默认行为。

示例展示了如何操作。

图 12.3 带配置加载 YAML 文件
class Program
{
    static void Main(string[] args)
    {
        ChoYamlRecordConfiguration config = new ChoYamlRecordConfiguration();
        config.YamlRecordFieldConfigurations.Add
                              (new ChoYamlRecordFieldConfiguration("Id"));
        config.YamlRecordFieldConfigurations.Add
                              (new ChoYamlRecordFieldConfiguration("Name"));

        dynamic row;
        using (var parser = new ChoYamlReader
              ("emp.yaml", config).WithYamlPath("$.emps[*]"))
        {
            while ((row = parser.Read()) != null)
            {
                Console.WriteLine(row.Name);
            }
        }
    }
}

要完全关闭自动字段发现,您需要将 ChoYamlRecordConfiguration.AutoDiscoverColumns 设置为 false

12.1. 默认值

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

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

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

ChoYamlRecordConfiguration config = new ChoYamlRecordConfiguration();
config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration("Id"));
config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration("Name") 
       { DefaultValue = "NoName" })

12.2. ChoFallbackValue

Yaml 值设置失败时,用于设置属性的值。Fallback 值仅在 ErrorModeIgnoreAndContinueReportAndContinue 时设置。

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

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

ChoYamlRecordConfiguration config = new ChoYamlRecordConfiguration();
config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration("Id"));
config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration("Name") 
       { FallbackValue = "Tom" });

12.3. 字段类型

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

图 12.5.1 定义字段类型
ChoYamlRecordConfiguration config = new ChoYamlRecordConfiguration();
config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration("Id") 
       { FieldType = typeof(int) });
config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration("Name"));

上面的示例显示将“Id”字段的字段类型定义为“int”。这指示 ChoYamlReader 在分配之前解析并将值转换为整数。这种额外的类型安全性消除了在解析期间将不正确的值加载到对象中的情况。

12.4. 类型转换器

ChoYamlReader 自动转换大多数原始类型并将其设置到属性中。如果 Yaml 字段的值无法自动转换为属性的类型,您可以指定自定义/内置 .NET 转换器来转换该值。这些可以是 IValueConverterTypeConverter 转换器。

在动态对象模型中,您可以通过配置指定这些转换器。以下示例展示了为 Yaml 字段指定类型转换器的方法。

图 12.4.1 指定 TypeConverters
ChoYamlRecordConfiguration config = new ChoYamlRecordConfiguration();

ChoYamlRecordFieldConfiguration idConfig = new ChoYamlRecordFieldConfiguration("Id");
idConfig.AddConverter(new IntConverter());
config.YamlRecordFieldConfigurations.Add(idConfig);

config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration("Name"));

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

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

12.5. 验证

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

图 12.5.1 指定验证
ChoYamlRecordConfiguration config = new ChoYamlRecordConfiguration();

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

config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration("Name"));

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

附注:在动态对象模型中不支持自验证。

13. 使用密封的 POCO 对象

如果您已经拥有现有的密封 POCO 对象或对象位于第三方库中,我们可以将其与 ChoYamlReader 一起使用。

图 13.1 现有的密封 POCO 对象
public sealed class ThirdPartyRec
{
    public int Id
    {
        get;
        set;
    }
    public string Name
    {
        get;
        set;
    }
}
图 13.2 消费 YAML 文件
class Program
{
    static void Main(string[] args)
    {
        using (var parser = new ChoYamlReader<ThirdPartyRec>
              ("emp.yaml").WithYamlPath("$.emps[*]"))
        {
            object row = null;
 
            while ((row = parser.Read()) != null)
                Console.WriteLine(row.ToString());
        }
    }
}

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

您可以将 ChoYamlRecordConfiguration.ThrowAndStopOnMissingField 属性设置为 false 来覆盖此行为。在这种情况下,如果缺少 Yaml 字段的属性,ChoYamlReader 将抛出 ChoMissingRecordFieldException 异常。

14. 异常

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

  • ChoParserException - YAML 文件格式错误,解析器无法恢复。
  • ChoRecordConfigurationException - 指定了任何无效的配置设置,将引发此异常。
  • ChoMissingRecordFieldException - 缺少 Yaml 字段的属性,将引发此异常。

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; }
}

[ChoYamlRecordObject]
public class EmployeeRecMeta : IChoNotifyRecordRead, IChoValidatable
{
    [ChoYamlRecordField(FieldName = "id", ErrorMode = ChoErrorMode.ReportAndContinue )]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, 1, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }

    [ChoYamlRecordField(FieldName = "Name")]
    [StringLength(1)]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
 
    public bool AfterRecordLoad(object target, int index, object source)
    {
        throw new NotImplementedException();
    }
 
    public bool BeforeRecordLoad(object target, int index, ref object source)
    {
        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 中。

16. 配置选项

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

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

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

图 16.1 密封 POCO 实体类
public sealed class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; }
}

16.1 手动配置

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

图 16.1.1 手动配置
ChoYamlRecordConfiguration config = new ChoYamlRecordConfiguration();
config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration("Id"));
config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration("Name"));

16.2 自动映射配置

这是一个替代方法,也是一个不太容易出错的方法,用于自动映射 POCO 实体类的 Yaml 字段。

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

图 16.2.1 自动映射类
public class EmployeeRecMap
{
    [ChoYamlRecordField(FieldName = "Id")]
    public int Id { get; set; }
 
    [ChoYamlRecordField(FieldName = "Name")]
    public string Name { get; set; } 
}

然后,您可以使用它通过 ChoYamlRecordConfiguration.MapRecordFields 方法自动映射 Yaml 字段。

图 16.2.2 使用自动映射配置
ChoYamlRecordConfiguration config = new ChoYamlRecordConfiguration();
config.MapRecordFields<EmployeeRecMap>();

foreach (var e in new ChoYamlReader<EmployeeRec>
        ("emp.yaml", config).WithYamlPath("$.emps[*]")) 
    Console.WriteLine(e.ToString());

16.3 附加 MetadataType 类

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

此模型通过定义 MetadataType 类并以声明方式指定 YAML 配置参数来处理所有内容。这在 POCO 实体是密封的且不是部分类时很有用。此外,它是配置 POCO 实体 YAML 解析的一种有利且不易出错的方法。

图 16.3.1 定义 MetadataType 类
[ChoYamlRecordObject]
public class EmployeeRecMeta : IChoNotifyRecordRead, IChoValidatable
{
    [ChoYamlRecordField(FieldName = "Id", ErrorMode = ChoErrorMode.ReportAndContinue )]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, 1, ErrorMessage = "Id must be > 0.")]
    public int Id { get; set; }

    [ChoYamlRecordField(FieldName = "Name")]
    [StringLength(1)]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
 
    public bool AfterRecordLoad(object target, int index, object source)
    {
        throw new NotImplementedException();
    }
 
    public bool BeforeRecordLoad(object target, int index, ref object source)
    {
        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)
    {
    }
}
图 16.3.2 附加 MetadataType 类
//Attach metadata 
ChoMetadataObjectCache.Default.Attach<EmployeeRec>(new EmployeeRecMeta());

foreach (var e in new ChoYamlReader<EmployeeRec>("emp.yaml").WithYamlPath("$.emps[*]")) 
    Console.WriteLine(e.ToString()

17. LoadText 辅助方法

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

图 17.1 使用 LoadText 方法
string txt = @"
emps:
    - id: 1
      name: Tom

    - id: 2
      name: Mark";

foreach (var e in ChoYamlReader.LoadText(txt).WithYamlPath("$.emps[*]"))
   Console.WriteLine(e.ToStringEx());

18. 高级主题

18.1 覆盖转换器格式规范

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

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

  1. ChoYamlRecordConfiguration.CultureInfo - 代表特定区域性的信息,包括区域性的名称、书写系统和使用的日历,以及对区域性特定对象的访问,这些对象提供常见操作的信息,例如格式化日期和排序字符串。默认为“en-US”。
  2. ChoTypeConverterFormatSpec - 它是全局格式说明符类,包含所有内在的 .NET 类型格式规范。

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

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

为每个内在类型提供了两组格式规范成员,一组用于加载,另一组用于写入值,但布尔值、枚举、日期时间类型除外。这些类型只有一个成员,用于加载和写入操作。

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

NumberStyles(可选)用于从 Yaml 流加载值,而 Format string 用于将值写入 Yaml 流。

在本文中,我将简要介绍使用 NumberStyles 从流中加载 YAML 数据。这些值是可选的。它确定了在解析 YAML 文件期间允许的每种类型的样式。系统会自动找出从底层区域性解析和加载值的方法。在特殊情况下,您可能希望覆盖并按照自己的意愿设置样式,以成功加载文件。有关 NumberStyles 及其值的更多信息,请参阅 MSDN NumberStyles

图 18.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; }
}

下面的示例显示了如何使用 ChoYamlReader 加载具有“se-SE”(瑞典)区域性特定数据的 Yaml 数据流。此外,输入源包含带有括号的“EmployeeNo”值。为了使加载成功,我们必须将 ChoTypeConverterFormatSpec.IntNumberStyle 设置为 NumberStyles.AllowParenthesis

图 18.1.2 在代码中使用 ChoTypeConverterFormatSpec
static void UsingFormatSpecs()
{
    ChoYamlRecordConfiguration config = new ChoYamlRecordConfiguration();
    config.Culture = new System.Globalization.CultureInfo("se-SE");
    config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration("Id") 
           { FieldType = typeof(int) });
    config.YamlRecordFieldConfigurations.Add
           (new ChoYamlRecordFieldConfiguration("Name"));
    config.YamlRecordFieldConfigurations.Add
           (new ChoYamlRecordFieldConfiguration("Salary") 
           { FieldType = typeof(ChoCurrency) });
    config.YamlRecordFieldConfigurations.Add
           (new ChoYamlRecordFieldConfiguration("JoinedDate") 
           { FieldType = typeof(DateTime) });
    config.YamlRecordFieldConfigurations.Add
           (new ChoYamlRecordFieldConfiguration("EmployeeNo") 
           { FieldType = typeof(int) });
 
    ChoTypeConverterFormatSpec.Instance.IntNumberStyle = NumberStyles.AllowParentheses;
 
    using (var parser = new ChoYamlReader("emp.yaml", config).WithYamlPath("$.emps[*]"))
    {
        object row = null;
 
        while ((row = parser.Read()) != null)
            Console.WriteLine(row.ToStringEx());
    }
}

18.2 货币支持

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

图 18.2.1 在动态模型中使用货币成员
static void CurrencyDynamicTest()
{
    ChoYamlRecordConfiguration config = new ChoYamlRecordConfiguration();
    config.YamlRecordFieldConfigurations.Add
                          (new ChoYamlRecordFieldConfiguration("Id"));
    config.YamlRecordFieldConfigurations.Add
                          (new ChoYamlRecordFieldConfiguration("Name"));
    config.YamlRecordFieldConfigurations.Add
                          (new ChoYamlRecordFieldConfiguration("Salary") 
                          { FieldType = typeof(ChoCurrency) });
 
    using (var parser = 
           new ChoYamlReader("emp.yaml", config).WithYamlPath("$.emps[*]"))
    {
        object rec;
        while ((rec = parser.Read()) != null)
        {
            Console.WriteLine(rec.ToStringEx());
        }
    }
}

上面的示例展示了如何使用动态对象模型加载货币值。默认情况下,动态对象的所有成员都将被视为 string 类型,除非通过 ChoYamlFieldConfiguration.FieldType 显式指定。通过将“SalaryYaml 字段的字段类型指定为 ChoCurrencyChoYamlReader 会将其加载为货币对象。

附注:货币值的格式通过 ChoYamlReader 使用 ChoRecordConfiguration.CultureChoTypeConverterFormatSpec.CurrencyNumberStyle 来确定。

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

图 18.2.2 在 POCO 模型中使用货币成员
public class EmployeeRecWithCurrency
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ChoCurrency Salary { get; set; }
}
 
static void CurrencyTest()
{
    using (var parser = new ChoYamlReader<EmployeeRecWithCurrency>
          ("emp.yaml").WithYamlPath("$.emps[*]"))
    {
        object rec;
        while ((rec = parser.Read()) != null)
        {
            Console.WriteLine(rec.ToStringEx());
        }
    }
}

18.3 枚举支持

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

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

可以使用三个可能的值:

  1. ChoEnumFormatSpec.Value - Enum 值用于解析。
  2. ChoEnumFormatSpec.Name - Enum 键名用于解析。
  3. ChoEnumFormatSpec.Description - 如果每个 enum 键都用 DescriptionAttribute 修饰,则使用其值进行解析。
图 18.3.1 在解析期间指定枚举格式规范
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;
 
    ChoYamlRecordConfiguration config = new ChoYamlRecordConfiguration();
    config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration("Id") 
                                            { FieldType = typeof(int) });
    config.YamlRecordFieldConfigurations.Add
                             (new ChoYamlRecordFieldConfiguration("Name"));
    config.YamlRecordFieldConfigurations.Add
                             (new ChoYamlRecordFieldConfiguration("Salary") 
                                            { FieldType = typeof(ChoCurrency) });
    config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration
                                            ("JoinedDate") 
                                            { FieldType = typeof(DateTime) });
    config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration
                          ("EmployeeType") { FieldType = typeof(EmployeeType) });
 
    ChoTypeConverterFormatSpec.Instance.IntNumberStyle = NumberStyles.AllowParentheses;
 
    using (var parser = new ChoYamlReader("emp.yaml", config).WithYamlPath("$.emps[*]"))
    {
        object row = null;
 
        while ((row = parser.Read()) != null)
            Console.WriteLine(row.ToStringEx());
    }
}

18.4 布尔值支持

Cinchoo ETL 会隐式处理从 YAML 文件中解析布尔 Yaml 字段值。如果您想精细控制这些值的解析,可以通过 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
图 18.4.1 在解析期间指定布尔值格式规范
static void BoolTest()
{
    ChoTypeConverterFormatSpec.Instance.BooleanFormat = ChoBooleanFormatSpec.ZeroOrOne;
 
    ChoYamlRecordConfiguration config = new ChoYamlRecordConfiguration();
    config.YamlRecordFieldConfigurations.Add
                          (new ChoYamlRecordFieldConfiguration("Id") 
                                            { FieldType = typeof(int) });
    config.YamlRecordFieldConfigurations.Add
                          (new ChoYamlRecordFieldConfiguration("Name"));
    config.YamlRecordFieldConfigurations.Add
                          (new ChoYamlRecordFieldConfiguration("Salary") 
                                            { FieldType = typeof(ChoCurrency) });
    config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration
                                            ("JoinedDate") 
                                            { FieldType = typeof(DateTime) });
    config.YamlRecordFieldConfigurations.Add
                          (new ChoYamlRecordFieldConfiguration("Active") 
                                            { FieldType = typeof(bool) });
 
    ChoTypeConverterFormatSpec.Instance.IntNumberStyle = NumberStyles.AllowParentheses;
 
    using (var parser = new ChoYamlReader
          ("emp.yaml", config).WithYamlPath("$.emps[*]"))
    {
        object row = null;
 
        while ((row = parser.Read()) != null)
            Console.WriteLine(row.ToStringEx());
    }
}

18.5 日期时间支持

Cinchoo ETL 使用系统区域性或自定义设置的区域性隐式处理从 YAML 文件中解析 datetime Yaml 字段值。如果您想精细控制这些值的解析,可以通过 ChoTypeConverterFormatSpec.DateTimeFormat 全局指定它们。默认值为“d”。

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

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

图 18.5.1 在解析期间指定日期时间格式规范
static void DateTimeTest()
{
    ChoTypeConverterFormatSpec.Instance.DateTimeFormat = "MMM dd, yyyy";
 
    ChoYamlRecordConfiguration config = new ChoYamlRecordConfiguration();
    config.YamlRecordFieldConfigurations.Add(new ChoYamlRecordFieldConfiguration("Id") 
                                            { FieldType = typeof(int) });
    config.YamlRecordFieldConfigurations.Add
                          (new ChoYamlRecordFieldConfiguration("Name"));
    config.YamlRecordFieldConfigurations.Add
                          (new ChoYamlRecordFieldConfiguration("Salary") 
                                            { FieldType = typeof(ChoCurrency) });
    config.YamlRecordFieldConfigurations.Add
                          (new ChoYamlRecordFieldConfiguration("JoinedDate") 
                                            { FieldType = typeof(DateTime) });
    config.YamlRecordFieldConfigurations.Add
                          (new ChoYamlRecordFieldConfiguration("Active") 
                                            { FieldType = typeof(bool) });
 
    ChoTypeConverterFormatSpec.Instance.IntNumberStyle = NumberStyles.AllowParentheses;
 
    using (var parser = new ChoYamlReader
          ("emp.yaml", config).WithYamlPath("$.emps[*]"))
    {
        object row = null;
 
        while ((row = parser.Read()) != null)
            Console.WriteLine(row.ToStringEx());
    }
}

上面的示例展示了如何解析 YAML 文件中的自定义 datetime Yaml 值。

注意:由于 datetime 值包含 YAML 分隔符,因此它们用双引号括起来以通过解析。

19. 流式 API

ChoYamlReader 通过流式 API 方法公开了一些常用的配置参数。这将使 YAML 文件解析的编程更快。

19.1 WithYamlPath

此 API 方法设置 YamlPath 表达式,以使用 ChoYamlReader 选择要加载的节点。

foreach (var e in new ChoYamlReader<EmployeeRec>("emp.yaml").WithYamlPath("$.emps[*]"))
    Console.WriteLine(e.ToString());

19.2 WithFields

此 API 方法指定要考虑解析和加载的 YAML 节点(属性或元素)列表。YAML 节点中的其他字段将被丢弃。

foreach (var e in new ChoYamlReader<EmployeeRec>("emp.yaml").WithFields("Id", "Name"))
    Console.WriteLine(e.ToString());

19.3 WithField

此 API 方法用于添加具有 YamlPath、数据类型和其他参数的 YAML 节点。此方法在动态对象模型中很有用,通过为每个单独的 YAML 节点指定适当的 datatype

foreach (var e in new ChoYamlReader<EmployeeRec>("emp.yaml").WithField
        ("Id", fieldType: typeof(int)))
    Console.WriteLine(e.ToString());

19.4 ColumnCountStrict

此 API 方法用于设置 ChoYamlWriter 在读取 YAML 文件之前执行字段计数检查。

foreach (var e in new ChoYamlReader<EmployeeRec>("emp.yaml").ColumnCountStrict())
    Console.WriteLine(e.ToString());

19.5 NotifyAfter

此 API 方法用于定义在生成通知事件之前要处理的行数。此属性专为说明 YAML 加载进度的用户界面组件而设计。通知将发送给订阅了 RowsLoaded 事件的订阅者。

static void NotifyAfterTest()
{
    using (var parser = new ChoYamlReader("emp.yaml").WithYamlPath("$.emps[*]")
        .NotifyAfter(1000)
        )
    {
        parser.RowsLoaded += (o, e) => Console.WriteLine(e.RowsLoaded); 

        foreach (var rec in parser)
        {
            Console.WriteLine(String.Format("Id: {0}", rec.Id));
            Console.WriteLine(String.Format("Name: {0}", rec.Name));
            Console.WriteLine(String.Format("Salary: {0}", rec.Salary));
        }
    }
}

19.6 Configure

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

static void ConfigureTest()
{
    using (var parser = new ChoYamlReader("emp.yaml").WithYamlPath("$.emps[*]")
        .Configure(c => c.ErrorMode = ChoErrorMode.ThrowAndStop)
        )
    {
        foreach (var rec in parser)
        {
            Console.WriteLine(String.Format("Id: {0}", rec.Id));
            Console.WriteLine(String.Format("Name: {0}", rec.Name));
            Console.WriteLine(String.Format("Salary: {0}", rec.Salary));
        }
    }
}

19.7 Setup

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

static void SetupTest()
{
    using (var parser = new ChoYamlReader("emp.yaml").WithYamlPath("$.emps[*]")
        .Setup(r => r.BeforeRecordLoad += (o, e) =>
        {
            if (e.Source.CastTo<JObject>().ContainsKey("Name1"))
                e.Skip = true;
        }
        )
    {
        foreach (var rec in parser)
        {
            Console.WriteLine(String.Format("Id: {0}", rec.Id));
            Console.WriteLine(String.Format("Name: {0}", rec.Name));
            Console.WriteLine(String.Format("Salary: {0}", rec.Salary));
        }
    }
}

20. 常见问题解答

20.1. 如何将字符串反序列化为枚举?

ChoYamlReader 会隐式处理将 enum 文本转换为 enum 值。下面的示例展示了如何使用 POCO 对象加载 YAML

public enum Gender { Male, Female }
public class Employee
{
    public int Age { get; set; }
    public Gender Gender { get; set; }
}

static void EnumTest()
{
    string yaml = @"
emps:
    - Age: 15
      name: Male

    - Age: 25
      name: Female
";

    using (var r = ChoYamlReader<Employee>.LoadText(yaml)
        .WithYamlPath("$.emps[*]")
        )
    {
        foreach (var rec in r)
            Console.WriteLine(rec.Dump());
    }
}

下面的示例展示了如何在动态对象模型方法中解析带有 enum 值的 YAML

static void DynamicEnumTest()
{
    string yaml = @"
emps:
    - Age: 15
      Gender: Male

    - Age: 25
      Gender: Female
";

    using (var r = ChoYamlReader.LoadText(yaml).WithYamlPath("$.emps[*]")
        .WithField("Age")
        .WithField("Gender", fieldType: typeof(Gender))
        )
    {
        foreach (var rec in r)
            Console.WriteLine(rec.Dump());
    }
}

20.2. 如何仅加载键值对 YAML 中的值?

使用自定义 YamlPath$..^),您可以仅加载 yaml 中的值。

static void LoadDictKeysTest()
{
    string yaml = @"
Age: 15
Gender: Male
";

    using (var r = ChoYamlReader.LoadText(yaml)
        .WithYamlPath("$.^")
        )
    {
        foreach (var rec in r)
            Console.WriteLine(rec.Dump());
    }
}

20.3. 如何仅加载键值对 Yaml 中的键?

使用自定义 YamlPath$..~),您可以仅加载 yaml 中的值。

static void LoadDictKeysTest()
{
    string yaml = @"
Age: 15
Gender: Male
";

    using (var r = ChoYamlReader.LoadText(yaml)
        .WithYamlPath("$.~")
        )
    {
        foreach (var rec in r)
            Console.WriteLine(rec.Dump());
    }
}

20.4. 将 Yaml 反序列化为动态对象?

ChoYamlReader 在动态对象模型中隐式执行此操作。

static void DynamicEnumTest()
{
    string yaml = @"
emps:
    - Age: 15
      Gender: Male

    - Age: 25
      Gender: Female
";

    using (dynamic r = ChoYamlReader.LoadText(yaml).WithYamlPath("$.emps[*]")
        .WithField("Age")
        .WithField("Gender", fieldType: typeof(Gender))
        )
    {
        foreach (var rec in r)
        {
            Console.WriteLine(rec.Age);
            Console.WriteLine(rec.Gender);
        }
    }
}

在上面,解析器加载 YAML 文件,构造并返回 dynamic 对象。

20.5. 如何将 YAML 转换为 XML?

Cinchoo ETL 提供了 ChoXmlWriter 来从对象生成 XML 文件。通过 ChoYamlReaderChoXmlWriter,您可以轻松地将 YAML 转换为 XML 格式。

static void Yaml2XmlTest()
{
     string yaml = @"
emps:
    - id: 1
      name: Mark

    - id: 2
      name: Tom
";

    StringBuilder xml = new StringBuilder();
    using (var r = ChoYamlReader.LoadText(yaml).WithYamlPath("$.emps[*]"))
    {
        using (var w = new ChoXmlWriter(xml)
            .WithRootName("Emps")
            .WithNodeName("Emp")
            )
            w.Write(r);
    }
    Console.WriteLine(xml.ToString());
}

输出

<Emps>
  <Emp>
    <Id>1</Id>
    <Name>Mark</Name>
  </Emp>
  <Emp>
    <Id>2</Id>
    <Name>Tom</Name>
  </Emp>
</Emps>

20.6. 如何从 YAML 中加载选定的节点?

使用 YamlPath,您可以使用 ChoYamlReader 将选定的节点加载到对象中。

对于以下示例 YAML

users:
    - name: 1
      teamname: Tom
      email: xx@gmail.com
      players: [1, 2]

想将 User 数据加载到对象中。示例代码使用 YamlPath “$.user” 来选择节点并进行解析。

public class UserInfo
{
    public string name { get; set; }
    public string teamname { get; set; }
    public string email { get; set; }
    public int[] players { get; set; }
}

static void SelectiveNodeTest1()
{
    string yaml = @"
users:
    - name: 1
      teamname: Tom
      email: xx@gmail.com
      players: [1, 2]
";
    using (var r = ChoYamlReader<UserInfo>.LoadText(yaml)
        .WithYamlPath("$.users[*]")
        )
    {
        foreach (var rec in r)
            Console.WriteLine(rec.Dump());
    }
}}

20.7. 如何从 YAML 中加载选定的子节点?

使用每个成员级别的 YamlPath,您可以使用 ChoYamlReader 将选定的子节点加载到对象成员中。

对于以下示例 YAML

users:
    - name: 1
      teamname: Tom
      email: xx@gmail.com
      players: [1, 2]

想将 User 数据加载到对象中。示例代码在每个字段上使用 YamlPath 来选择子节点并进行解析。

public class UserInfo
{
    [ChoYamlRecordField(YamlPath = "$.name")]
    public string name { get; set; }
    [ChoYamlRecordField(YamlPath = "$.teamname")]
    public string teamname { get; set; }
    [ChoYamlRecordField(YamlPath = "$.email")]
    public string email { get; set; }
    [ChoYamlRecordField(YamlPath = "$.players")]
    public int[] players { get; set; }
}

在上面,每个成员都指定了 YamlPath 来从节点值中选择。这样,您就可以使用复杂的 YamlPath 来选择和选择 yaml 节点值,从而为您提供了更大的控制来选择数据。

static void SelectiveNodeTest()
{
    string yaml = @"
users:
    - name: 1
      teamname: Tom
      email: xx@gmail.com
      players: [1, 2]
";
    using (var r = ChoYamlReader<UserInfo>.LoadText(yaml)
        .WithYamlPath("$.users[*]")
        )
    {
        foreach (var rec in r)
            Console.WriteLine(rec.Dump());
    }
}

20.7. 如何将 Yaml 转换为 DataTable?

ChoYamlReader 提供了一个简单的辅助方法来将 yaml 转换为 Datatable,即 AsDataTable()

static void ConvertToDataTableTest()
{
    string yaml = @"
emps:
    - id: 1
      name: Tom

    - id: 2
      name: Mark
";

    using (var r = ChoYamlReader<UserInfo>.LoadText(yaml)
        .WithYamlPath("$.emps[*]")
        )
    {
        var dt = r.AsDataTable();
    }
}

20.8. 如何将 Yaml 转换为 DataReader?

ChoYamlReader 提供了一个简单的辅助方法来将 yaml 转换为 DataReader,即 AsDataReader()

static void ConvertToDataReaderTest()
{
    string yaml = @"
emps:
    - id: 1
      name: Tom

    - id: 2
      name: Mark
";

    using (var r = ChoYamlReader<UserInfo>.LoadText(yaml)
        .WithYamlPath("$.emps[*]")
        )
    {
        var dt = r.AsDataReader();
    }
}

20.9. 如何将 Yaml 转换为 CSV?

Cinchoo ETL 提供了 ChoCSVWriter 来从对象生成 csv 文件。通过 ChoYamlReaderChoCSVWriter,您可以轻松地将 yaml 转换为 CSV 格式。

Yaml 是层次化对象模型,CSV 是扁平结构。Cinchoo 无缝处理它们并产生预期的输出。

static void Yaml2CSV2()
{
    string yaml = @"
emps:
    - id: 1
      name: Tom

    - id: 2
      name: Mark
";

    StringBuilder csv = new StringBuilder();
    using (var r = ChoYamlReader.LoadText(yaml).WithYamlPath("$.emps[*]")
        )
    {
        using (var w = new ChoCSVWriter(csv)
            .WithFirstLineHeader()
            .NestedColumnSeparator('/')
            )
            w.Write(r);
    }

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

20.10. 如何反序列化对象?

此示例将 yaml 反序列化为对象

public class Emp
{
    public int Id { get; set; }
    public string Name { get; set; }
}

static void DeserializeObjectTest()
{
    string yaml = @"
id: 1
name: Tom
";
    Console.WriteLine(ChoYamlReader.DeserializeText<Emp>(yaml).FirstOrDefault().Dump());
}

20.11. 如何反序列化集合?

此示例将 yaml 反序列化为集合

static void DeserializeCollectioTest()
{
    string yaml = @"
emps: 
    - Tom
    - Mark
";
    var emps = ChoYamlReader.DeserializeText<string>(yaml, "$.emps[*]").ToList();
    Console.WriteLine(emps.Dump());
}

20.12. 如何反序列化字典?

此示例将 yaml 反序列化为 Dictionary

static void DeserializeDictTest()
{
    string yaml = @"
id: 1
name: Tom
";
    Console.WriteLine(ChoYamlReader.DeserializeText<Dictionary
                      <string, object>>(yaml).FirstOrDefault().Dump());
}

20.13. 如何从文件中反序列化?

此示例将 yaml 反序列化为 Dictionary

public class Movie
{
    public string Name { get; set; }
    public int Year { get; set; }
}

static void DeserializeFromFile()
{
    Movie movie1 = ChoYamlReader.Deserialize<Movie>("emps.yaml").FirstOrDefault();
}

20.14. 如何使用自定义工厂进行反序列化?

此示例使用自定义工厂反序列化 yaml,以实例化 Person 类型的 Employee 实例。

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
}

public class Employee : Person
{
    public string Department { get; set; }
    public string JobTitle { get; set; }
}

static void CustomCreationTest()
{
    ChoActivator.Factory = (type, args) =>
    {
        if (type == typeof(Person))
            return new Employee();
        else
            return null;
    };
    Person person = ChoYamlReader.DeserializeText<Person>(yaml).FirstOrDefault();
    Console.WriteLine(person.GetType().Name);
}

20.15 读取整个文件 - 使用简单数据结构

通常,您需要读取文件的全部内容,因此使用 YAML 路径过滤器没有帮助。您想要做的是将 YAML 文件映射到一个可以直接使用的数据结构。

class PersonData
{
    public DateTime Creation { get; set; }
    public string Firstname { get; set; }
    public string Lastname { get; set; }
    public int Age { get; set; }
}

static void ReadEntireFile1()
{
    string yaml = @"
creation: 2020-12-26
firstname: John
lastname: Doe
age: 35";

    using (var parser = ChoYamlReader<PersonData>.LoadText(yaml))
    {
        foreach (var e in parser)
        {
            Console.WriteLine(e.Firstname); // John
            Console.WriteLine(e.Lastname);  // Doe
        }
    }
}

20.16 读取整个文件 - 使用多个列表

通常,您有一个包含多个列表的文件。这些只需使用不同结构的数组进行定义。

class ChildData
{
    public string Firstname { get; set; }
    public string Lastname { get; set; }
    public DateTime Dob { get; set; }
}
class AddressData
{
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
    public string Country { get; set; }
}
class ParentData
{
    public DateTime Creation { get; set; }
    public string Firstname { get; set; }
    public string Lastname { get; set; }
    public int Age { get; set; }
    public ChildData[] Children  { get; set; }
    public AddressData[] Addresses  { get; set; }
}

static void ReadEntireFile2()
{
    string yaml = @"
creation: 2020-12-26
firstname: John
lastname: Doe
age: 35
children:
- firstname: Emmanuel
  lastname: Doe
  dob: 1990-01-02
- firstname: Elise
  lastname: Doe
  dob: 1996-10-02
addresses:
- street: 1234 California Ave
  city: San Francisco
  state: CA
  zip: 98765
  country: USA
- street: 666 Midtown Ct
  city: Palo Alto
  zip: 94444
  state: CA
  country: USA
";

    using (var parser = ChoYamlReader<ParentData>.LoadText(yaml))
    {
        foreach (var e in parser)
        {
            Console.WriteLine(e.Firstname); // John
            Console.WriteLine(e.Lastname);  // Doe
            Console.WriteLine(e.Children[0].Firstname); // Emmanuel
            Console.WriteLine(e.Addresses[0].Street);   // 1234 California Ave
        }
    }
}

20.17 使用通用数据结构 (IDictionnary)

尽管这更麻烦,而且可能只在某些非常特定的情况下使用(参见 FAQ 20.19),但了解 ChoYamlReader 的逻辑及其如何处理读取的内容很有用。以下内容与 12.15 中的内容相同,但没有假设文件中的任何内容。请注意,您必须进行丑陋的类型转换和手动工作才能获取值。

static void ReadEntireFileAnonymous()
{
    string yaml = @"
creation: 2020-12-26
firstname: John
lastname: Doe
age: 35";
    using (var parser = ChoYamlReader<IDictionary<string, object>>.LoadText(yaml))
    {
        foreach (var e in parser)
        {
            string firstname = (string) e["firstname"];                 // John
            DateTime creation = DateTime.Parse((string) e["creation"]); // 2020-12-26
            int age = (int) e["age"];                                   // 35
            Console.WriteLine(e.Dump());
        }
    }
}

20.18 使用通用数据列表 (IList)

同样,您可以读取匿名数据列表。在此,此示例仅用于解释其内部工作原理,如 20.16 中所述,使用数组更安全、更方便。

class ChildrenData
{
    public IList<object> Children { get; set; }
}

static void ReadAnonymousLists()
{
    string yaml = @"
children:
- firstname: Emmanuel
  lastname: Doe
  dob: 1990-01-02
- firstname: Elise
  lastname: Doe
  dob: 1996-10-02
";
    using (var parser = ChoYamlReader<ChildrenData>.LoadText(yaml))
    {
        foreach (var e in parser)
        {
            IDictionary<object, object> firstChild = 
                                (IDictionary<object, object>) e.Children[0];
            string firstname = (string) firstChild["firstname"];    // Emmanuel
            Console.WriteLine(e.Dump());
        }
    }
}

20.19 读取整个文件 - 动态结构

有时,文件格式可能差异很大,并且在同一级别包含非同类数据。我们不会争论这是否是好的做法,但有时这就是计算机科学的现实。

例如,这里有一系列包含 type 字段的财产,以及取决于对象类型的属性。结合 20.17 和 20.18,这会产生

class OwnerData
{
    public PossessionData[] Possessions { get; set; }
}

class PossessionData
{
    public string Type { get; set; }
    public IDictionary<string, object> Description  { get; set; }
}

static void ReadDynamicData()
{
    string yaml = @"
possessions:
- type: car
  description:
    color: blue
    doors: 4
- type: computer
  description:
    disk: 1 TB
    memory: 16 MB
";
    using (var parser = ChoYamlReader<OwnerData>.LoadText(yaml))
    {
        foreach (var e in parser)
        {
            string carColor = (string) e.Possessions[0].Description["color"]; // blue
            foreach (var p in e.Possessions)
            {
                Console.WriteLine(p.Description.Dump());
            }
        }
    }
}

历史

  • 2020 年 7 月 1 日:初始版本
Cinchoo ETL - YAML Reader - CodeProject - 代码之家
© . All rights reserved.