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

Cinchoo ETL - XML 读取器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (12投票s)

2017年2月19日

CPOL

31分钟阅读

viewsIcon

38399

.NET 简单的 XML 文件读取器

目录

  1. 引言
  2. 要求
  3. “Hello World!”示例
    1. 快速加载 - 数据优先方法
    2. 代码优先方法
    3. 配置优先方法
    4. 带声明式配置的代码优先方法
  4. 读取所有记录
  5. 手动读取记录
  6. 自定义 XML 记录
  7. 自定义 XML 字段
    1. 默认值
    2. ChoFallbackValue
    3. 类型转换器
      1. 声明式方法
      2. 配置方法
    4. 验证
  8. 回调机制
    1. 使用 ChoXmlReader 事件
    2. 实现 IChoNotifyRecordRead 接口
    3. BeginLoad
    4. EndLoad
    5. BeforeRecordLoad
    6. AfterRecordLoad
    7. RecordLoadError
    8. BeforeRecordFieldLoad
    9. AfterRecordFieldLoad
    10. RecordLoadFieldError
  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. 日期时间支持
    6. CDATA 支持
  19. Fluent API
    1. WithXPath
    2. WithXmlNamespaceManager
    3. WithXmlNamespace
    4. WithFields
    5. WithField
    6. ColumnCountStrict
  20. 历史

1. 引言

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

本文介绍了如何使用 ChoETL 框架提供的 ChoXmlReader 组件。它是一个简单的实用类,用于将文件/源中的 XML 数据抽取到对象中。

更新:对应的 XmlWriter 文章可以在 此处找到。

特点

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

2. 要求

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

3. “Hello World!” 示例

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

让我们从一个读取包含两列的简单 XML 文件的示例开始。

清单 3.1 示例 XML 数据文件
<Employees>
    <Employee Id='1'>
        <Name>Tom</Name>
    </Employee>
    <Employee Id='2'>
        <Name>Mark</Name>
    </Employee>
</Employees>

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

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

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

清单 3.1.1 使用迭代器加载 XML 文件
foreach (dynamic rec in new ChoXmlReader("emp.xml"))
{
    Console.WriteLine(rec.Id);
    Console.WriteLine(rec.Name);
}

示例 Fiddlehttps://dotnetfiddle.net/X4nJO9

清单 3.1.2 使用循环加载 XML 文件
var reader = new ChoXmlReader("emp.xml");
dynamic rec;
 
while ((rec = reader.Read()) != null)
{
    Console.WriteLine(rec.Id);
    Console.WriteLine(rec.Name);
}

示例 Fiddlehttps://dotnetfiddle.net/bm47gw

3.2. 代码优先方法

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

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

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

清单 3.2.2 加载 XML 文件
foreach (var e in new ChoXmlReader<EmployeeRec>("emp.xml"))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

示例 Fiddlehttps://dotnetfiddle.net/C2B7KO

3.3. 配置优先方法

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

清单 3.3.1 定义 XML 配置
ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();
config.XmlRecordFieldConfigurations.Add(new ChoXmlRecordFieldConfiguration("Id"));
config.XmlRecordFieldConfigurations.Add(new ChoXmlRecordFieldConfiguration("Name"));

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

清单 3.3.2 不带 POCO 对象加载 XML 文件
foreach (dynamic e in new ChoXmlReader("emp.xml", config))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

示例 Fiddlehttps://dotnetfiddle.net/hHbVLP

清单 3.3.3 带 POCO 对象加载 XML 文件
foreach (var e in new ChoXmlReader<EmployeeRec>("emp.xml", config))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

示例 Fiddlehttps://dotnetfiddle.net/U3QO5e

3.4. 代码优先声明式配置

这是将 POCO 实体类与 XML 配置参数声明式结合的方法。Id 是必需的列,Name 是可选的值列,默认值为“XXXX”。如果 Name 不存在,则使用默认值。这是 OptIn 方法,仅指示被装饰的成员参与序列化操作。任何未用 ChoXmlNodeRecordFieldAttribute 装饰的成员都将被忽略。

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

上面的代码说明了如何定义 POCO 对象来承载输入文件中每条记录行的值。首先,为每个记录字段定义一个带有 ChoXmlNodeRecordFieldAttribute 的属性,以符合 XML 记录映射的资格。XPath 是一个可选属性。如果未指定,框架会自动发现并加载 xmlattributexmlelement 的值。Id 被装饰了 RequiredAttribute,如果值缺失,将引发异常。Name 使用 DefaultValueAttribute 指定了默认值。这意味着如果文件中的 Name XML 列包含空值,它将被默认为“XXXX”值。

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

清单 3.4.2 主方法
foreach (var e in new ChoXmlReader<EmployeeRec>("emp.xml"))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

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

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

示例 Fiddlehttps://dotnetfiddle.net/lnO6ft

3.4.1. 使用 BCL 序列化属性

这是另一种方法,使用 BCL 序列化属性(即 XmlElementAttribute, XmlAttributeAttribute 等)来装饰 POCO 对象的成员。

清单 3.4.1.1 定义 POCO 对象
public class EmployeeRec
{
    [XmlAttribute]
    [Required]
    [ChoXPath("@Id")]   //Optional
    public int Id
    {
        get;
        set;
    }
    [XmlElement]
    [DefaultValue("XXXX")]
    public string Name
    {
        get;
        set;
    }
 
    public override string ToString()
    {
        return "{0}. {1}".FormatString(Id, Name);
    }
}

上面的代码说明了如何定义 POCO 对象来承载输入文件中每条记录行的值。首先,为每个记录字段定义一个带有 XAttributeAttribute / XElementAttribute 的属性,以指定要映射到的节点类型。使用这些属性是可选的。如果未指定,框架会自动发现并适当地加载成员。ChoXPath 是一个可选属性,用于指定要加载的节点的 xpath。Id 被装饰了 RequiredAttribute,如果值缺失,将引发异常。Name 使用 DefaultValueAttribute 指定了默认值。这意味着如果文件中的 Name XML 列包含空值,它将被默认为“XXXX”值。

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

清单 3.4.1.2 主方法
foreach (var e in new ChoXmlReader<EmployeeRec>("emp.xml"))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

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

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

4. 读取所有记录

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

清单 4.1 读取 XML 文件
foreach (var e in new ChoXmlReader<EmployeeRec>("emp.xml"))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

清单 4.2 读取 XML 文件流
foreach (var e in new ChoXmlReader<EmployeeRec>(textReader))
{
    Console.WriteLine(e.Id);
    Console.WriteLine(e.Name);
}

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

清单 4.3 使用 LINQ
var list = (from o in new ChoXmlReader<EmployeeRec>("emp.xml")
           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. 手动读取记录

就像设置匹配 XML 文件结构的 POCO 对象一样容易,您可以以枚举模式读取整个文件。

清单 5.1 读取 XML 文件
var reader = new ChoXmlReader<EmployeeRec>("emp.xml");
EmployeeRec rec = null;
 
while ((rec = reader.Read()) != null)
{
    Console.WriteLine(rec.Id);
    Console.WriteLine(rec.Name);
}

示例 Fiddlehttps://dotnetfiddle.net/BYK1n4

6. 自定义 XML 记录

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

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

以下是用于执行文件上 XML 加载操作自定义的可用属性:

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

7. 自定义 XML 字段

对于每个 XML 列,您可以使用 ChoXmlNodeRecordFieldAttribute 在 POCO 实体属性中指定映射。只有当您想使用自定义 xpath 映射到此列时才使用此属性。否则,请使用简单的变体属性,即 ChoXmlElementRecordFieldAttribute/ChoXmlAttributeRecordFieldAttribute

清单 7.1 自定义 POCO 对象以匹配 XML 列
public class EmployeeRec
{
    [ChoXmlNodeRecordField(XPath="/@Id")]
    public int Id { get; set; }
    [ChoXmlNodeRecordField(XPath="/Name")]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}

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

  • XPath - 可选。XPath 表达式使用路径表示法,类似于 URL 中使用的表示法,用于定位 XML 文档的各个部分。如果未指定,ChoXmlReader 会自动发现并从 XmlElementXmlAttribute 加载值。如果未指定,ChoXmlReader 会自动发现并从与字段名匹配的 XElementXAttribute 加载值。
  • ErrorMode - 此标志指示在读取时是否应抛出异常,并且预期的字段加载失败。可能的值是
    • IgnoreAndContinue - 忽略错误,继续加载记录的其他属性。
    • ReportAndContinue - 如果 POCO 实体是 IChoRecord 类型,则向其报告错误。
    • ThrowAndStop - 抛出错误并停止执行。
  • IgnoreFieldValueMode - 一个标志,告知读取器在读取时是否应跳过空/null 的记录。可能的值是
    • Null - N/A
    • DBNull - N/A
    • Empty - 如果记录值为空,则跳过。
    • WhiteSpace - 如果记录值仅包含空格,则跳过。

7.1. 默认值

当 XML 值为空或仅包含空格时(通过 IgnoreFieldValueMode 控制),使用的值将被设置到属性。

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

7.2. 后备值

当 XML 值未能设置时使用的值。Fallback 值仅在 ErrorModeIgnoreAndContinueReportAndContinue 时设置。

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

7.3. 类型转换器

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

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

  • 声明式方法
  • 配置方法

7.3.1. 声明式方法

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

图 7.3.1.1 指定类型转换器
public class EmployeeRec
{
    [ChoXmlNodeRecordField(XPath: "/@Id")]
    [ChoTypeConverter(typeof(IntConverter))]
    public int Id { get; set; }
    [ChoXmlNodeRecordField(XPath: "/Name")]
    [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”XML 属性。

7.3.2. 配置式方法

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

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

ChoXmlNodeRecordFieldConfiguration idConfig = 
      new ChoXmlNodeRecordFieldConfiguration("Id", XPath: "/@Id");
idConfig.AddConverter(new IntConverter());
config.XmlRecordFieldConfigurations.Add(idConfig);

config.XmlRecordFieldConfigurations.Add
       (new ChoXmlNodeRecordFieldConfiguration("Name", XPath: "/Name"));
config.XmlRecordFieldConfigurations.Add
       (new ChoXmlNodeRecordFieldConfiguration("Name1", XPath: "/Name1"));

在上面,我们使用 ChoXmlNodeRecordFieldConfiguration 对象中的 AddConverter 辅助方法来构建和附加 IntConverter 到“Id”字段。

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

7.4. 验证

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

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

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

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

static void ValidationOverridePOCOTest()
{
    ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();
    var idConfig = new ChoXmlNodeRecordFieldConfiguration("Id", XPath: "/@Id");
    idConfig.Validators = new ValidationAttribute[] { new RequiredAttribute() };
    config.XmlRecordFieldConfigurations.Add(idConfig);
    config.XmlRecordFieldConfigurations.Add
      (new ChoXmlNodeRecordFieldConfiguration("Name", XPath: "/Name));
    config.XmlRecordFieldConfigurations.Add
      (new ChoXmlNodeRecordFieldConfiguration("Salary", XPath: "/Salary"));
 
    using (var parser = new ChoXmlReader<EmployeeRecWithCurrency>("emp.xml", config))
    {
        object rec;
        while ((rec = parser.Read()) != null)
        {
            Console.WriteLine(rec.ToStringEx());
        }
    }
}
 
public class EmployeeRecWithCurrency
{
    public int? Id { get; set; }
    public string Name { get; set; }
    public ChoCurrency Salary { get; set; }
}

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

图 7.4.2 在 POCO 实体上手动验证
[ChoXmlRecordObject]
public partial class EmployeeRec : IChoValidatable
{
    [ChoXmlNodeRecordField(FieldName = "id", XPath: "/@Id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
 
    [ChoXmlNodeRecordField(FieldName = "Name", XPath: "/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. 回调机制

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

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

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

IChoReader 公开了以下事件

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

IChoNotifyRecordRead 公开了以下方法

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

IChoNotifyFileRead 公开了以下方法

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

IChoNotifyRecordFieldRead 公开以下方法

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

IChoNotifyRecordConfigurable 公开了以下方法:

  • RecordConfigure - 在 XML 记录配置时引发

IChoNotifyRecordFieldConfigurable 公开了以下方法:

  • RecondFieldConfigure - 在每个 XML 记录字段配置时引发

8.1. 使用 ChoXmlReader 事件

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

下面的示例展示了如何使用 BeforeRecordLoad 回调方法来跳过 Id < 100 的节点。

static void IgnoreNodeTest()
{
    using (var parser = new ChoXmlReader("emp.xml"))
    {
        parser.BeforeRecordLoad += (o, e) =>
        {
            if (e.Source != null)
            {
                e.Skip = ((XElement)e.Source).Attribute["Id"].Value < 100;
            }
        };
        foreach (var e in parser)
            Console.WriteLine(e.Dump());
    }
}

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

8.2. 实现 IChoNotifyRecordRead 接口

下面的示例展示了如何为直接 POCO 类实现 IChoNotifyRecordRead 接口。

清单 8.2.1 直接 POCO 回调机制实现
[ChoXmlRecordObject]
public partial class EmployeeRec : IChoNotifyRecordRead
{
    [ChoXmlNodeRecordField(FieldName = "Id", XPath: "/@Id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
    
    [ChoXmlNodeRecordField(FieldName = "Name", XPath: "/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 RecordFieldLoadError
    (object target, int index, string propName, object value, Exception ex)
    {
        throw new NotImplementedException();
    }
}

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

清单 8.2.2 基于 MetaDataType 的回调机制实现
[ChoXmlRecordObject]
public class EmployeeRecMeta : IChoNotifyRecordRead
{
    [ChoXmlNodeRecordField(FieldName = "Id", XPath: "/@Id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }

    [ChoXmlNodeRecordField(FieldName = "Name", XPath: "/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 RecordFieldLoadError
    (object target, int index, string propName, object value, Exception ex)
    {
        throw new NotImplementedException();
    }
} 

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

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

清单 8.2.3 基于 ChoMetaDataRefType 的回调机制实现
[ChoMetadataRefType(typeof(EmployeeRec))]
[ChoXmlRecordObject]
public class EmployeeRecMeta : IChoNotifyRecordRead
{
    [ChoXmlNodeRecordField(FieldName = "Id", XPath: "/@Id")]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }

    [ChoXmlNodeRecordField(FieldName = "Name", XPath: "/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

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

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

8.4. EndLoad

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

清单 8.4.1 EndLoad 回调示例
public void EndLoad(object source)
{
    StreamReader sr = source as StreamReader;
}

8.5. BeforeRecordLoad

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

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

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

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

清单 8.5.1 BeforeRecordLoad 回调示例
public bool BeforeRecordLoad(object target, int index, ref object source)
{
    XNode node = source as XNode; 
    return true;
}

8.6. AfterRecordLoad

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

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

清单 8.6.1 AfterRecordLoad 回调示例
public bool AfterRecordLoad(object target, int index, object source)
{
    XNode node = source as XNode;
    return true;
}

8.7. RecordLoadError

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

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

清单 8.7.1 RecordLoadError 回调示例
public bool RecordLoadError(object target, int index, object source, Exception ex)
{
    XNode node = source as XNode;
    return true;
}

8.8. BeforeRecordFieldLoad

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

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

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

8.9. AfterRecordFieldLoad

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

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

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

8.10. RecordLoadFieldError

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

  • ChoXmlReader 查找每个 XML 属性的 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;
}

9. 自定义

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

清单 9.1 运行时自定义 ChoXmlReader
class Program
{
    static void Main(string[] args)
    {
        using (var parser = new ChoXmlReader<EmployeeRec>("emp.xml"))
        {
            object row = null;
  
            parser.Configuration.ColumnCountStrict = true;
            while ((row = parser.Read()) != null)
                Console.WriteLine(row.ToString());
        }
    }

10. AsDataReader 辅助方法

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

清单 10.1 作为 DataReader 读取示例
static void AsDataReaderTest()
{
    using (var parser = new ChoXmlReader<EmployeeRec>("emp.xml"))
    {
        IDataReader dr = parser.AsDataReader();
        while (dr.Read())
        {
            Console.WriteLine("Id: {0}, Name: {1}", dr[0], dr[1]);
        }
    }
}

11. AsDataTable 辅助方法

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

清单 11.1 作为 DataTable 读取示例
static void AsDataTableTest()
{
    using (var parser = new ChoXmlReader<EmployeeRec>("emp.xml"))
    {
        DataTable dt = parser.AsDataTable();
        foreach (DataRow dr in dt.Rows)
        {
            Console.WriteLine("Id: {0}, Name: {1}", dr[0], dr[1]);
        }
    }
}

12. 使用动态对象

到目前为止,本文档解释了如何使用 POCO 对象。ChoXmlReader 也支持在没有 POCO 对象的情况下加载 XML 文件。它利用 .NET 的动态特性。下面的示例展示了如何在没有 POCO 对象的情况下读取 XML 流。

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

下面的示例展示了这一点

清单 12.1 加载 XML 文件
class Program
{
    static void Main(string[] args)
    {
        dynamic row;
        using (var parser = new ChoXmlReader("emp.xml"))
        {
            while ((row = parser.Read()) != null)
            {
                Console.WriteLine(row.Id);
            }
        }
    }
}

上面的示例自动发现 XML 元素/属性并解析文件。

您可以通过手动添加字段配置并将它们传递给 ChoXmlReader 来解析文件,从而覆盖自动发现列的默认行为。

示例展示了如何操作。

清单 12.3 使用配置加载 XML 文件
class Program
{
    static void Main(string[] args)
    {
        ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();
        config.XmlRecordFieldConfigurations.Add
        (new ChoXmlNodeRecordFieldConfiguration("Id", XPath: "/@Id"));
        config.XmlRecordFieldConfigurations.Add
        (new ChoXmlNodeRecordFieldConfiguration("Name", XPath: "/Name"));

        dynamic row;
        using (var parser = new ChoXmlReader("Emp.xml", config))
        {
            while ((row = parser.Read()) != null)
            {
                Console.WriteLine(row.Name);
            }
        }
    }
}

要完全关闭自动列发现,您需要将 ChoXmlRecordConfiguration.AutoDiscoverColumns 设置为 false

12.1. DefaultValue

当 XML 值为空或仅包含空格时(通过 IgnoreFieldValueMode 控制),使用的值将被设置到属性。

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

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

ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();
config.XmlRecordFieldConfigurations.Add(new ChoXmlNodeRecordFieldConfiguration("Id"));
config.XmlRecordFieldConfigurations.Add
  (new ChoXmlNodeRecordFieldConfiguration("Name") { DefaultValue = "NoName" })

12.2. ChoFallbackValue

当 XML 值未能设置时,使用的值将被设置到属性。Fallback 值仅在 ErrorModeIgnoreAndContinueReportAndContinue 时设置。

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

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

ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();
config.XmlRecordFieldConfigurations.Add(new ChoXmlNodeRecordFieldConfiguration("Id"));
config.XmlRecordFieldConfigurations.Add
  (new ChoXmlNodeRecordFieldConfiguration("Name") { FallbackValue = "Tom" });

12.3. FieldType

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

清单 12.3.1 定义 FieldType
ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();
config.XmlRecordFieldConfigurations.Add
  (new ChoXmlNodeRecordFieldConfiguration("Id") { FieldType = typeof(int) });
config.XmlRecordFieldConfigurations.Add
  (new ChoXmlNodeRecordFieldConfiguration("Name"));

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

12.4. Type Converters

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

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

清单 12.4.1 指定 TypeConverters
ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();

ChoXmlNodeRecordFieldConfiguration idConfig = 
      new ChoXmlNodeRecordFieldConfiguration("Id");
idConfig.AddConverter(new IntConverter());
config.XmlRecordFieldConfigurations.Add(idConfig);

config.XmlRecordFieldConfigurations.Add
       (new ChoXmlNodeRecordFieldConfiguration("Name"));
config.XmlRecordFieldConfigurations.Add
       (new ChoXmlNodeRecordFieldConfiguration("Name1"));

在上面,我们使用 ChoXmlNodeRecordFieldConfiguration 对象中的 AddConverter 辅助方法来构建和附加 IntConverter 到“Id”字段。

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

12.5. Validations

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

清单 12.5.1 指定 Validations
ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();
config.ThrowAndStopOnMissingField = false;

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

config.XmlRecordFieldConfigurations.Add
       (new ChoXmlNodeRecordFieldConfiguration("Name"));
config.XmlRecordFieldConfigurations.Add
       (new ChoXmlNodeRecordFieldConfiguration("Name1"));

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

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

13. 使用密封 POCO 对象

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

清单 13.1 现有密封 POCO 对象
public sealed class ThirdPartyRec
{
    public int Id
    {
        get;
        set;
    }
    public string Name
    {
        get;
        set;
    }
}
清单 13.2 消费 XML 文件
class Program
{
    static void Main(string[] args)
    {
        using (var parser = new ChoXmlReader<ThirdPartyRec>("Emp.xml"))
        {
            object row = null;
 
            while ((row = parser.Read()) != null)
                Console.WriteLine(row.ToString());
        }
    }
}

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

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

14. 异常

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

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

15. 使用 MetadataType 注解

Cinchoo ETL 与数据注释的 MetadataType 模型配合得更好。这是一种将 MetaData 类附加到数据模型类的方法。在此关联类中,您可以提供数据模型中不存在的附加 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; }
}

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

    [ChoXmlNodeRecordField(FieldName = "Name")]
    [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 中。

16. 配置选项

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

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

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

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

16.1 手动配置

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

图 16.1.1 手动配置
ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();
config.ThrowAndStopOnMissingField = true;
config.XmlRecordFieldConfigurations.Add(new ChoXmlNodeRecordFieldConfiguration("Id"));
config.XmlRecordFieldConfigurations.Add(new ChoXmlNodeRecordFieldConfiguration("Name"));

16.2 自动映射配置

这是一种替代方法,也是一种不易出错的方法,可以自动映射 POCO 实体类的 XML 列。

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

清单 16.2.1 Auto Map 类
public class EmployeeRecMap
{
    [ChoXmlNodeRecordField(FieldName = "Id")]
    public int Id { get; set; }
 
    [ChoXmlNodeRecordField(FieldName = "Name")]
    public string Name { get; set; } 
}

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

清单 16.2.2 使用 Auto Map 配置
ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();
config.MapRecordFields<EmployeeRecMap>();

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

16.3 附加 MetadataType 类

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

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

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

    [ChoXmlNodeRecordField(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 ChoXmlReader<EmployeeRec>("Emp.xml")) 
    Console.WriteLine(e.ToString()

17. LoadText 辅助方法

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

清单 17.1 使用 LoadText 方法
string txt = @"
            <Employees>
                <Employee Id='1'>
                    <Name>Tom</Name>
                </Employee>
                <Employee Id='2'>
                    <Name>Mark</Name>
                </Employee>
            </Employees>
        ";
foreach (var e in ChoXmlReader.LoadText(txt))
   Console.WriteLine(e.ToStringEx());

18. 高级主题

18.1 覆盖转换器格式规范

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

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

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

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

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

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

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

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

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

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

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

清单 18.1.2 在代码中使用 ChoTypeConverterFormatSpec
static void UsingFormatSpecs()
{
    ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();
    config.Culture = new System.Globalization.CultureInfo("se-SE");
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("Id") { FieldType = typeof(int) });
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("Name"));
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("Salary") 
                         { FieldType = typeof(ChoCurrency) });
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("JoinedDate") 
                         { FieldType = typeof(DateTime) });
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("EmployeeNo") 
                         { FieldType = typeof(int) });
 
    ChoTypeConverterFormatSpec.Instance.IntNumberStyle = NumberStyles.AllowParentheses;
 
    using (var parser = new ChoXmlReader("Emp.xml", config))
    {
        object row = null;
 
        while ((row = parser.Read()) != null)
            Console.WriteLine(row.ToStringEx());
    }
}

18.2 货币支持

Cinchoo ETL 提供 ChoCurrency 对象来读取和写入 XML 文件中的货币值。ChoCurrency 是一个包装类,用于以 decimal 类型存储货币值,并在 XML 加载期间以文本格式序列化它们。

清单 18.2.1 在动态模型中使用 Currency 成员
static void CurrencyDynamicTest()
{
    ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();
    config.XmlRecordFieldConfigurations.Add
           (new ChoXmlNodeRecordFieldConfiguration("Id"));
    config.XmlRecordFieldConfigurations.Add
           (new ChoXmlNodeRecordFieldConfiguration("Name"));
    config.XmlRecordFieldConfigurations.Add
           (new ChoXmlNodeRecordFieldConfiguration("Salary") 
    { FieldType = typeof(ChoCurrency) });
 
    using (var parser = new ChoXmlReader("Emp.xml", config))
    {
        object rec;
        while ((rec = parser.Read()) != null)
        {
            Console.WriteLine(rec.ToStringEx());
        }
    }
}

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

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

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

清单 18.2.2 在 POCO 模型中使用 Currency 成员
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 ChoXmlReader<EmployeeRecWithCurrency>("Emp.xml"))
    {
        object rec;
        while ((rec = parser.Read()) != null)
        {
            Console.WriteLine(rec.ToStringEx());
        }
    }
}

18.3 枚举支持

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

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

可以使用三个可能的值:

  1. ChoEnumFormatSpec.Value - Enum 值用于解析。
  2. ChoEnumFormatSpec.Name - Enum 键名用于解析。
  3. ChoEnumFormatSpec.Description - 如果每个 enum 键都用 DescriptionAttribute 修饰,则使用其值进行解析。
清单 18.3.1 解析时指定 Enum 格式规范
public enum EmployeeType
{
    [Description("Full Time Employee")]
    Permanent = 0,
    [Description("Temporary Employee")]
    Temporary = 1,
    [Description("Contract Employee")]
    Contract = 2
}

static void EnumTest()
{
    ChoTypeConverterFormatSpec.Instance.EnumFormat = ChoEnumFormatSpec.Description;
 
    ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("Id") { FieldType = typeof(int) });
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("Name"));
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("Salary") 
                   { FieldType = typeof(ChoCurrency) });
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("JoinedDate") 
                   { FieldType = typeof(DateTime) });
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("EmployeeType") 
                   { FieldType = typeof(EmployeeType) });
 
    ChoTypeConverterFormatSpec.Instance.IntNumberStyle = NumberStyles.AllowParentheses;
 
    using (var parser = new ChoXmlReader("Emp.xml", config))
    {
        object row = null;
 
        while ((row = parser.Read()) != null)
            Console.WriteLine(row.ToStringEx());
    }
}

18.4 布尔值支持

Cinchoo ETL 会隐式处理从 XML 文件中解析布尔 XML 列值。如果您想精细控制这些值的解析,可以通过 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;
 
    ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("Id") { FieldType = typeof(int) });
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("Name"));
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("Salary") 
                         { FieldType = typeof(ChoCurrency) });
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("JoinedDate") 
                         { FieldType = typeof(DateTime) });
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("Active") { FieldType = typeof(bool) });
 
    ChoTypeConverterFormatSpec.Instance.IntNumberStyle = NumberStyles.AllowParentheses;
 
    using (var parser = new ChoXmlReader("Emp.xml", config))
    {
        object row = null;
 
        while ((row = parser.Read()) != null)
            Console.WriteLine(row.ToStringEx());
    }
}

18.5 DateTime 支持

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

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

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

清单 18.5.1 解析时指定 datetime 格式规范
static void DateTimeTest()
{
    ChoTypeConverterFormatSpec.Instance.DateTimeFormat = "MMM dd, yyyy";
 
    ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("Id") { FieldType = typeof(int) });
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("Name"));
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("Salary") 
                         { FieldType = typeof(ChoCurrency) });
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("JoinedDate") 
                         { FieldType = typeof(DateTime) });
    config.XmlRecordFieldConfigurations.Add
    (new ChoXmlNodeRecordFieldConfiguration("Active") { FieldType = typeof(bool) });
 
    ChoTypeConverterFormatSpec.Instance.IntNumberStyle = NumberStyles.AllowParentheses;
 
    using (var parser = new ChoXmlReader("Emp.xml", config))
    {
        object row = null;
 
        while ((row = parser.Read()) != null)
            Console.WriteLine(row.ToStringEx());
    }
}

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

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

18.6 CDATA 支持

Cinchoo ETL 会隐式处理从 XML 文件中解析 CDATA XML 值。CDATA 值也可以加载为 string 对象或 ChoCDATA 对象。

清单 18.6.1 将 CDATA 加载为字符串对象
public class EmployeeRec
{
    [ChoXmlNodeRecordField()]
    [Required]
    public int Id
    {
        get;
        set;
    }
    [ChoXmlNodeRecordField()]
    [DefaultValue("XXXX")]
    public string Name
    {
        get;
        set;
    }
    [ChoXmlNodeRecordField()]
    [DefaultValue("XXXX")]
    public string Message
    {
        get;
        set;
    }
 
    public override string ToString()
    {
        return "{0}. {1}.".FormatString(Id, Name);
    }
}
清单 18.6.2 将 CDATA 加载为本机 CDATA 对象本身

ChoETL 提供 ChoCDATA 类来以本机格式存储 CDATA XML 值。ChoXmlReader 会自动处理此值的解析并相应地加载它们。

public class EmployeeRec
{
    [ChoXmlNodeRecordField()]
    [Required]
    public int Id
    {
        get;
        set;
    }
    [ChoXmlNodeRecordField()]
    [DefaultValue("XXXX")]
    public string Name
    {
        get;
        set;
    }
    [ChoXmlNodeRecordField()]
    [DefaultValue("XXXX")]
    public ChoCDATA Message
    {
        get;
        set;
    }
 
    public override string ToString()
    {
        return "{0}. {1}.".FormatString(Id, Name);
    }
}

19. Fluent API

XmlReader 通过 Fluent API 方法公开了一些常用配置参数。这将使 XML 文件解析的编程更加快捷。

19.1 WithXPath

此 API 方法设置 XPath 表达式,用于使用 XmlReader 选择要加载的节点。

foreach (var e in new ChoXmlReader<EmployeeRec>
        ("Emp.xml").WithXPath("Employees/Employee"))
    Console.WriteLine(e.ToString());

19.2 WithXmlNamespaceManager

此 API 方法设置 XML 命名空间管理器,为这些命名空间提供范围管理。它以 string 形式存储前缀和命名空间。这用于 xpath 引用了命名空间限定元素和属性名称的 XML 节点。

foreach (var e in new ChoXmlReader<EmployeeRec>("Emp.xml").WithXNamespaceManager(ns))
    Console.WriteLine(e.ToString());

19.3 WithXmlNamespace

此 API 方法用于将 XML 命名空间添加到 XML 名称表中。这用于 xpath 引用了命名空间限定元素和属性名称的 XML 节点。

foreach (var e in new ChoXmlReader<EmployeeRec>
("Emp.xml").WithXNamespace("dd", "http://www.cinchoo.com/dd"))
    Console.WriteLine(e.ToString());

19.3 WithFields

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

foreach (var e in new ChoXmlReader<EmployeeRec>
("Emp.xml").WithFields("Id", "Name"))
    Console.WriteLine(e.ToString());

19.4 WithField

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

foreach (var e in new ChoXmlReader<EmployeeRec>
("Emp.xml").WithField("Id", "/Id", typeof(int)))
    Console.WriteLine(e.ToString());

19.5 ColumnCountStrict

此 API 方法用于设置 XmlWriter 在读取 XML 文件之前执行列计数检查。

foreach (var e in new ChoXmlReader<EmployeeRec>("Emp.xml").ColumnCountStrict())
    Console.WriteLine(e.ToString());

20. 历史记录

  • 2017年2月19日:初始版本
  • 2022年2月9日:文章更新
Cinchoo ETL - XML Reader - CodeProject - 代码之家
© . All rights reserved.