Cinchoo ETL - CSV 读取器
简单的 .NET CSV 文件读取器
目录
- 1. 简介
- 2. 要求
- 3. “Hello World!” 示例
- 4. 读取所有记录
- 5. 手动读取记录
- 6. 自定义 CSV 记录
- 7. 自定义 CSV 标题
- 8. 自定义 CSV 字段
- 9. Excel 字段分隔符
- 10. 回调机制
- 10. 定制
- 11. AsDataReader 辅助方法
- 12. AsDataTable 辅助方法
- 13. 使用动态对象
- 14. 处理已密封的 POCO 对象
- 15. 异常
- 16. 技巧
- 17. 使用 MetadataType 注解
- 18. 配置选项
- 19. LoadText 辅助方法
- 20. 高级主题
- 21. Fluent API
- 21.1 WithDelimiter
- 21.2. WithFirstLineHeader
- 21.3. WithFields
- 21.4. WithField
- 21.5. QuoteAllFields
- 21.6. ColumnCountStrict
- 21.6. ColumnOrderStrict
- 21.8. NotifyAfter
- 21.9. Configure
- 21.10. Setup
- 21.11. IgnoreHeader
- 21.12. WithEOLDelimiter
- 21.13. WithHeaderLineAt
- 21.14. WithMaxScanRows
- 21.15. MayHaveQuotedFields
- 22. FAQ
- 22.1. 我想读取带标题的文件
- 22.1. 文件中有我想要跳过的行
- 22.2. 数据文件中的字符没有被完全读取
- 22.3. 如何处理多行记录
- 22.4. 如何处理错误并记录它们
- 22.5. 如何处理包含空格或特殊字符的列标题?
- 22.6. 加载过程中 DateTime 格式化失败?
- 22.7. 如何处理重复的 CSV 列?
- 22.8. 如果 CSV 标题不在第一行怎么办?
- 22.9. 如何关闭加载的记录或其他跟踪消息?
- 22.10. 如何为 CSV 列指定默认值?
- 22.11. Cinchoo ETL 支持分层对象吗?
- 22.12. Cinchoo 驱动程序是否自动发现列数据类型?
- 22.13 如何在 CSV 文件中注释或忽略行?
- 22.14 如何将 CSV 文件批量复制到 SqlServer (任何数据库)?
- 22.15. 获取 CSV 文件列名的最佳方法是什么?
- 22.16. 有没有办法通过不同的列名解析 CSV,并进行映射?
- 22.17. 如何将 CSV 文件提取到 DataTable?
- 22.18. 如何将 CSV 文件提取到 DataReader?
- 22.19. 如何处理包含引号的列值?
- 22.20. 如何将 CSV 文件读入类型化的 DataTable?
- 22.21. 为了跟踪目的,我如何找到 CSV 文件中的空行?
- 22.22. 如何更改字段的顺序?
- 22.23. 如何关闭列类型发现?
- 22.24. 如何在 CSV 解析过程中有条件地跳过行?
- 22.25. 如何有条件地停止 CSV 解析?
- 22.26. Cinchoo 读取器支持将 CSV 加载到子类吗?
- 22.27. 如何在子对象上开启验证?
- 22.28. 如何为成员指定 CSV 列大小?
- 22.28. 如何为成员指定 CSV 列名?
- 22.29. 如何处理 CSV 中的特殊 null 值?
- 22.30 Cinchoo 是否处理货币值?
- 22.31 Cinchoo 能读取同一个 CSV 中的不同记录类型吗?
- 22.32. 如何验证 CSV 文件?
- 22.33. 如何对大型 CSV 文件进行排序?
- 22.34 如何从 CSV 加载中忽略成员?
- 22.35. 如何处理自定义日期时间格式的值?
- 22.36. 如何处理自定义布尔值
- 22.37. 如何处理自定义布尔值
- 22.38. 如何将 CSV 读取到集合/数组成员?
- 22.39. 如何正确地在 Excel 中保留前导/填充零?
- 23.40. ChoCSVReader 支持多行标题吗?
- 23.41. 如何将 CSV 加载到 POCO 类型的字典成员?
- 23.42. 读取动态记录时是否可以处理重复的名称?
- 23.43. ChoCSVReader 能自动查找 CSV 文件中的分隔符吗?
- 23. ChoTSVReader
- 25. 参考
1. 简介
ChoETL 是一个用于 .NET 的开源 ETL(提取、转换和加载)框架。它是一个基于代码的库,用于从多个源提取数据,在 .NET 环境中对其进行转换,并将其加载到您自己的数据仓库中。您可以立即将数据放入数据仓库。本文讨论了使用 ChoETL 框架提供的 CSVReader 组件。它是一个简单的类,用于从文件/源中提取 CSV 数据。
更新:相应的 CSV Lite 读取器可以在 这里 找到
相应的 CSVWriter 文章可以在 这里 找到。
特点
- 快速 CSV 读取器,在 15 秒内解析包含 15 列/500,000 条记录的 CSV 文件。(使用
LiteParsing
选项以有限的框架功能提高解析速度。) - 基于流的解析器能够实现极致的性能、低资源消耗,以及近乎无限的多功能性,可扩展到任何大小的数据文件,甚至数十或数百 GB。
- 基于事件的数据操作和验证允许在批量插入过程中对数据流进行完全控制。
- 遵循 CSV 标准文件规则。优雅地处理包含逗号和换行符的数据字段。
- 除了逗号,还可以使用大多数分隔字符,包括制表符分隔的字段。
- 公开
IEnumarable
对象列表 - 通常与 LINQ 查询一起用于投影、聚合和过滤等。 - 支持延迟读取。
- 支持处理具有特定文化背景的日期、货币和数字格式的文件。
- 支持不同的字符编码。
- 读取文件时识别各种日期、货币、枚举、布尔值和数字格式。
- 在写入文件时提供对日期、货币、枚举、布尔值、数字格式的精细控制。
- 详细而健壮的错误处理,让您能够快速查找和修复问题。
2. 要求
此框架库使用 C# 编写,基于 .NET 4.5 Framework / .NET core 2.x。
3. “Hello World!” 示例
- 打开 VS.NET 2013 或更高版本
- 创建一个示例 VS.NET (.NET Framework 4.5) 控制台应用程序项目
- 根据 .NET 环境,通过 程序包管理器控制台 使用 Nuget 命令安装 ChoETL
- Install-Package ChoETL
- Install-Package ChoETL.NETStandard
- 使用
ChoETL
命名空间
让我们从一个简单的示例开始,读取一个包含 2 列的 CSV 文件
清单 3.1 示例 CSV 数据文件 (Emp.csv)
Id,Name 1,Tom 2,Carl 3,Mark
有多种方法可以开始解析 CSV 文件,只需最少的设置
3.1. 快速加载 - 数据优先方法
这是 零配置 的快速方法,可以立即加载 CSV 文件。不需要 POCO 对象。下面的示例代码显示了如何加载文件
清单 3.1.1 使用迭代器加载 CSV 文件
foreach (dynamic rec in new ChoCSVReader("Emp.csv")
.WithFirstLineHeader())
{
Console.WriteLine($"Id: {rec.Id}");
Console.WriteLine($"Name: {rec.Name}");
}
示例 fiddle: https://dotnetfiddle.net/ttO8ii
清单 3.1.2 使用循环加载 CSV 文件
var reader = new ChoCSVReader("Emp.csv").WithFirstLineHeader();
dynamic rec;
while ((rec = reader.Read()) != null)
{
Console.WriteLine($"Id: {rec.Id}");
Console.WriteLine($"Name: {rec.Name}");
}
示例 fiddle: https://dotnetfiddle.net/ifUH6h
您也可以通过索引访问 csv 字段。下面的示例显示了如何通过索引访问它们
清单 3.1.3 按索引访问
foreach (dynamic rec in new ChoCSVReader("Emp.csv")
.WithFirstLineHeader())
{
Console.WriteLine($"Id: {rec[0]}");
Console.WriteLine($"Name: {rec[1]}");
}
示例 fiddle: https://dotnetfiddle.net/WQP9HU
如果 CSV 文件不带标题,CSVReader 会在动态对象中自动为列命名为 Column1, Column2...
3.2. 代码优先方法
这是另一种 零配置 的方法,可以使用 POCO 类来解析和加载 CSV 文件。首先定义一个简单的数据类来匹配底层 CSV 文件布局
清单 3.2.1 简单的 POCO 实体类
public partial class EmployeeRec
{
public int Id { get; set; }
public string Name { get; set; }
}
上面,类定义了两个属性,与示例 CSV 文件模板匹配。
清单 3.2.2 加载 CSV 文件
foreach (var rec in new ChoCSVReader<EmployeeRec>("Emp.csv")
.WithFirstLineHeader())
{
Console.WriteLine($"Id: {rec.Id}");
Console.WriteLine($"Name: {rec.Name}");
}
示例 fiddle: https://dotnetfiddle.net/b5hRhG
3.3. 配置优先方法
在此模型中,我们使用所有必要的解析参数以及与底层 CSV 文件匹配的 CSV 列来定义 CSV 配置。
清单 3.3.1 定义 CSV 配置
ChoCSVRecordConfiguration config = new ChoCSVRecordConfiguration();
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Id", 1));
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name", 2));
上面,类定义了两个属性,与示例 CSV 文件模板匹配。
清单 3.3.2 加载不带 POCO 对象的 CSV 文件
foreach (dynamic rec in new ChoCSVReader("Emp.csv", config)
.WithFirstLineHeader())
{
Console.WriteLine($"Id: {rec.Id}");
Console.WriteLine($"Name: {rec.Name}");
}
示例 fiddle: https://dotnetfiddle.net/VDxnSI
清单 3.3.3 加载带 POCO 对象的 CSV 文件
foreach (var rec in new ChoCSVReader<EmployeeRec>("Emp.csv", config).WithFirstLineHeader())
{
Console.WriteLine(String.Format("Id: {0}", rec.Id));
Console.WriteLine(String.Format("Name: {0}", rec.Name));
}
示例 fiddle: https://dotnetfiddle.net/Nalc2x
3.4. 带数据注解的代码优先方法
数据注解是一种简单的基于属性的配置方法,可以将属性应用于 POCO 类来配置模型。id
是必需的列,name
是可选的值列,默认值为 "XXXX
"。 如果 name
不存在,它将采用默认值。
清单 3.4.1 定义 POCO 对象
[ChoCSVFileHeader]
[ChoCSVRecordObject(ObjectValidationMode = ChoObjectValidationMode.ObjectLevel)]
public class EmployeeRec
{
[Required]
public int? Id
{
get;
set;
}
[DefaultValue("XXXX")]
public string Name
{
get;
set;
}
public override string ToString()
{
return $"{Id}. {Name}.";
}
}
上面的代码说明了如何定义 POCO 对象来承载输入文件中每条记录行的值。按照 CSV 列在文件中出现的顺序定义成员。[ChoCSVFileHeader]
属性告诉解析器第一行是标题行。默认情况下,解析器关闭了验证。要开启,您必须使用 ChoCSVRecordObject
属性指定 ChoObjectValidationMode
为 MemberLevel
/ ObjectLever
。Id
是一个必需的属性。我们用 RequiredAttribute
装饰了它。Name
使用 DefaultValueAttribute
设置了默认值。 这意味着如果文件中的 Name
CSV 列为空,它将被默认为“XXXX
”值。
它非常简单,可以立即提取 CSV 数据。
清单 3.4.2 主方法
class Program
{
static void Main(string[] args)
{
//direct the framework to show the error message to console
ChoETLFrxBootstrap.TraceLevel = TraceLevel.Error;
string csv = @"Id, Name
, Mark
2, Tom";
foreach (var rec in ChoCSVReader<EmployeeRec>.LoadText(csv))
{
Console.WriteLine($"Id: {rec.Id}");
Console.WriteLine($"Name: {rec.Name}");
}
}
}
我们通过创建一个 ChoCSVReader
对象的新实例开始。仅此而已。解析器在后台完成了解析并将 CSV 数据流加载到对象中的所有繁重工作。
默认情况下,CSVReader
在加载 CSV 文件时会发现并使用默认配置参数。这些可以根据您的需要进行覆盖。以下部分将详细介绍每个配置属性。
运行示例后,读取器发现第一条记录缺少 id 值,抛出错误并忽略该记录的加载。
Error [Failed to validate 'Program+EmployeeRec' object. The Id field is required. ] found. Ignoring record... Id: 2 Name: Carl Id: 3 Name: Mark
示例 fiddle: https://dotnetfiddle.net/MzWNsz
3.5. 使用 Fluent API 的代码优先方法
这是另一种方法,用于定义 POCO 实体类以及使用 Fluent API 进行自定义选择性 CSV 配置。定义 POCO 类
清单 3.5.1 定义 POCO 对象
public class EmployeeRec
{
public int Id
{
get;
set;
}
public string Name
{
get;
set;
}
public string Address
{
get;
set;
}
}
下面的示例显示了如何使用 Fluent API 进行自定义 CSV 字段映射。POCO 定义了 3 个成员,而 CSV 文件只有 Id、Name 列,您可以使用 fluent API 指定解析器仅考虑 Id、Name 列。
清单 3.5.2 主方法
class Program
{
static void Main(string[] args)
{
string csv = @"Id, Name
1, Mark
2, Tom";
using (var p = ChoCSVReader<EmployeeRec>.LoadText(csv)
.WithFirstLineHeader()
.WithField(c => c.Id)
.WithField(c => c.Name)
.ThrowAndStopOnMissingField(false)
)
{
foreach (var rec in p)
{
Console.WriteLine($"Id: {rec.Id}");
Console.WriteLine($"Name: {rec.Name}");
}
}
}
}
示例 fiddle: https://dotnetfiddle.net/SVRQdp
我们通过创建一个 ChoCSVReader
对象的新实例开始。仅此而已。解析器在后台完成了解析并将 CSV 数据流加载到对象中的所有繁重工作。
默认情况下,CSVReader
在加载 CSV 文件时会发现并使用默认配置参数。这些可以根据您的需要进行覆盖。以下部分将详细介绍每个配置属性。
4. 读取所有记录
就像设置匹配 CSV 文件结构的 POCO 对象一样简单,您可以以可枚举模式读取整个文件。这是延迟执行模式,但负责对其执行任何聚合操作。顺便说一句,在执行聚合操作时要小心,因为这些类型的操作会将整个文件记录加载到内存中。
清单 4.1 读取 CSV 文件
foreach (var rec in new ChoCSVReader<EmployeeRec>("Emp.csv").WithFirstLineHeader())
{
Console.WriteLine($"Id: {rec.Id}");
Console.WriteLine($"Name: {rec.Name}");
}
或
清单 4.2 读取 CSV 文件流
using (var stream = File.Open(@"test.csv", FileMode.Open))
{
foreach (var rec in new ChoCSVReader<EmployeeRec>(stream).WithFirstLineHeader())
{
Console.WriteLine($"Id: {rec.Id}");
Console.WriteLine($"Name: {rec.Name}");
}
}
此模型使您的代码优雅、简洁、易于阅读和维护。还利用 LINQ 扩展方法执行分组、连接、投影、聚合等。
清单 4.3 使用 LINQ
var list = (from o in new ChoCSVReader<EmployeeRec>("Emp.csv").WithFirstLineHeader()
where o.Name != null && o.Name.StartsWith("R")
select o).ToArray();
foreach (var rec in list)
{
Console.WriteLine($"Id: {rec.Id}");
Console.WriteLine($"Name: {rec.Name}");
}
5. 手动读取记录
就像设置匹配 CSV 文件结构的 POCO 对象一样简单,您可以以可枚举模式读取整个文件
清单 5.1 读取 CSV 文件
var reader = new ChoCSVReader<EmployeeRec>("Emp.csv").WithFirstLineHeader();
EmployeeRec rec = null;
while ((rec = reader.Read()) != null)
{
Console.WriteLine($"Id: {rec.Id}");
Console.WriteLine($"Name: {rec.Name}");
}
6. 自定义 CSV 记录
使用 ChoCSVRecordObjectAttribute
,您可以声明性地自定义 POCO 实体对象。
清单 6.1 为每个记录自定义 POCO 对象
[ChoCSVRecordObject(Encoding = "Encoding.UTF32",
ErrorMode = ChoErrorMode.IgnoreAndContinue, IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All)]
public class EmployeeRec
{
[ChoCSVRecordField(1, FieldName = "id")]
public int Id { get; set; }
[ChoCSVRecordField(2, FieldName ="Name", QuoteField = true)]
[Required]
[DefaultValue("ZZZ")]
public string Name { get; set; }
}
以下是用于执行 CSV 加载操作定制的可用属性。
Delimiter
- CSV 行中用于分隔字段的值。默认为Culture.TextInfo.ListSeparator
用法。EOLDelimiter
- 用于分隔 CSV 行的值。默认为 \r\n (NewLine)。CultureName
- 用于读写 CSV 数据的文化名称(例如,en-US、en-GB)。IgnoreEmptyLine
- 一个标志,告知读取器在读取时是否应跳过空记录。如果所有字段都为空,则认为记录为空。Comments
- 用于表示被注释掉的行的值。可以指定多个注释。必须用逗号分隔。QuoteChar
- 用于转义包含分隔符、引号或行结尾的字段的值。QuoteAllFields
- 如果所有/任何字段都要用引号括起来,则为 True,否则为 False。Encoding
- CSV 文件的编码。HasExcelSeperator
- 对读取器无效。读取器无缝识别 CSV 文件中指定的分隔符并使用它们进行解析。ColumnCountStrict
- 此标志指示在读取预期字段丢失时是否应抛出异常。ColumnOrderStrict
- 此标志指示在读取预期字段位置错误时是否应抛出异常。仅当ColumnCountStrict
为 true 时才执行此检查。BufferSize
- 在从StreamReader
读取时使用的内部缓冲区的大小。- NullValue - 特殊的 null 值文本,期望在记录级别被视为 CSV 文件中的 null 值。
ErrorMode
- 此标志指示在读取时是否应抛出异常,并且预期的字段加载失败。这可以每隔属性覆盖。可能的值是IgnoreAndContinue
- 忽略错误,跳过记录并继续处理下一条。ReportAndContinue
- 如果 POCO 实体是IChoNotifyRecordRead
类型,则向其报告错误ThrowAndStop
- 抛出错误并停止执行
IgnoreFieldValueMode
- 一个标志,告知读取器在读取时是否应跳过空/null
的记录。这可以每隔属性覆盖。可能的值是Null
- N/ADBNull
- N/AEmpty
- 如果记录值为空,则跳过WhiteSpace
- 如果记录值仅包含空格,则跳过
ObjectValidationMode
- 一个标志,告知读取器关于记录对象要执行的验证类型。可能的值是Off
- 不执行对象验证。MemberLevel
- 在加载每个 CSV 属性的值时执行验证。ObjectLevel
- 在所有属性加载到 POCO 对象后执行验证。
7. 自定义 CSV 标题
如果 CSV 文件带有标题,您可以使用 ChoCSVFileHeaderAttribute
来指示 POCO 实体。
清单 6.1 为文件标题自定义 POCO 对象
[ChoCSVFileHeader]
public class EmployeeRec
{
[ChoCSVRecordField(1, FieldName = "id")]
public int Id { get; set; }
[ChoCSVRecordField(2, FieldName ="Name", QuoteField = true)]
[Required]
[DefaultValue("ZZZ")]
public string Name { get; set; }
}
以下是您可以根据需要添加一些自定义的可用成员。
FillChar
- 对读取器无效Justification
- 对读取器无效- IgnoreCase - true,CSV 列匹配不区分大小写。否则为 false。
TrimOption
- 此标志指示读取器在读取 CSV 列标题时修剪开头和结尾的空白。可能的值是Trim
、TrimStart
、TrimEnd
。Truncate
- 对读取器无效IgnoreColumnsWithEmptyHeader
- true,忽略具有空标题的 CSV 列。否则为 false。QuoteAll
- 对读取器无效HeaderLineAt
- 如果 CSV 文件带有标题但不在第一行,则可以使用此属性指定标题行。IgnoreHeader
- true,忽略文件标题并按字段位置加载数据。
8. 自定义 CSV 字段
对于每个 CSV 列,您可以使用 ChoCSVRecordFieldAttribute
在 POCO 实体属性中指定映射。
清单 6.1 为 CSV 列自定义 POCO 对象
[ChoCSVFileHeader]
public class EmployeeRec
{
[ChoCSVRecordField(1, FieldName = "id")]
public int Id { get; set; }
[ChoCSVRecordField(2, FieldName ="Name", QuoteField = true)]
[Required]
[DefaultValue("ZZZ")]
public string Name { get; set; }
}
以下是您可以为每个属性添加自定义的可用成员
FieldPosition
- 按位置映射时,指定要用于该属性的 CSV 列的索引。它是从 1 开始的。FieldName
- 按名称映射时,指定要用于该属性的 CSV 列的名称。要使其生效,CSV 文件必须有标题记录。您指定的名称必须与标题记录的名称匹配。FillChar
- 对读取器无效。FieldValueJustification
- 对读取器无效。FieldValueTrimOption
- 此标志指示读取器在读取时修剪字段值开头和结尾的空白。可能的值是Trim
、TrimStart
、TrimEnd
。Truncate
- 如果字段值超过最大字段大小,则截断。Size
- 字段的最大字段值大小。QuoteField
- 一个标志,告知读取器 CSV 列值用引号括起来。- NullValue - 特殊的 null 值文本,期望在字段级别被视为 CSV 文件中的 null 值。
FormatText
- 指定自定义格式说明符来解析 CSV 字段值。ErrorMode
- 此标志指示在读取时是否应抛出异常,并且预期的字段加载失败。可能的值是IgnoreAndContinue
- 忽略错误,继续加载记录的其他属性。ReportAndContinue
- 如果 POCO 实体是IChoRecord
类型,则向其报告错误。ThrowAndStop
- 抛出错误并停止执行。
IgnoreFieldValueMode
- 一个标志,告知读取器在读取时是否应跳过空/null
的记录。可能的值是Null
- N/ADBNull
- N/AEmpty
- 如果记录值为空,则跳过。WhiteSpace
- 如果记录值仅包含空格,则跳过。
8.1. DefaultValue
当 CSV 值为空或为空格(通过 IgnoreFieldValueMode
控制)时,用于设置属性的值。
可以使用 System.ComponentModel.DefaultValueAttribute
为任何 POCO 实体属性指定默认值。
8.2. ChoFallbackValue
当 CSV 值未能设置为属性时,用于设置属性的值。Fallback
值仅在 ErrorMode
为 IgnoreAndContinue
或 ReportAndContinue
时设置。
可以使用 ChoETL.ChoFallbackValueAttribute
为任何 POCO 实体属性指定回退值。
8.3. 类型转换器
大多数原始类型都会自动转换并设置为属性。如果 CSV 字段的值无法自动转换为属性的类型,您可以指定自定义/内置的 .NET 转换器来转换该值。这些可以是 IValueConverter
、IChoTypeConverter
或 TypeConverter
转换器。
有几种方法可以为每个字段指定转换器
- 声明式方法
- 配置方法
8.3.1. 声明式方法
此模型仅适用于 POCO 实体对象。如果您有 POCO 类,可以为每个属性指定转换器来执行必要的转换。下面的示例显示了如何执行此操作。
[ChoCSVFileHeader]
public class EmployeeRec
{
[ChoCSVRecordField(1, FieldName = "id")]
[ChoTypeConverter(typeof(IntConverter))]
public int Id { get; set; }
[ChoCSVRecordField(2, FieldName ="Name", QuoteField = true)]
[Required]
[DefaultValue("ZZZ")]
public string Name { get; set; }
}
清单 8.3.1.2 IntConverter 实现
public class IntConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
在上面的示例中,我们定义了自定义 IntConverter
类。并展示了如何将其用于“Id
" CSV 属性。
8.3.2. 配置式方法
此模型适用于动态和 POCO 实体对象。这使得可以在运行时将转换器附加到每个属性。这优先于 POCO 类上的声明式转换器。
清单 8.3.2.1 指定 TypeConverters
ChoCSVRecordConfiguration config = new ChoCSVRecordConfiguration();
config.FileHeaderConfiguration.HasHeaderRecord = true;
config.ThrowAndStopOnMissingField = false;
ChoCSVRecordFieldConfiguration idConfig = new ChoCSVRecordFieldConfiguration("Id", 1);
idConfig.AddConverter(new IntConverter());
config.CSVRecordFieldConfigurations.Add(idConfig);
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name", 2));
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name1", 2));
在上面,我们使用 AddConverter
辅助方法在 ChoCSVRecordFieldConfiguration
对象中构造并附加 IntConverter
到 'Id' 字段。
同样,如果要删除任何转换器,可以使用 ChoCSVRecordFieldConfiguration
对象上的 RemoveConverter。
8.3.3. 自定义值转换器方法
这种方法允许使用 Fluenrt API 将值转换器附加到每个 CSV 成员。这是处理任何奇怪的转换过程并避免创建值转换器类的快速方法。
清单 8.3.3.1 POCO 类
[ChoCSVFileHeader]
public class EmployeeRec
{
[ChoCSVRecordField(1, FieldName = "id")]
public int Id { get; set; }
[ChoCSVRecordField(2, FieldName ="Name", QuoteField = true)]
[Required]
[DefaultValue("ZZZ")]
public string Name { get; set; }
}
使用 fluent API,下面的示例显示了如何将值转换器附加到 Id 列
清单 8.3.3.2 附加值转换器
using (var dr = new ChoCSVReader<EmployeeRec>(@"Test.csv")
.WithFirstLineHeader()
.WithField(c => c.Id, valueConverter: (v) => Convert.ToInt32(v as string))
)
{
foreach (var rec in dr)
{
Console.WriteLine(rec.Id);
}
}
8.4. 验证
CSVReader
利用 System.ComponentModel.DataAnnotations
和 Validation Block 验证属性来为 POCO 实体的单个字段指定验证规则。有关可用 DataAnnotations 验证属性的列表,请参阅 MSDN 站点。
清单 8.4.1 在 POCO 实体中使用验证属性
[ChoCSVFileHeader]
[ChoCSVRecordObject(Encoding = "Encoding.UTF32", ErrorMode = ChoErrorMode.IgnoreAndContinue,
IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, ThrowAndStopOnMissingField = false)]
public partial class EmployeeRec
{
[ChoCSVRecordField(1, FieldName = "id")]
[ChoTypeConverter(typeof(IntConverter))]
[Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
[ChoFallbackValue(1)]
public int Id { get; set; }
[ChoCSVRecordField(2, FieldName = "Name")]
[Required]
[DefaultValue("ZZZ")]
[ChoFallbackValue("XXX")]
public string Name { get; set; }
}
在上面的示例中,我们为 Id
属性使用了 Range
验证属性。Required
验证属性用于 Name
属性。当 Configuration.ObjectValidationMode
设置为 ChoObjectValidationMode.MemberLevel
或 ChoObjectValidationMode.ObjectLevel
时,CSVReader 在加载期间对其进行验证。
有时您可能希望覆盖 POCO 类附带的声明式验证行为,您可以通过配置方式在 Cinchoo ETL 中完成。下面的示例展示了如何覆盖它们。
static void ValidationOverridePOCOTest()
{
ChoCSVRecordConfiguration config = new ChoCSVRecordConfiguration();
var idConfig = new ChoCSVRecordFieldConfiguration("Id", 1);
idConfig.Validators = new ValidationAttribute[] { new RequiredAttribute() };
config.CSVRecordFieldConfigurations.Add(idConfig);
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name", 2));
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Salary", 3) { FieldType = typeof(ChoCurrency) });
using (var stream = new MemoryStream())
using (var reader = new StreamReader(stream))
using (var writer = new StreamWriter(stream))
using (var parser = new ChoCSVReader<EmployeeRecWithCurrency>(reader, config))
{
writer.WriteLine("1,Carl,$100000");
writer.WriteLine("2,Mark,$50000");
writer.WriteLine("3,Tom,1000");
writer.Flush();
stream.Position = 0;
object rec;
while ((rec = parser.Read()) != null)
{
Console.WriteLine(String.Format("Id: {0}", rec.Id));
Console.WriteLine(String.Format("Name: {0}", rec.Name));
Console.WriteLine(String.Format("Salary: {0}", rec.Salary));
}
}
}
public class EmployeeRecWithCurrency
{
public int? Id { get; set; }
public string Name { get; set; }
public ChoCurrency Salary { get; set; }
}
在某些情况下,您可能希望在 POCO 实体类中进行手动 自我验证。这可以通过继承 POCO 对象自 IChoValidatable
接口来实现。
清单 8.4.2 POCO 实体上的手动验证
[ChoCSVFileHeader]
[ChoCSVRecordObject(Encoding = "Encoding.UTF32", ErrorMode = ChoErrorMode.IgnoreAndContinue,
IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, ThrowAndStopOnMissingField = false)]
public partial class EmployeeRec : IChoValidatable
{
[ChoCSVRecordField(1, FieldName = "id")]
[ChoTypeConverter(typeof(IntConverter))]
[ChoFallbackValue(1)]
public int Id { get; set; }
[ChoCSVRecordField(2, FieldName = "Name")]
[Required]
[DefaultValue("ZZZ")]
[ChoFallbackValue("XXX")]
public string Name { get; set; }
public bool TryValidate(object target, ICollection<ValidationResult> validationResults)
{
return true;
}
public bool TryValidateFor(object target, string memberName, ICollection<ValidationResult> validationResults)
{
return true;
}
}
上面的示例展示了如何在 POCO 对象中实现自定义的自我验证。
IChoValidatable
接口公开以下方法
TryValidate
- 验证整个对象,如果所有验证都通过则返回 true。否则返回 false。TryValidateFor
- 验证对象的特定属性,如果所有验证都通过则返回 true。否则返回 false。
8.5. ChoIgnoreMember
如果您想在 OptOut 模式下从 CSV 解析中忽略 POCO 成员,请用 ChoIgnoreMemberAttribute
装饰它们。下面的示例显示 Title 成员被忽略了 CSV 加载过程。
[ChoCSVFileHeader]
public class EmployeeRec
{
public int Id { get; set; }
public string Name { get; set; }
[ChoIgnoreMember]
public string Title { get; set; }
}
8.6. StringLength
在 OptOut 模式下,您可以使用 System.ComponentModel.DataAnnotations.StringLengthAttribute.
来指定 CSV 列的大小。
[ChoCSVFileHeader]
public class EmployeeRec
{
public int Id { get; set; }
[StringLength(25)]
public string Name { get; set; }
[ChoIgnoreMember]
public string Title { get; set; }
}
8.7. Display
在 OptOut 模式下,您可以使用 System.ComponentModel.DataAnnotations.DisplayAttribute
来指定映射到成员的 CSV 列的名称。
[ChoCSVFileHeader]
public class EmployeeRec
{
public int Id { get; set; }
[Display(Name="FullName")]
[StringLength(25)]
public string Name { get; set; }
[ChoIgnoreMember]
public string Title { get; set; }
}
8.8. DisplayName
在 OptOut 模式下,您可以使用 System.ComponentModel.DataAnnotations.DisplayNameAttribute
来指定映射到成员的 CSV 列的名称。
[ChoCSVFileHeader]
public class EmployeeRec
{
public int Id { get; set; }
[DisplayName("FullName")]
[StringLength(25)]
public string Name { get; set; }
[ChoIgnoreMember]
public string Title { get; set; }
}
9. Excel 字段分隔符
如果输入的 CSV 文件包含 Excel 字段分隔符,读取器会自动检测并将其用作字段分隔符。
清单 9.1 带有 Excel 字段分隔符的示例 CSV 文件
sep=,
1,"Eldon Base for stackable storage shelf, platinum"
2,"1.7 Cubic Foot Compact ""Cube"" Office Refrigerators"
3,"Cardinal Slant-D® Ring Binder, Heavy Gauge Vinyl"
4,R380
5,Holmes HEPA Air Purifier
10. 回调机制
CSVReader
提供行业标准的 CSV 解析功能,开箱即用,可满足大多数解析需求。 如果解析不满足任何需求,您可以使用 CSVReader
提供的回调机制来处理这种情况。为了参与回调机制,您可以使用以下任一模型
- 使用
CSVReader
通过IChoReader
接口公开的事件处理程序。 - 继承 POCO 实体对象自
IChoNotifyRecordRead / IChoNotifyFileRead / IChoNotifyRecordFieldRead
接口 - 继承 DataAnnotation 的 MetadataType 类型对象自
IChoNotifyRecordRead
/ IChoNotifyFileRead / IChoNotifyRecordFieldRead
接口。 - 继承
IChoNotifyRecordFieldConfigurable / IChoNotifyRecordFieldConfigurable
配置接口
注意:从这些接口方法中引发的任何异常都将被忽略。
IChoReader
公开以下事件
BeginLoad
- 在 CSV 文件加载开始时调用EndLoad
- 在 CSV 文件加载结束时调用BeforeRecordLoad
- 在 CSV 记录加载之前引发AfterRecordLoad
- 在 CSV 记录加载之后引发RecordLoadError
- 在 CSV 记录加载出错时引发BeforeRecordFieldLoad
- 在 CSV 列值加载之前引发AfterRecordFieldLoad
- 在 CSV 列值加载之后引发RecordFieldLoadError
- 在 CSV 列值加载出错时引发- SkipUntil - 在 CSV 解析启动之前引发,以添加自定义逻辑来跳过记录行。
- DoWhile - 在 CSV 解析过程中引发,您可以在此添加自定义逻辑来停止解析。
IChoNotifyRecordRead
公开以下方法
BeforeRecordLoad
- 在 CSV 记录加载之前引发AfterRecordLoad
- 在 CSV 记录加载之后引发RecordLoadError
- 在 CSV 记录加载出错时引发
IChoNotifyFileRead
公开以下方法
BeginLoad
- 在 CSV 文件加载开始时调用EndLoad
- 在 CSV 文件加载结束时调用- SkipUntil - 在 CSV 解析启动之前引发,以添加自定义逻辑来跳过记录行。
- DoWhile - 在 CSV 解析过程中引发,您可以在此添加自定义逻辑来停止解析。
IChoNotifyRecordFieldRead
公开以下方法
BeforeRecordFieldLoad
- 在 CSV 列值加载之前引发AfterRecordFieldLoad
- 在 CSV 列值加载之后引发RecordFieldLoadError
- 在 CSV 列值加载出错时引发
IChoNotifyRecordConfigurable
公开以下方法
RecondConfigure
- 为 CSV 记录配置引发
IChoNotifyRecordFieldConfigurable
公开以下方法
RecondFieldConfigure
- 为每个 CSV 记录字段配置引发
10.1. 使用 CSVReader 事件
这是订阅回调事件并处理 CSV 文件解析中奇怪情况的最直接、最简单的方法。缺点是代码无法像通过实现 IChoNotifyRecordRead
与 POCO 记录对象那样重用。
下面的示例展示了如何使用 BeforeRecordLoad
回调方法来跳过以“%”字符开头的行。
清单 10.1.1 使用 CSVReader 回调事件
static void IgnoreLineTest()
{
using (var parser = new ChoCSVReader("IgnoreLineFile.csv").WithFirstLineHeader())
{
parser.Configuration.Encoding = Encoding.BigEndianUnicode;
parser.BeforeRecordLoad += (o, e) =>
{
if (e.Source != null)
{
e.Skip = ((string)e.Source).StartsWith("%");
}
};
foreach (var e in parser)
Console.WriteLine(e.Dump());
}
}
同样,您也可以在 CSVReader 中使用其他回调方法。
10.2. 实现 IChoNotifyRecordRead 接口
下面的示例展示了如何实现 IChoNotifyRecordRead
接口以直接用于 POCO 类。
清单 10.2.1 直接 POCO 回调机制实现
[ChoCSVFileHeader]
[ChoCSVRecordObject(Encoding = "Encoding.UTF32", ErrorMode = ChoErrorMode.IgnoreAndContinue,
IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, ThrowAndStopOnMissingField = false)]
public partial class EmployeeRec : IChoNotifyRecordRead
{
[ChoCSVRecordField(1, FieldName = "id")]
[ChoTypeConverter(typeof(IntConverter))]
[Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
[ChoFallbackValue(1)]
public int Id { get; set; }
[ChoCSVRecordField(2, FieldName = "Name", QuoteField = true)]
[Required]
[DefaultValue("ZZZ")]
[ChoFallbackValue("XXX")]
public string Name { get; set; }
public bool 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
类附加到 POCO 类。
清单 10.2.2 基于 MetaDataType 的回调机制实现
[ChoCSVFileHeader]
[ChoCSVRecordObject(Encoding = "Encoding.UTF32", ErrorMode = ChoErrorMode.IgnoreAndContinue,
IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, ThrowAndStopOnMissingField = false)]
public class EmployeeRecMeta : IChoNotifyRecordRead
{
[ChoCSVRecordField(1, FieldName = "id")]
[ChoTypeConverter(typeof(IntConverter))]
[Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
[ChoFallbackValue(1)]
public int Id { get; set; }
[ChoCSVRecordField(2, FieldName = "Name", QuoteField = true)]
[Required]
[DefaultValue("ZZZ")]
[ChoFallbackValue("XXX")]
public string Name { get; set; }
public bool 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; }
}
下面的示例展示了如何通过使用 ChoMetadataRefTypeAttribute
将 Metadata
类附加到已密封的或第三方 POCO 类。
清单 10.2.3 基于 ChoMetaDataRefType 的回调机制实现
[ChoMetadataRefType(typeof(EmployeeRec))]
[ChoCSVFileHeader]
[ChoCSVRecordObject(Encoding = "Encoding.UTF32", ErrorMode = ChoErrorMode.IgnoreAndContinue,
IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, ThrowAndStopOnMissingField = false)]
public class EmployeeRecMeta : IChoNotifyRecordRead
{
[ChoCSVRecordField(1, FieldName = "id")]
[ChoTypeConverter(typeof(IntConverter))]
[Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
[ChoFallbackValue(1)]
public int Id { get; set; }
[ChoCSVRecordField(2, FieldName = "Name", QuoteField = true)]
[Required]
[DefaultValue("ZZZ")]
[ChoFallbackValue("XXX")]
public string Name { get; set; }
public bool 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; }
}
10.3. BeginLoad
此回调在 CSV 文件加载 开始 时调用一次。source
是 CSV 文件流对象。在这里,您有机会检查流,返回 true
以继续 CSV 加载。返回 false
以停止解析。
清单 10.3.1 BeginLoad 回调示例
public bool BeginLoad(object source)
{
StreamReader sr = source as StreamReader;
return true;
}
10.4. EndLoad
此回调在 CSV 文件加载 结束 时调用一次。source
是 CSV 文件流对象。在这里,您有机会检查流,执行将在流上执行的任何后续步骤。
清单 10.4.1 EndLoad 回调示例
public void EndLoad(object source)
{
StreamReader sr = source as StreamReader;
}
10.5. BeforeRecordLoad
此回调在 CSV 文件中的 每个 记录行加载 之前 调用。target
是 POCO 记录对象的实例。index
是文件中的行索引。source
是 CSV 记录行。在这里,您有机会检查该行,并在需要时用新行覆盖它。
提示:如果您想跳过该行不加载,请将源设置为 null。
提示:如果您想自行控制解析和加载记录属性,请将源设置为 String.Empty。
返回 true
继续加载过程,否则返回 false
停止过程。
清单 10.5.1 BeforeRecordLoad 回调示例
public bool BeforeRecordLoad(object target, int index, ref object source)
{
string line = source as string;
return true;
}
10.6. AfterRecordLoad
此回调在 CSV 文件中的 每个 记录行加载 之后 调用。target
是 POCO 记录对象的实例。index
是文件中的行索引。source
是 CSV 记录行。在这里,您有机会对记录行执行任何后续操作。
返回 true
继续加载过程,否则返回 false
停止过程。
清单 10.6.1 AfterRecordLoad 回调示例
public bool AfterRecordLoad(object target, int index, object source)
{
string line = source as string;
return true;
}
10.7. RecordLoadError
当加载记录行时遇到错误,将调用此回调。target
是 POCO 记录对象的实例。index
是文件中的行索引。source
是 CSV 记录行。ex 是异常对象。在这里,您有机会处理异常。仅当 Configuration.ErrorMode
设置为 ReportAndContinue
时才会调用此方法。
返回 true
继续加载过程,否则返回 false
停止过程。
清单 10.7.1 RecordLoadError 回调示例
public bool RecordLoadError(object target, int index, object source, Exception ex)
{
string line = source as string;
return true;
}
10.8. BeforeRecordFieldLoad
在加载每个 CSV 记录列 之前 调用此回调。target
是 POCO 记录对象的实例。index
是文件中的行索引。propName
是 CSV 记录属性名。value
是 CSV 列值。在这里,您有机会检查 CSV 记录属性值并执行任何自定义验证等。
返回 true
继续加载过程,否则返回 false
停止过程。
清单 10.8.1 BeforeRecordFieldLoad 回调示例
public bool BeforeRecordFieldLoad(object target, int index, string propName, ref object value)
{
return true;
}
10.9. AfterRecordFieldLoad
在加载每个 CSV 记录列 之后 调用此回调。target
是 POCO 记录对象的实例。index
是文件中的行索引。propName
是 CSV 记录属性名。value
是 CSV 列值。在这里可以执行任何后续字段操作,例如计算其他属性、验证等。
返回 true
继续加载过程,否则返回 false 停止过程。
清单 10.9.1 AfterRecordFieldLoad 回调示例
public bool AfterRecordFieldLoad(object target, int index, string propName, object value)
{
return true;
}
10.10. RecordLoadFieldError
当加载 CSV 记录列值时遇到错误,将调用此回调。target
是 POCO 记录对象的实例。index
是文件中的行索引。propName
是 CSV 记录属性名。value
是 CSV 列值。ex
是异常对象。在这里,您有机会处理异常。在执行了 CSVReader 执行的以下两个序列步骤之后,将调用此方法
- CSVReader 查找每个 CSV 属性的
FallbackValue
值。如果存在,则尝试将其值分配给它。 - 如果
FallbackValue
值不存在且Configuration.ErrorMode
指定为ReportAndContinue
。将调用此回调。
返回 true
继续加载过程,否则返回 false
停止过程。
清单 10.10.1 RecordFieldLoadError 回调示例
public bool RecordFieldLoadError(object target, int index, string propName, object value, Exception ex)
{
return true;
}
10.11. SkipUntil
在 CSV 解析开始时调用此回调,并具有自定义逻辑来跳过行。index
是文件中的行索引。
返回 true
以跳过行,否则返回 false
。
清单 10.11.1 SkipUntil 回调示例
public bool SkipUntil(long index, object source)
{
return false;
}
10.12. DoWhile
在 CSV 解析开始时调用此回调,并具有自定义逻辑来跳过行。index
是文件中的行索引。
返回 true
以停止解析,否则返回 false
。
清单 10.12.1 DoWhile 回调示例
public bool DoWhile(long index, object source)
{
return false;
}
10. 定制
CSVReader
自动检测并从 POCO 实体加载配置的设置。在运行时,您可以在 CSV 解析之前自定义和调整这些参数。CSVReader
公开 Configuration
属性,它是 ChoCSVRecordConfiguration
对象。使用此属性,您可以自定义它们。
清单 10.1 运行时自定义 CSVReader
class Program
{
static void Main(string[] args)
{
string csv = @"Id, Name
1, Carl
2, Mark
3,";
dynamic row = null;
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
)
{
parser.Configuration.ColumnCountStrict = true;
while ((row = parser.Read()) != null)
{
Console.WriteLine($"Id: {row.Id}");
Console.WriteLine($"Name: {row.Name}");
}
}
}
}
11. AsDataReader 辅助方法
CSVReader
公开 AsDataReader
辅助方法,以 .NET datareader
对象的形式检索 CSV 记录。DataReader
是快速向前的数据流。此 datareader
可用于几个地方,例如使用 SqlBulkCopy
将数据批量复制到数据库、加载断开连接的 DataTable
等。
清单 11.1 读取为 DataReader 示例
static void AsDataReaderTest()
{
string csv = @"Id, Name
1, Carl
2, Mark
3,";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
)
{
IDataReader dr = parser.AsDataReader();
while (dr.Read())
{
Console.WriteLine("Id: {0}, Name: {1}", dr[0], dr[1]);
}
}
}
12. AsDataTable 辅助方法
CSVReader
公开 AsDataTable
辅助方法,以 .NET DataTable
对象的形式检索 CSV 记录。然后可以将其持久化到磁盘、显示在网格/控件中或像任何其他对象一样存储在内存中。
清单 12.1 读取为 DataTable 示例
static void AsDataTableTest()
{
string csv = @"Id, Name
1, Carl
2, Mark
3,";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
)
{
IDataReader dr = parser.AsDataTable();
while (dr.Read())
{
Console.WriteLine("Id: {0}, Name: {1}", dr[0], dr[1]);
}
}
}
13. 使用动态对象
到目前为止,本文已解释了如何将 CSVReader
与 POCO 对象一起使用。CSVReader
还支持在没有 POCO 对象的情况下加载 CSV 文件。它利用 .NET 动态功能。下面的示例显示了如何在没有 POCO 对象的情况下读取 CSV 流。
如果您有 CSV 文件,则可以使用最少/零配置来解析和加载该文件。如果 CSV 文件没有标题记录行,解析器会自动将列命名为 Column1
、Column2
等。
下面的示例展示了这一点
清单 13.1 加载不带标题的 CSV 文件示例
class Program
{
static void Main(string[] args)
{
string csv = @"1, Carl
2, Mark
3,";
dynamic row = null;
using (var parser = ChoCSVReader.LoadText(csv))
{
while ((row = parser.Read()) != null)
{
Console.WriteLine($"Id: {row.Column1}");
Console.WriteLine($"Name: {row.Column2}");
}
}
}
}
如果 CSV 文件带有标题,您可以在配置中将其声明为 HasHeaderRecord
为 true
/ 使用 WithFirstLineHeader
fluent API 并像下面一样简单地解析文件
清单 13.2 加载带标题的 CSV 文件示例
class Program
{
static void Main(string[] args)
{
string csv = @"Id, Name
1, Carl
2, Mark
3,";
dynamic row = null;
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
)
{
while ((row = parser.Read()) != null)
{
Console.WriteLine($"Id: {row.Id}");
Console.WriteLine($"Name: {row.Name}");
}
}
}
}
上面的示例自动从标题中发现 CSV 列并解析文件。
您可以通过手动添加字段配置并将它们传递给 CSVReader
进行文件解析来覆盖自动发现列的默认行为。
示例展示了如何执行此操作
清单 13.3 加载带配置的 CSV 文件
class Program
{
static void Main(string[] args)
{
ChoCSVRecordConfiguration config = new ChoCSVRecordConfiguration();
config.CSVFileHeaderConfiguration.HasHeaderRecord = true;
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Id", 1));
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name", 2));
dynamic row = null;
using (var parser = ChoCSVReader.LoadText(csv, config)
)
{
while ((row = parser.Read()) != null)
{
Console.WriteLine($"Id: {row.Id}");
Console.WriteLine($"Name: {row.Name}");
}
}
}
}
要完全关闭自动列发现,您必须将 ChoCSVRecordConfiguration.AutoDiscoverColumns
设置为 false
。
13.1. DefaultValue
当 CSV 值为空或为空格(通过 IgnoreFieldValueMode
控制)时,用于设置属性的值。
可以使用 System.ComponentModel.DefaultValueAttribute
为任何 POCO 实体属性指定默认值。
对于动态对象成员或覆盖声明式 POCO 对象成员的默认值,可以通过配置进行,如下所示。
ChoCSVRecordConfiguration config = new ChoCSVRecordConfiguration();
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Id", 1));
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name", 2) { DefaultValue = "NoName" })
13.2. ChoFallbackValue
当 CSV 值未能设置为属性时,用于设置属性的值。Fallback
值仅在 ErrorMode
为 IgnoreAndContinue
或 ReportAndContinue
时设置。
可以使用 ChoETL.ChoFallbackValueAttribute
为任何 POCO 实体属性指定回退值。
对于动态对象成员或覆盖声明式 POCO 对象成员的回退值,可以通过配置进行,如下所示。
ChoCSVRecordConfiguration config = new ChoCSVRecordConfiguration();
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Id", 1));
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name", 2) { FallbackValue = "Tom" });
13.3. FieldType
在无类型动态对象模型中,读取器读取各个字段值并将它们填充到动态对象成员中,值为“string”类型。如果您想强制执行类型并在加载期间进行额外的类型检查,可以通过在字段配置中声明字段类型来实现。
清单 8.5.1 定义 FieldType
ChoCSVRecordConfiguration config = new ChoCSVRecordConfiguration();
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Id", 1) { FieldType = typeof(int) });
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name", 2));
上面示例展示了如何将 'Id' 字段的字段类型定义为“int”。这会指示 CSVReader 在分配给它之前解析并将值转换为整数。这种额外的类型安全可以避免在解析过程中将不正确的值加载到对象中。
13.4. 类型转换器
CSVReader 会自动转换大多数原始类型并将它们设置为属性。如果 CSV 字段的值无法自动转换为属性的类型,您可以指定自定义/内置的 .NET 转换器来转换该值。这些可以是 IValueConverter
或 TypeConverter
转换器。
在动态对象模型中,您可以通过配置来指定这些转换器。请参阅下面的示例,了解为 CSV 列指定类型转换器的方法
清单 13.4.1 指定 TypeConverters
ChoCSVRecordConfiguration config = new ChoCSVRecordConfiguration();
config.FileHeaderConfiguration.HasHeaderRecord = true;
config.ThrowAndStopOnMissingField = false;
ChoCSVRecordFieldConfiguration idConfig = new ChoCSVRecordFieldConfiguration("Id", 1);
idConfig.AddConverter(new IntConverter());
config.CSVRecordFieldConfigurations.Add(idConfig);
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name", 2));
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name1", 2));
在上面,我们使用 AddConverter
辅助方法在 ChoCSVRecordFieldConfiguration
对象中构造并附加 IntConverter
到 'Id' 字段。
同样,如果要删除任何转换器,可以使用 RemoveConverter 在 ChoCSVRecordFieldConfiguration
对象上。
13.5. 验证
CSVReader
利用 System.ComponentModel.DataAnnotations
和 Validation Block 验证属性来为各个 CSV 字段指定验证规则。有关可用 DataAnnotations 验证属性的列表,请参阅 MSDN 站点。
清单 13.5.1 指定验证
ChoCSVRecordConfiguration config = new ChoCSVRecordConfiguration();
config.FileHeaderConfiguration.HasHeaderRecord = true;
config.ThrowAndStopOnMissingField = false;
ChoCSVRecordFieldConfiguration idConfig = new ChoCSVRecordFieldConfiguration("Id", 1);
idConfig.Validators = new ValidationAttribute[] { new RangeAttribute(0, 100) };
config.CSVRecordFieldConfigurations.Add(idConfig);
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name", 2));
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name1", 2));
在上面的示例中,我们为 Id
属性使用了 Range
验证属性。当 Configuration.ObjectValidationMode
设置为 ChoObjectValidationMode.MemberLevel
或 ChoObjectValidationMode.ObjectLevel
时,CSVReader 会在加载期间执行验证。
注意:动态对象模型不支持自验证
14. 处理已密封的 POCO 对象
如果您已经有一个现有的已密封 POCO 对象,或者该对象在第三方库中,我们可以将其与 CSVReader 一起使用。您只需要一个带有标题的 CSV 文件。
清单 14.1 现有的已密封 POCO 对象
public sealed class ThirdPartyRec
{
public int Id
{
get;
set;
}
public string Name
{
get;
set;
}
}
清单 14.2 使用 CSV 文件
class Program
{
static void Main(string[] args)
{
string csv = @"Id, Name
1, Carl
2, Mark
3,";
dynamic row = null;
using (var parser = ChoCSVReader<ThirdPartyRec>.LoadText(csv)
.WithFirstLineHeader()
)
{
while ((row = parser.Read()) != null)
{
Console.WriteLine($"Id: {row.Id}");
Console.WriteLine($"Name: {row.Name}");
}
}
}
}
在这种情况下,CSVReader 会反向发现 CSV 文件中的 CSV 列并将数据加载到 POCO 对象中。如果 CSV 文件结构和 POCO 对象匹配,加载将成功,并将所有相应的数据填充到其属性中。如果缺少任何 CSV 列的属性,CSVReader 会悄悄地忽略它们并继续处理其余的。
您可以通过将 ChoCSVRecordConfiguration.ThrowAndStopOnMissingField
属性设置为 false
来覆盖此行为。在这种情况下,如果缺少 CSV 列的属性,CSVReader 将抛出 ChoMissingRecordFieldException 异常。
有没有办法通过 CSV 规范来扩展已密封的第三方对象?是的,Cinchoo 提供了一个模型来通过创建带有 ChoMetadataRefTypeAttribute
的元数据类来扩展它们。Cinchoo 会自动发现它并从这个特殊类加载规范。
[ChoMetadataRefType(typeof(ThirdPartyRec))]
public sealed class ThirdPartyRecMetaData
{
[ChoCSVRecordField(1, Name = "SeqNo")]
public int Id
{
get;
set;
}
[ChoCSVRecordField(2)]
public string Name
{
get;
set;
}
}
还有另一种方法可以将 MetaData
对象注入 Cinchoo 框架。
ChoMetadataObjectCache.Default.Attach(typeof(ThirdPartyRec), new ThirdPartyRecMetaData());
15. 异常
CSVReader 在不同情况下会抛出不同类型的异常。
ChoParserException
- CSV 文件损坏,解析器无法恢复。ChoRecordConfigurationException
- 指定了任何无效的配置设置,将引发此异常。ChoMissingRecordFieldException
- CSV 列缺少属性,将引发此异常。
16. 技巧
16.1. 多行 CSV 列值
如果 CSV 文件中的列值包含换行符,ChoCSVReader 可以通过指定 Configuration.MayContainEOLInData = true
来处理。
注意:此选项可能会减慢解析速度。仅在需要时才考虑使用此选项。
清单 16.1.1 CSV 文件中的多行列值
Id,Name 1,"Tom Cassawaw" 2,"Carl" 3,"Mark"
在上面,Id (1) 的名称是多行的,并用引号括起来。下面的示例展示了如何处理它。
清单 16.1.2 读取 CSV 文件中的多行列值
static void IgnoreUnwantedLines()
{
using (var parser = new ChoCSVReader("Emp.csv").WithFirstLineHeader())
{
parser.Configuration.MayContainEOLInData = true;
foreach (var e in parser)
Console.WriteLine(e.Dump());
}
}
16.2. 包含字段分隔符的 CSV 列值
如果 CSV 文件中的列值包含 字段分隔符 (,),ChoCSVReader 可以通过用引号括起来来处理。
清单 16.2.1 带有分隔符的 CSV 列值
Id,Name 1,"Tom Cassawaw" 2,"Carl, Malcolm" 3,"Mark"
在上面,Id (2) 的名称包含分隔符 (,)。 为了让 ChoCSVReader 识别这种情况,它必须用引号括起来。
16.3. 包含单引号的 CSV 列值
ChoCSVReader 可以无缝地读取包含单引号的 CSV 列值。不需要用引号括起来。
清单 16.3.1 包含单引号的 CSV 列值
Id,Name 1,Tom Cassawaw 2,Carl'Malcolm 3,Mark
在上面,Id (2) 的名称包含单引号 (')。 ChoCSVReader 识别这种情况,并成功加载这些值。
16.4. 使用 LiteParsing 选项进行快速 CSV 解析
Cinchoo ETL 框架提供了许多用于简化 CSV 文件解析的功能。这会带来性能缓慢的代价。有时,在解析大型 CSV 文件时,您可能希望选择快速解析而不是框架功能。在这种情况下,您可以使用 LiteParsing
选项来提高解析性能
清单 16.4.1 使用 LiteParsing 选项解析大型 CSV 文件
static void ParseLargeCSVFile()
{
using (var parser = new ChoCSVReader("LargeEmp.csv")
.WithFirstLineHeader()
.Configure(c => c.LiteParsing = true)
)
{
foreach (var e in parser)
Console.WriteLine(e.Dump());
}
}
17. 使用 MetadataType 注解
Cinchoo ETL 在与 DataAnnotation 的 MetadataType 模型配合使用时效果更好。这是将元数据类附加到数据模型类的一种方法。在这个关联类中,您提供了数据模型中不存在的额外元数据信息。它的作用是在不修改类的情况下向类添加属性。您可以将此属性(接受单个参数)添加到将包含所有属性的类中。这在 POCO 类由自动工具(如 Entity Framework、MVC 等)自动生成时很有用。这就是第二个类发挥作用的原因。您可以添加新内容而不触及生成的文件。此外,这通过将关注点分离到多个类来促进模块化。
有关更多信息,请在 MSDN 中搜索。
清单 17.1 MetadataType 注解用法示例
[MetadataType(typeof(EmployeeRecMeta))]
public class EmployeeRec
{
public int Id { get; set; }
public string Name { get; set; }
}
[ChoCSVFileHeader]
[ChoCSVRecordObject(Encoding = "Encoding.UTF32", ErrorMode = ChoErrorMode.ThrowAndStop,
IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, ThrowAndStopOnMissingField = false,
ObjectValidationMode = ChoObjectValidationMode.MemberLevel)]
public class EmployeeRecMeta : IChoNotifyRecordRead, IChoValidatable
{
[ChoCSVRecordField(1, FieldName = "id", ErrorMode = ChoErrorMode.ReportAndContinue )]
[ChoTypeConverter(typeof(IntConverter))]
[Range(1, 1, ErrorMessage = "Id must be > 0.")]
[ChoFallbackValue(1)]
public int Id { get; set; }
[ChoCSVRecordField(2, FieldName = "Name", QuoteField = true)]
[StringLength(1)]
[DefaultValue("ZZZ")]
[ChoFallbackValue("XXX")]
public string Name { get; set; }
public bool 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();
}
public bool TryValidate(object target, ICollection<ValidationResult> validationResults)
{
return true;
}
public bool TryValidateFor(object target, string memberName, ICollection<ValidationResult> validationResults)
{
return true;
}
public void Validate(object target)
{
}
public void ValidateFor(object target, string memberName)
{
}
}
在上面的 EmployeeRec
是数据类。仅包含域特定属性和操作。使其成为一个简单的类。
我们将验证、回调机制、配置等分离到元数据类型类 EmployeeRecMeta
中。
18. 配置选项
如果 POCO 实体类是自动生成的类,或者通过库公开,或者是一个已密封的类,那么它限制您声明性地将 CSV 架构定义附加到它。在这种情况下,您可以选择以下选项之一来指定 CSV 布局配置
- 手动配置
- 自动映射配置
- 附加 MetadataType 类
我将向您展示如何配置下面的 POCO 实体类,针对每种方法
清单 18.1 已密封的 POCO 实体类
public sealed class EmployeeRec
{
public int Id { get; set; }
public string Name { get; set; }
}
18.1. 手动配置
从头开始定义一个新的配置对象,并将所有必需的 CSV 字段添加到 ChoCSVConfiguration.CSVRecordFieldConfigurations
集合属性中。此选项为您提供了更大的灵活性来控制 CSV 解析的配置。但是缺点是,如果 CSV 文件布局很大,可能会出错且难以管理。
清单 18.1.1 手动配置
ChoCSVRecordConfiguration config = new ChoCSVRecordConfiguration();
config.FileHeaderConfiguration.HasHeaderRecord = true;
config.ThrowAndStopOnMissingField = true;
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Id", 1));
config.CSVRecordFieldConfigurations.Add(new ChoCSVRecordFieldConfiguration("Name", 2));
foreach (var e in new ChoCSVReader<EmployeeRec>("Emp.csv", config))
{
Console.WriteLine(String.Format("Id: {0}", e.Id));
Console.WriteLine(String.Format("Name: {0}", e.Name));
}
上述配置方法适用于 POCO 和动态模型方法。如果使用 POCO 模型解析 CSV 文件,则下面的方法更类型安全。
var config = new ChoCSVRecordConfiguration<EmployeeRec>()
.WithFirstLineHeader()
.Configure(c => c.ThrowAndStopOnMissingField = true)
.Map(f => f.Id, 1)
.Map(f => f.Name, 2);
foreach (var e in new ChoCSVReader<EmployeeRec>("Emp.csv", config))
{
Console.WriteLine(String.Format("Id: {0}", e.Id));
Console.WriteLine(String.Format("Name: {0}", e.Name));
}
18.2. 自动映射配置
这是一种替代方法,也是一种出错率很低的方法,用于为 POCO 实体类自动映射 CSV 列。此方法在 POCO 模型对象已密封/通过第三方库公开/维护在不受您控制的单独存储库中时很有帮助,您可以定义映射类并将其注册到解析器以定义字段映射和配置。
首先,定义一个 EmployeeRec
POCO 实体类的模式类,如下所示
清单 18.2.1 自动映射类
public class EmployeeRecMap
{
[ChoCSVRecordField(1, FieldName = "id")]
public int Id { get; set; }
[ChoCSVRecordField(2, FieldName = "Name")]
public string Name { get; set; }
}
然后,您可以使用 ChoCSVRecordConfiguration.MapRecordFields
方法来自动映射 CSV 列
清单 18.2.2 使用 自动映射配置
ChoCSVRecordConfiguration config = new ChoCSVRecordConfiguration();
config.MapRecordFields<EmployeeRecMap>();
foreach (var e in new ChoCSVReader<EmployeeRec>("Emp.csv", config).WithFirstLineHeader())
{
Console.WriteLine(String.Format("Id: {0}", e.Id));
Console.WriteLine(String.Format("Name: {0}", e.Name));
}
18.3. 附加 MetadataType 类
这是另一种将 MetadataType 类附加到 POCO 实体对象的方法。前面的方法仅处理 CSV 列的自动映射。其他配置属性,如属性转换器、解析器参数、默认/回退值等,均不考虑。
此模型通过定义 MetadataType 类并声明性地指定 CSV 配置参数来处理所有内容。当您的 POCO 实体已密封且不是部分类时,此模型很有用。它也是配置 POCO 实体 CSV 解析的有利且不易出错的方法之一。
清单 18.3.1 定义 MetadataType 类
[ChoCSVFileHeader()]
[ChoCSVRecordObject(Encoding = "Encoding.UTF32", ErrorMode = ChoErrorMode.ReportAndContinue,
IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, ThrowAndStopOnMissingField = false,
ObjectValidationMode = ChoObjectValidationMode.MemberLevel)]
public class EmployeeRecMeta : IChoNotifyRecordRead, IChoValidatable
{
[ChoCSVRecordField(1, FieldName = "id", ErrorMode = ChoErrorMode.ReportAndContinue )]
[ChoTypeConverter(typeof(IntConverter))]
[Range(1, 1, ErrorMessage = "Id must be > 0.")]
public int Id { get; set; }
[ChoCSVRecordField(2, FieldName = "Name", QuoteField = true)]
[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 RecordFieldLoadError(object target, int index, string propName, object value, Exception ex)
{
return true;
}
public bool TryValidate(object target, ICollection<ValidationResult> validationResults)
{
return true;
}
public bool TryValidateFor(object target, string memberName, ICollection<ValidationResult> validationResults)
{
return true;
}
public void Validate(object target)
{
}
public void ValidateFor(object target, string memberName)
{
}
}
清单 18.3.2 附加 MetadataType 类
//Attach metadata
ChoMetadataObjectCache.Default.Attach<EmployeeRec>(new EmployeeRecMeta());
foreach (var e in new ChoCSVReader<EmployeeRec>("Emp.csv").WithFirstLineHeader())
{
Console.WriteLine(String.Format("Id: {0}", e.Id));
Console.WriteLine(String.Format("Name: {0}", e.Name));
}
19. LoadText 辅助方法
这是一个非常巧妙的辅助方法,用于将 CSV 文本字符串解析并加载到对象中。
清单 19.1 使用 LoadText 方法
string txt = "Id, Name\r\n1, Mark";
foreach (dynamic e in ChoCSVReader.LoadText(txt).WithFirstLineHeader())
{
Console.WriteLine(String.Format("Id: {0}", e.Id));
Console.WriteLine(String.Format("Name: {0}", e.Name));
}
20. 高级主题
20.1. 覆盖转换器格式规范
Cinchoo ETL 会自动解析并将每个 CSV 列值无缝地转换为相应的 CSV 列的底层数据类型。大多数基本的 .NET 类型都自动处理,无需任何设置。
这是通过 ETL 系统中的两个关键设置实现的
ChoCSVRecordConfiguration.CultureInfo
- 表示有关特定文化的信息,包括文化的名称、书写系统和使用的日历,以及访问提供常见操作(如格式化日期和排序字符串)信息的文化特定对象。默认为 'en-US'。ChoTypeConverterFormatSpec
- 这是一个全局格式说明符类,包含所有固有的 .NET 类型格式规范。
在本节中,我将讨论如何根据解析需求更改每个 .NET 内在数据类型的默认格式规范。
ChoTypeConverterFormatSpec
是一个单例类,其实例通过“Instance”静态成员公开。它是线程本地的,意味着每个线程都会保留单独的实例副本。
为每个内在类型提供了两组格式规范成员,一组用于加载,另一组用于写入值,但布尔、枚举、日期时间类型除外。这些类型只有一对成员用于加载和写入操作。
通过 ChoTypeConverterFormatSpec
指定每个内在数据类型的格式规范将影响整个系统。即,通过设置 ChoTypeConverterFormatSpec.IntNumberStyle = NumberStyles.AllowParentheses
,将影响所有 CSV 对象的整数成员允许使用括号。如果您想覆盖此行为并控制特定 CSV 数据成员以处理其来自全局系统范围设置的独特 CSV 值解析,可以通过在 CSV 字段成员级别指定 TypeConverter 来完成。有关更多信息,请参阅第 13.4 节。
NumberStyles
(可选) 用于从 CSV 流加载值,而格式字符串用于将值写入 CSV 流。
在本文中,我将简要介绍如何使用 NumberStyles
从流中加载 CSV 数据。这些值是可选的。它们确定了解析 CSV 文件期间每个类型允许的样式。系统会自动确定如何根据底层 Culture 解析和加载值。在特殊情况下,您可能希望覆盖并按所需方式设置样式以成功加载文件。有关 NumberStyles 及其值的更多信息,请参阅 MSDN。
清单 20.1.1 ChoTypeConverterFormatSpec 成员
public class ChoTypeConverterFormatSpec
{
public static readonly ThreadLocal<ChoTypeConverterFormatSpec> Instance = new ThreadLocal<ChoTypeConverterFormatSpec>(() => new ChoTypeConverterFormatSpec());
public string DateTimeFormat { get; set; }
public ChoBooleanFormatSpec BooleanFormat { get; set; }
public ChoEnumFormatSpec EnumFormat { get; set; }
public NumberStyles? CurrencyNumberStyle { get; set; }
public string CurrencyFormat { get; set; }
public NumberStyles? BigIntegerNumberStyle { get; set; }
public string BigIntegerFormat { get; set; }
public NumberStyles? ByteNumberStyle { get; set; }
public string ByteFormat { get; set; }
public NumberStyles? SByteNumberStyle { get; set; }
public string SByteFormat { get; set; }
public NumberStyles? DecimalNumberStyle { get; set; }
public string DecimalFormat { get; set; }
public NumberStyles? DoubleNumberStyle { get; set; }
public string DoubleFormat { get; set; }
public NumberStyles? FloatNumberStyle { get; set; }
public string FloatFormat { get; set; }
public string IntFormat { get; set; }
public NumberStyles? IntNumberStyle { get; set; }
public string UIntFormat { get; set; }
public NumberStyles? UIntNumberStyle { get; set; }
public NumberStyles? LongNumberStyle { get; set; }
public string LongFormat { get; set; }
public NumberStyles? ULongNumberStyle { get; set; }
public string ULongFormat { get; set; }
public NumberStyles? ShortNumberStyle { get; set; }
public string ShortFormat { get; set; }
public NumberStyles? UShortNumberStyle { get; set; }
public string UShortFormat { get; set; }
}
下面的示例展示了如何使用 CSVReader 加载包含“se-SE”(瑞典语)文化特定数据的 CSV 数据流。此外,输入源包含带有括号的“EmployeeNo
”值。为了使加载成功,我们需要将 ChoTypeConverterFormatSpec.IntNumberStyle
设置为 NumberStyles.AllowParenthesis
。
清单 20.1.2 代码中使用 ChoTypeConverterFormatSpec
static void UsingFormatSpecs()
{
string csv = @"Id,Name,Salary,JoinedDate,EmployeeNo
1,Carl,12.345679 kr,2017-10-10,5
2,Mark,50000 kr,2001-10-01,6
3,Tom,150000 kr,1996-01-25,9";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.WithField("Id")
.WithField("Name")
.WithField("Salary", fieldType: typeof(float))
.WithField("JoinedDate", fieldType: typeof(DateTime))
.WithField("EmployeeNo", fieldType: typeof(int))
.Configure(c => c.Culture = new System.Globalization.CultureInfo("se-SE"))
)
{
foreach (var rec in parser)
{
Console.WriteLine(rec.Dump());
}
}
}
20.2. 货币支持
Cinchoo ETL 提供了 ChoCurrency 对象来读写 CSV 文件中的货币值。ChoCurrency
是一个包装类,用于以 decimal 类型存储货币值,并支持在 CSV 加载期间将其序列化为文本格式。
清单 20.2.1 在动态模型中使用 Currency 成员
static void CurrencyDynamicTest()
{
string csv = @"Id,Name,Salary,JoinedDate,EmployeeNo
1,Carl,12.345679 kr,2017-10-10,5
2,Mark,50000 kr,2001-10-01,6
3,Tom,150000 kr,1996-01-25,9";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.WithField("Id")
.WithField("Name")
.WithField("Salary", fieldType: typeof(ChoCurrency))
.WithField("JoinedDate", fieldType: typeof(DateTime))
.WithField("EmployeeNo", fieldType: typeof(int))
.Configure(c => c.Culture = new System.Globalization.CultureInfo("se-SE"))
)
{
foreach (var rec in parser)
{
Console.WriteLine(rec.Dump());
}
}
}
上面的示例展示了如何使用动态对象模型加载货币值。默认情况下,动态对象的所有成员都被视为 string 类型,除非通过 ChoCSVFieldConfiguration.FieldType
显式指定。通过将 'Sa;lary' CSV 字段的字段类型指定为 ChoCurrency,CSVReader 会将它们加载为货币对象。
注意:货币值的格式由 CSVReader 通过 ChoRecordConfiguration.Culture 和 ChoTypeConverterFormatSpec.CurrencyNumberStyle 来确定。
下面的示例展示了如何在 POCO 实体类中使用 ChoCurrency CSV 字段。
清单 20.2.2 在 POCO 模型中使用 Currency 成员
public class EmployeeRecWithCurrency
{
public int Id { get; set; }
public string Name { get; set; }
public ChoCurrency Salary { get; set; }
}
static void CurrencyTest()
{
string csv = @"Id,Name,Salary
1,Carl,$12.345679
2,Mark,$50000
3,Tom,$150000";
using (var parser = ChoCSVReader<EmployeeRecWithCurrency>.LoadText(csv)
.WithFirstLineHeader()
)
{
foreach (var rec in parser)
{
Console.WriteLine(rec.Dump());
}
}
}
20.3. 枚举支持
Cinchoo ETL 会隐式地处理从 CSV 文件中解析枚举列值。如果您想精细控制这些值的解析,可以全局地通过 ChoTypeConverterFormatSpec.EnumFormat
指定。 默认为 ChoEnumFormatSpec.Value
FYI,更改此值将影响整个系统。
有 3 个可能的值可以用来
ChoEnumFormatSpec.Value
- 枚举 值用于解析。ChoEnumFormatSpec.Name
- 枚举 键名用于解析。ChoEnumFormatSpec.Description
- 如果每个枚举 键都用DescriptionAttribute
装饰,则使用其值进行解析。
清单 20.3.1 解析时指定 Enum 格式规范
public enum EmployeeType
{
[Description("Full Time Employee")]
Permanent = 0,
[Description("Temporary Employee")]
Temporary = 1,
[Description("Contract Employee")]
Contract = 2
}
static void EnumTest()
{
ChoTypeConverterFormatSpec.Instance.EnumFormat = ChoEnumFormatSpec.Description;
string csv = @"Id,Name,Salary,JoinedDate,EmployeeType
1,Carl,12345679,01/10/2016,Full Time Employee
2,Mark,50000,10/01/1995,Temporary Employee
3,Tom,150000,01/01/1940,Contract Employee";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.WithField("Id")
.WithField("Name")
.WithField("Salary", fieldType: typeof(float))
.WithField("JoinedDate", fieldType: typeof(DateTime))
.WithField("EmployeeType", fieldType: typeof(EmployeeType))
)
{
foreach (var rec in parser)
{
Console.WriteLine(rec.Dump());
}
}
}
20.4. 布尔值支持
Cinchoo ETL 会隐式地处理从 CSV 文件中解析布尔值。如果您想精细控制这些值的解析,可以全局地通过 ChoTypeConverterFormatSpec.BooleanFormat
指定。 默认值为 ChoBooleanFormatSpec.ZeroOrOne
FYI,更改此值将影响整个系统。
有 4 个可能的值可以用来
ChoBooleanFormatSpec.ZeroOrOne
- '0' 表示 false,'1' 表示 true。ChoBooleanFormatSpec.YOrN
- 'Y' 表示 true,'N' 表示 false。ChoBooleanFormatSpec.TrueOrFalse
- 'True' 表示 true,'False' 表示 false。ChoBooleanFormatSpec.YesOrNo
- 'Yes' 表示 true,'No' 表示 false。
清单 20.4.1 解析时指定布尔值格式规范
static void BoolTest()
{
ChoTypeConverterFormatSpec.Instance.BooleanFormat = ChoBooleanFormatSpec.ZeroOrOne;
string csv = @"Id,Name,Salary,JoinedDate,Active
1,Carl,12345679,01/10/2016,0
2,Mark,50000,10/01/1995,1
3,Tom,150000,01/01/1940,1";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.WithField("Id")
.WithField("Name")
.WithField("Salary", fieldType: typeof(float))
.WithField("JoinedDate", fieldType: typeof(DateTime))
.WithField("Active", fieldType: typeof(bool))
)
{
foreach (var rec in parser)
{
Console.WriteLine(rec.Dump());
}
}
}
20.5. DateTime 支持
Cinchoo ETL 会隐式地使用系统 Culture 或自定义设置的 Culture 来解析 datetime CSV 列值。如果您想精细控制这些值的解析,可以全局地通过 ChoTypeConverterFormatSpec.DateTimeFormat
指定。 默认值为 'd'。
FYI,更改此值将影响整个系统。
您可以使用任何有效的 标准 或 自定义 datetime .NET 格式规范来解析文件中的 datetime CSV 值。
清单 20.5.1 解析时指定 datetime 格式规范
static void DateTimeTest()
{
ChoTypeConverterFormatSpec.Instance.DateTimeFormat = "MMM dd, yyyy";
ChoTypeConverterFormatSpec.Instance.BooleanFormat = ChoBooleanFormatSpec.ZeroOrOne;
string csv = @"Id,Name,Salary,JoinedDate,Active
1,Carl,12345679,""Jan 01, 2011"",0
2,Mark,50000,""Sep 23, 1995"",1
3,Tom,150000,""Apr 10, 1999"",1";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.WithField("Id")
.WithField("Name")
.WithField("Salary", fieldType: typeof(float))
.WithField("JoinedDate", fieldType: typeof(DateTime))
.WithField("Active", fieldType: typeof(bool))
)
{
foreach (var rec in parser)
{
Console.WriteLine(rec.Dump());
}
}
}
上面的示例展示了如何解析 CSV 文件中的自定义 datetime CSV 值。
注意:由于 datetime 值包含 CSV 分隔符,因此它用双引号括起来以通过解析。
21. Fluent API
CSVReader 通过 fluent API 方法公开了一些常用的配置参数。这将使 CSV 文件解析的编程更快。
21.1 WithDelimiter
此 API 方法在 CSVReader 上设置 CSV 字段分隔符。
static void QuickDynamicTest()
{
string csv = @"Id,Name,Salary
1,Carl,10000
2,Mark,5000
3,Tom,2000";
using (var parser = ChoCSVReader.LoadText(csv)
.WithDelimiter(",")
.WithFirstLineHeader()
)
{
foreach (var rec in parser)
{
Console.WriteLine(rec.Dump());
}
}
}
21.2. WithFirstLineHeader
此 API 方法标记 CSV 文件是否包含第一行作为标题。可选的 bool 参数指定第一行是否为标题。默认为 true。
static void QuickDynamicTest()
{
string csv = @"Id,Name,Salary
1,Carl,10000
2,Mark,5000
3,Tom,2000";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
)
{
foreach (var rec in parser)
{
Console.WriteLine(rec.Dump());
}
}
}
21.3. WithFields
此 API 方法指定要用于解析和加载的 CSV 字段列表。CSV 文件中的其他字段将被丢弃。此调用将使用指定的列重新初始化。
在动态对象模型中,所有 CSV 列都将创建并解析为 string 类型。
static void QuickDynamicTest()
{
string csv = @"Id,Name,Salary
1,Carl,10000
2,Mark,5000
3,Tom,2000";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.WithFields("Id", "Name")
)
{
foreach (var rec in parser)
{
Console.WriteLine(rec.Dump());
}
}
}
21.4. WithField
此 API 方法用于添加具有特定日期类型的 CSV 列。此方法在动态对象模型中很有用,通过为每个单独的 CSV 列指定适当的数据类型。
static void QuickDynamicTest()
{
string csv = @"Id,Name,Salary
1,Carl,10000
2,Mark,5000
3,Tom,2000";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.WithField("Id")
.WithField("Name")
.WithField("Salary", typeof(float))
)
{
foreach (var rec in parser)
{
Console.WriteLine(rec.Dump());
}
}
}
在 POCO 模型中,您可以使用类型安全的 WithField API 调用选择性地包含 CSV 列
对于 POCO 类
public class EmployeeRec
{
public int Id
{
get;
set;
}
public string Name
{
get;
set;
}
public double Salary
{
get;
set;
}
}
您可以如下选择 Id、Name 字段
foreach (var e in new ChoCSVReader<EmployeeRec>("Emp.csv")
.WithFirstLineHeader()
.WithField(c => c.Id)
.WithField(c => c.Name)
)
{
Console.WriteLine(String.Format("Id: {0}", e.Id));
Console.WriteLine(String.Format("Name: {0}", e.Name));
}
21.5. QuoteAllFields
此 API 方法用于指定是否所有字段都要用引号括起来。
static void QuickDynamicTest()
{
string csv = @"Id,Name,Salary
1,"Carl,Smith",10000
2,Mark,5000
3,Tom,2000";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.QuoteAllFields()
)
{
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));
}
}
}
21.6. ColumnCountStrict
此 API 方法用于设置 CSVReader 在加载 CSV 文件中的每一行之前执行列计数检查。
static void QuickDynamicTest()
{
string csv = @"Id,Name,Salary
1,Carl,10000
2,Mark,5000
3,Tom,2000";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.ColumnCountStrict()
)
{
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));
}
}
}
21.6. ColumnOrderStrict
此 API 方法用于在加载 CSV 文件中的每一行之前设置 CSVReader 来执行列顺序检查。如果任何行中发现顺序不匹配,将报告错误。此选项仅在 CSV 文件包含第一行为标题时有效。因此,必须将其与WithFirstLineHeader()
结合使用。
static void QuickDynamicTest()
{
string csv = @"Id,Name,Salary
1,Carl,10000
2,Mark,5000
3,Tom,2000";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.ColumnOrderStrict()
)
{
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));
}
}
}
21.8. NotifyAfter
此 API 方法用于定义生成通知事件之前要处理的行数。此属性专为说明 CSV 加载进度的用户界面组件而设计。通知会发送给订阅了 RowsLoaded
事件的订阅者。
static void NotifyAfterTest()
{
string csv = @"Id,Name,Salary
1,Carl,10000
2,Mark,5000
3,Tom,2000";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.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));
}
}
}
21.9. Configure
此 API 方法用于配置未通过 fluent API 公开的所有配置参数。
static void ConfigureTest()
{
string csv = @"Id,Name,Salary
1,Carl,10000
2,Mark,5000
3,Tom,2000";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.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));
}
}
}
21.10. Setup
此 API 方法用于通过 fluent API 设置读取器的参数/事件。
static void SetupTest()
{
string csv = @"Id,Name,Salary
1,Carl,10000
2,Mark,5000
3,Tom,2000";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.Setup(r => r.BeforeRecordLoad += (o, e) =>
{
if (e.Source.CastTo<string>().StartsWith("//"))
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));
}
}
}
21.11. IgnoreHeader
此 API 方法用于简单地忽略文件中的 CSV 标题行。在这种情况下,不会执行字段名交叉检查。在动态模式下,所有列名将是 Column1、Column2 等。
static void IgnoreHeaderTest()
{
string csv = @"Id,Name,Salary
1,Carl,10000
2,Mark,5000
3,Tom,2000";
using (var parser = ChoCSVReader.LoadText(csv)
.IgnoreHeader()
)
{
foreach (var rec in parser)
{
Console.WriteLine(String.Format("Column1: {0}", rec.Column1));
Console.WriteLine(String.Format("Column2: {0}", rec.Column2));
Console.WriteLine(String.Format("Column3: {0}", rec.Column3));
}
}
}
21.12. WithEOLDelimiter
此 API 方法允许您为解析 CSV 文件设置非标准行尾 (EOL) 字符。
static void EOLDelimiterTest()
{
string csv = @"Id,Name,Salary
1,Carl,10000
2,Mark,5000
3,Tom,2000";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.WithEOLDelimiter(Environment.NewLine)
)
{
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));
}
}
}
21.13. WithHeaderLineAt
如果您的 CSV 文件带有出现在不同行的标题行,那么您可以使用此 API 方法来设置标题行号。
using (var parser = new ChoCSVReader("emp.csv").WithHeaderLineAt(10))
{
foreach (var t in parser)
{
}
}
21.14. WithMaxScanRows
当 CSV 文件在动态模型中解析时,所有字段值都被视为 string
类型。ChoCSVReader 具有自动检测字段值类型功能,方法是使用 WithMaxScanRows
方法。您可以指示读取器扫描一定数量的行以自动确定字段值的类型。
static void AutoDetectFieldTypes()
{
string csv = @"Id,Name,Salary
1,Carl,10000
2,Mark,5000
3,Tom,2000";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.WithMaxScanRows(2)
)
{
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));
}
}
}
21.15. MayHaveQuotedFields
此 API 方法用于指定是否某些字段要用引号括起来。
static void MayHaveQuotedFieldsTest()
{
string csv = @"Id,Name,Salary
1,"Carl,Smith",10000
2,Mark,5000
3,Tom,2000";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.MayHaveQuotedFields()
)
{
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));
}
}
}
22. FAQ
22.1. 我想读取带标题的文件
您可以使用 WithFirstLineHeader()
fluent API 方法,如下所示
foreach (dynamic e in new ChoCSVReader("Emp.csv").WithFirstLineHeader())
{
Console.WriteLine(String.Format("Id: {0}", e.Id));
Console.WriteLine(String.Format("Name: {0}", e.Name));
}
或使用 Configuration.FirstLineHeader
成员
foreach (dynamic e in new ChoCSVReader("Emp.csv").WithFirstLineHeader())
{
parser.Configuration.FileHeaderConfiguration.HasHeaderRecord = true;
Console.WriteLine(String.Format("Id: {0}", e.Id));
Console.WriteLine(String.Format("Name: {0}", e.Name));
}
22.1. 文件中有我想要跳过的行
您可以通过订阅解析器的 BeforeRecordLoad
事件来观察行并像下面这样忽略不需要的行
using (var parser = new ChoCSVReader("Emp.csv").WithFirstLineHeader())
{
parser.BeforeRecordLoad += (o, e) =>
{
if (e.Source != null)
{
e.Skip = ((string)e.Source).StartsWith("_");
}
};
foreach (var e in parser)
{
Console.WriteLine(String.Format("Id: {0}", e.Id));
Console.WriteLine(String.Format("Name: {0}", e.Name));
}
}
22.2. 数据文件中的字符没有被完全读取
CSVReader 足够智能,可以自动确定 CSV 文件的编码。在极少数情况下,您可能需要显式地将编码指定给 CSVReader,以便读取 CSV 文件中的所有字符。
下面的示例展示了如何将编码指定给 CSVReader 配置对象,以便读取器可以使用传递的编码值来处理文件的打开。
using (var parser = new ChoCSVReader("Emp.csv").WithFirstLineHeader())
{
parser.Configuration.Encoding = Encoding.BigEndianUnicode;
foreach (var e in parser)
{
Console.WriteLine(String.Format("Id: {0}", e.Id));
Console.WriteLine(String.Format("Name: {0}", e.Name));
}
}
22.3. 如何处理多行记录
CSVReader 可以处理数据文件中的多行记录。您可以通过 Configuration.MayContainEOLInData
选项开启此选项。
下面的示例展示了如何读取此类文件
using (var parser = new ChoCSVReader("MultiLineValue.csv").WithFirstLineHeader())
{
parser.Configuration.MayContainEOLInData = true;
foreach (var e in parser)
Console.WriteLine(e.Dump());
}
22.4. 如何处理错误并记录它们
CSVReader 通过回调机制公开,您可以通过该机制订阅事件并优雅地处理奇怪的情况。下面的示例展示了如何处理字段级别的错误并将其记录到自定义日志对象
using (var parser = new ChoCSVReader<EmployeeRec>("empwithsalary.csv").WithFirstLineHeader())
{
parser.RecordFieldLoadError += (o, e) =>
{
//Log the error message to log object
Console.Write(e.Exception.Message);
//Set the handled flag as true to continue loading of CSV file
e.Handled = true;
};
foreach (var i in parser)
Console.WriteLine(i.Dump());
}
在上面,我们订阅了 RecordFieldLoadError
事件,并在自定义日志对象中捕获任何错误。
22.5. 如何处理包含空格或特殊字符的列标题?
CSVReader 可以优雅地处理这种情况,并成功加载 CSV 文件。当您使用动态解析器时,带有特殊字符的列名将被替换为 _ 字符。
例如。
COMPANY NAME -> COMPANY_NAME
COMPANY TYPE -> COMPANY_TYPE
FIRST NAME$ -> FIRST_NAME_
COMPANY NAME, Email, Job Title,COMPANY TYPE, CITY, FIRST NAME$, LAST NAME
"Bbc Worldwide Labs, Bounce Republic Ltd",hidden @hidden.com,"Broadcaster, Voice Over & Founder Of Passion Pods", Broadcast Media, London, Emily, Chiswell
使用动态解析器加载文件时,字段名会自动规范化,如下所示,以 _ 字符为分隔符。
foreach (dynamic rec in new ChoCSVReader("Emp.csv").WithFirstLineHeader())
{
Console.WriteLine(rec.COMPANY_NAME);
Console.WriteLine(rec.COMPANY_TYPE);
Console.WriteLine(rec.FIRST_NAME_);
}
更新
现在您可以通过索引器按原始名称访问成员,如下所示
foreach (dynamic rec in new ChoCSVReader("Emp.csv").WithFirstLineHeader())
{
Console.WriteLine(rec["COMPANY_NAME");
Console.WriteLine(rec["COMPANY TYPE");
Console.WriteLine(rec["FIRST NAME$"]);
}
22.6. 加载过程中 DateTime 格式化失败?
CSVReader 使用默认文化 en-US。如果数据文件中包含不同文化格式的 datetime,CSVReader 在解析时可能会无法加载这些值。这可以通过将文化设置为匹配数据文件文化来处理。下面的示例展示了如何加载包含 en-GB 文化 datetime 的数据文件。
CSV 文件 (Emp.csv)
Id,Date,Account,Amount,Subcategory,Memo 1,09/05/2017,XXX XXXXXX,-29.00,FT,Sample string 2,09/05/2017,XXX XXXXXX,-20.00,FT,Sample string 3,25/05/2017,XXX XXXXXX,-6.30,PAYMENT,Sample string
POCO 类
class Transaction
{
public string Id { get; set; }
public DateTime Date { get; set; }
public string Account { get; set; }
public decimal Amount { get; set; }
public string Subcategory { get; set; }
public string Memo { get; set; }
}
解析代码
using (var parser = new ChoCSVReader<Transaction>("emp.csv").WithFirstLineHeader())
{
csv.Configuration.Culture = CultureInfo.GetCultureInfo("en-GB");
foreach (var t in parser)
Console.WriteLine(string.Format("{0:dd-MMM-yyyy} {1} {2,6} {3,-7} {4}", t.Date, t.Account, t.Amount, t.Subcategory, t.Memo));
}
22.7. 如何处理重复的 CSV 列?
CSVReader 无法开箱即用地处理重复的 CSV 列。如果找到,它将报错并停止解析 CSV 文件。但是有一个变通方法可以处理这种情况。
CSV 文件 (Emp.csv)
Id,Date,Account,Amount,Subcategory,Memo,Memo 1,09/05/2017,XXX XXXXXX,-29.00,FT,Memo1,DupMemo1 2,09/05/2017,XXX XXXXXX,-20.00,FT,Memo2,DupMemo2 3,25/05/2017,XXX XXXXXX,-6.30,PAYMENT,Memo3,DupMemo2
在上面,文件中存在重复的“Memo”csv 列。为了使用动态模式加载它,您应该通过 WithFirstLineHeader()
重载告诉 CSVReader 忽略标题行,如下所示
using (var parser = new ChoCSVReader("emp.csv").WithFirstLineHeader(true))
{
foreach (var t in parser)
{
Console.WriteLine(t[1]);
Console.WriteLine(t["Column1"]);
}
}
在上面,您可以选择通过索引或系统生成的名称 Column1、Column2 等来访问 CSV 列。
或者,您可以通过配置提供列名覆盖,如下所示,以便按名称访问它们
using (var parser = new ChoCSVReader("emp.csv").WithFirstLineHeader(true)
.WithColumns("Id","Date","Account","Amount","Subcategory","Memo","Memo1"))
{
foreach (var t in parser)
{
Console.WriteLine(t.Memo);
Console.WriteLine(t.Memo1);
}
}
POCO 类
使用 POCO 类进行解析时,请使用 ChoCSVRecordFieldAttribute
按位置定义与 CSV 列对应的成员。在下面,我们定义了 Memo1
对象成员来映射重复的 Memo 列。
class Transaction
{
[ChoCSVRecordField(1)]
public string Id { get; set; }
[ChoCSVRecordField(2)]
public DateTime Date { get; set; }
[ChoCSVRecordField(3)]
public string Account { get; set; }
[ChoCSVRecordField(4)]
public decimal Amount { get; set; }
[ChoCSVRecordField(5)]
public string Subcategory { get; set; }
[ChoCSVRecordField(6)]
public string Memo { get; set; }
[ChoCSVRecordField(7)]
public string Memo1 { get; set; }
}
然后,使用此 POCO 类通过 ChoCSVReader
加载文件,如下所示。
using (var parser = new ChoCSVReader<Tranaction>("emp.csv").WithFirstLineHeader(true))
{
foreach (var t in parser)
{
Console.WriteLine(t.Memo);
Console.WriteLine(t.Memo1);
}
}
22.8. 如果 CSV 标题不在第一行怎么办?
很少情况下,CSV 文件可能在第一个行以外的某个位置带有标题行。如果标题行始终出现在固定行,此示例将向您展示如何使用 WithHeaderLineAt()
方法解析文件。
CSV 文件 (Emp.csv)
# This file is produced by XYZ systems # Id - id column # Date - Date of the account opened Id,Date,Account,Amount,Subcategory,Memo,Memo1 1,09/05/2017,XXX XXXXXX,-29.00,FT,Memo1,DupMemo1 2,09/05/2017,XXX XXXXXX,-20.00,FT,Memo2,DupMemo2 3,25/05/2017,XXX XXXXXX,-6.30,PAYMENT,Memo3,DupMemo2
在上面,CSV 文件的标题位于第 4 行。(附注:索引从 1 开始)。以下是标题位于第 4 行时如何加载它们。
using (var parser = new ChoCSVReader("emp.csv").WithHeaderLineAt(4))
{
foreach (var t in parser)
{
Console.WriteLine(t.Id);
Console.WriteLine(t.Date);
}
}
如果 CSV 文件标题每次接收文件时出现在随机行,但前面只有注释行,该怎么办?可以通过标准机制处理,如下所示。
using (var parser = new ChoCSVReader("emp.csv")
.Configure(c => c.Comments = new string[] { "#" })
.WithFirstLineHeader()
)
{
foreach (var t in parser)
{
Console.WriteLine(t.Id);
Console.WriteLine(t.Date);
}
}
22.9. 如何关闭加载的记录或其他跟踪消息?
您可以通过在应用程序启动时设置 ChoETLBootstrap.TraceLevel
属性来关闭或控制 Cinchoo ETL 的跟踪消息。
ChoETLFrxBootstrap.TraceLevel = System.Diagnostics.TraceLevel.Off;
可能的值是
System.Diagnostics.TraceLevel.Off
System.Diagnostics.TraceLevel.Info;
System.Diagnostics.TraceLevel.Error;
System.Diagnostics.TraceLevel.Verbose;
22.10. 如何为 CSV 列指定默认值?
示例显示了如何为动态模型中的 CSV 列指定默认值。
using (var cr = new ChoCSVReader("sample.csv") .WithFirstLineHeader() .WithField("firstName", defaultValue) .WithField("lastName") .WithField("salary", defaultValue: 100, fieldType: typeof(double)) ) ) { foreach (var x in cr) Console.WriteLine(ChoUtility.Dump(x)); }
在上面的示例中,为 'Salary' CSV 列使用 WithField 流畅 API 指定了默认值 100。
在 POCO 模型中,可以使用 System.ComponentModel.DefaultValueAttribute
指定默认值,如下所示。
public class Employee { public string FirstName { get; set; } public string LastName { get; set; } [DefaultValue(100)] public double Salary { get; set; } }
22.11. Cinchoo ETL 支持分层对象吗?
是的,它确实可以。如果您的 CSV 文件带有标题,其中 CSV 列名使用分层分隔符指定,Cinchoo ETL 会自动将其构造为嵌套对象。
附注:此功能目前仅在动态对象模型中受支持。
嵌套 CSV
id,name,category/id,category/name,category/subcategory/id,category/subcategory/name,description 1,Test123,15,Cat123,10,SubCat123,Desc123
上面的示例 CSV 文件带有嵌套的列名,例如 category/name,category/subcategory/id 等。列名分隔符是 '/'。为了将它们视为嵌套列并加载为嵌套对象,您必须在 ChoCSVReader 引擎中指定 'NestedColumnSepatator
' 属性。下面的示例显示了如何执行此操作。
using (var csv = new ChoCSVReader("nested.csv").WithFirstLineHeader()
.Configure(c => c.NestedColumnSeparator = '/')
)
{
foreach (var x in csv)
Console.WriteLine(x.DumpAsJson());
}
22.12. Cinchoo 驱动程序是否自动发现列数据类型?
是的,它确实可以。CSV 配置公开 MaxScanRows
属性,用于在决定列数据类型之前指定的要扫描的行数。默认值为 0。Cinchoo ETL 驱动程序应用其自身的算法来确定列类型。这并不总是有效。仅当所有单元格值都存在于文件中时,它才更有效。
using (var csv = new ChoCSVReader("sample.csv").WithFirstLineHeader()
.Configure(c => c.MaxScanRows = 10)
)
{
foreach (var x in csv)
Console.WriteLine(x.DumpAsJson());
}
22.13 如何在 CSV 文件中注释或忽略行?
有几种方法可以忽略行。
使用注释字符
using (var csv = new ChoCSVReader("sample.csv").WithFirstLineHeader()
.Configure(c => c.Comment = "#")
)
{
foreach (var x in csv)
Console.WriteLine(x.DumpAsJson());
}
通过在 CSVReader 引擎上注册 'BeforeRecordLoad
' 事件处理程序。
foreach (dynamic rec in new ChoCSVReader("emp.csv")
.Setup(r => r.BeforeRecordLoad += (o, e) =>
{
if (e.Source != null)
{
e.Skip = ((string)e.Source).StartsWith("#");
}
})
)
{
Console.WriteLine(rec.Id);
}
22.14 如何将 CSV 文件批量复制到 SqlServer (任何数据库)?
SqlBulkCopy 可让您高效地将数据批量加载到 SQL Server。使用 Cinchoo ETL,您可以快速将大型 CSV 文件加载到 SQL Server。下面的示例显示了如何做到这一点。
对于示例 CSV
CustId,Name,Balance,AddedDate
1732,Tom Perez,435.00,05/11/2002
5541,Pedro Gomez,12342.30,06/02/2004
1122,Mark Politti,0.00,01/02/2000
1924,Pablo Ramirez,3321.30,11/24/2002
定义与上述 CSV 文件格式匹配的数据库表。
IF OBJECT_ID ('dbo.Customers') IS NOT NULL
DROP TABLE dbo.Customers
GO
CREATE TABLE dbo.Customers
(
CustId INT NOT NULL,
Name NVARCHAR (max) NULL,
Balance DECIMAL (18, 2) NOT NULL,
AddedDate DATETIME NOT NULL
)
GO
以下是如何将文件上传到 SQL Server。
string connectionstring =
@"Data Source=(localdb)\v11.0;Initial Catalog=TestDb;Integrated Security=True";
using (SqlBulkCopy bcp = new SqlBulkCopy(connectionstring))
{
using (var dr = new ChoCSVReader("Cust.csv").WithFirstLineHeader()
.WithField("CustId", fieldType: typeof(int))
.WithField("Name", fieldType: typeof(string))
.WithField("Balance", fieldType: typeof(double))
.WithField("AddedDate", fieldType: typeof(DateTime))
.AsDataReader())
{
bcp.DestinationTableName = "dbo.Customers";
bcp.EnableStreaming = true;
bcp.BatchSize = 10000;
bcp.BulkCopyTimeout = 0;
bcp.NotifyAfter = 10;
bcp.SqlRowsCopied += delegate (object sender, SqlRowsCopiedEventArgs e)
{
Console.WriteLine(e.RowsCopied.ToString("#,##0") + " rows copied.");
};
bcp.WriteToServer(dr);
}
}
22.15. 获取 CSV 文件列名的最佳方法是什么?
标题存储在 CSVReader.Context
对象中。要获取标题,您必须读取第一条记录。示例显示了如何获取 CSV 文件的列名。
Id, Name 1, Tom 2, Mark
示例代码显示了如何获取列名。
using (var p = new ChoCSVReader("emp.csv").WithFirstLineHeader())
{
p.Read();
Console.WriteLine(String.Join(", ", p.Context.Headers));
}
22.16. 有没有办法通过不同的列名解析 CSV,并进行映射?
假设您有多个具有不同列名的 CSV 文件,并希望使用具有相同解析器的 POCO 对象进行解析。Cinchoo ETL 提供了一种设置此场景的方法,通过为每个字段指定备用列名。
对于 CSV 文件
Emp1.csv
Num, FirstName 1, Tom 2, Mark
Emp2.csv
Identity, LastName 1, Tom 2, Mark
为了使用相同的 POCO 对象解析这两个文件,您必须按如下方式设置。
public class Employee
{
[ChoCSVRecordField(1, AltFieldNames = "Num, Identity")]
public int Id { get; set; }
[ChoCSVRecordField(1, AltFieldNames = "FirstName, LastName")]
public int Name { get; set; }
}
一旦您将列与备用名称一起设置好,您就可以使用相同的 POCO 来解析以上两个文件。下面的示例显示了如何执行此操作。
using (var p = new ChoCSVReader<Employee>("emp1.csv").WithFirstLineHeader())
{
foreach (var emp in p)
Console.WriteLine(emp.Id);
}
using (var p = new ChoCSVReader<Employee>("emp2.csv").WithFirstLineHeader())
{
foreach (var emp in p)
Console.WriteLine(emp.Id);
}
22.17. 如何将 CSV 文件提取到 DataTable?
您可以使用 Cinchoo ETL 仅用几行代码即可完成此操作。
using (var p = new ChoCSVReader("emp.csv").WithFirstLineHeader())
{
DataTable dt = p.AsDataTable();
}
22.18. 如何将 CSV 文件提取到 DataReader?
您可以使用 Cinchoo ETL 仅用几行代码即可完成此操作。
using (var p = new ChoCSVReader("emp.csv").WithFirstLineHeader())
{
IDataReader dr = p.AsDataReader();
while (dr.Read())
{
Console.WriteLine("Id: {0}, Name: {1}", dr[0], dr[1]);
}
}
22.19. 如何处理包含引号的列值?
通过将 QuoteField
属性值设置为 true,可以指定带有引号的值的列。假设下面的 CSV 文件中的 Name 列周围带有引号。
Emp.csv
Id, Name 1, "Tom" 2, "Mark"
您可以在 POCO 对象中将 Name
属性设置为 QuoteField
为 true。
public class Employee
{
[ChoCSVRecordField(1)]
public int Id { get; set; }
[ChoCSVRecordField(1, QuoteField = true)]
public int Name { get; set; }
}
22.20. 如何将 CSV 文件读入类型化的 DataTable?
Cinchoo CSV 读取器通过公开 Fill()
方法将 CSV 文件填充到类型化数据表中,从而简化了这一点。
假设 DataSet1
是用 Employees 类型化数据表创建的。下面的示例显示了如何将其加载到 CSV。
DataSet1 ds = new DataSet1();
using (var p = new ChoCSVReader("emp.csv").WithFirstLineHeader())
{
p.Fill(ds.Employees);
}
22.21. 为了跟踪目的,我如何找到 CSV 文件中的空行?
Cinchoo CSV 读取器公开回调事件来报告 CSV 文件中找到的空行。
下面的示例显示了如何在动态模型中实现这一点。
static void ReportEmptyLines()
{
using (var p = new ChoCSVReader("EmptyLines.csv").WithFirstLineHeader()
.Setup(s => s.EmptyLineFound += (o, e) =>
{
Console.WriteLine(e.LineNo);
})
)
{
foreach (dynamic rec in p)
Console.WriteLine(rec.DumpAsJson());
}
}
在 POCO 模型中,您还可以通过实现 IChoEmptyLineReportable
接口的记录对象来跟踪它们。
public class Employee : IChoEmptyLineReportable
{
[ChoCSVRecordField(1)]
public int Id { get; set; }
[ChoCSVRecordField(1, QuoteField = true)]
public int Name { get; set; }
bool EmptyLineFound(long lineNo)
{
Console.WriteLine("Empty line at: " + lineNo);
return true; //true to continue the process, otherwise false
}
}
然后,解析代码如下所示。
static void ReportEmptyLines()
{
using (var p = new ChoCSVReader<Employee>("EmptyLines.csv").WithFirstLineHeader())
{
foreach (var rec in p)
Console.WriteLine(rec.DumpAsJson());
}
}
22.22. 如何更改字段的顺序?
如果您有多个具有以下设置的类
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
}
public class FullTime : Employee
{
public double Salary { get; set; }
public string Department { get; set; }
}
public class Contract : Employee
{
public double Rate { get; set; }
public string Department { get; set; }
}
Cinchoo CSV 读取器公开回调事件来报告 CSV 文件中找到的空行。
下面的示例显示了如何在动态模型中实现这一点。
static void ReportEmptyLines()
{
using (var p = new ChoCSVReader("EmptyLines.csv").WithFirstLineHeader()
.Setup(s => s.EmptyLineFound += (o, e) =>
{
Console.WriteLine(e.LineNo);
})
)
{
foreach (dynamic rec in p)
Console.WriteLine(rec.DumpAsJson());
}
}
22.23. 如何关闭列类型发现?
Cinchoo CSV 读取器在动态模型中自动最佳地发现列类型。您可以通过将 Configuration.MaxScanRows
设置为 0 来关闭此功能。在这种情况下,所有列类型都将被视为字符串类型,除非另有明确指定。
static void TurnOffColumnTypeDiscovery()
{
using (var p = new ChoCSVReader("EmptyLines.csv").WithFirstLineHeader()
.Configure(c => c.MaxScanRows = 0)
)
{
foreach (var rec in p)
Console.WriteLine(rec.DumpAsJson());
}
}
22.24. 如何在 CSV 解析过程中有条件地跳过行?
如果 CSV 文件包含实际 CSV 数据之外的行,并且您想忽略它们,Cinchoo 会公开 SkipUntil
回调事件来跳过这些行。下面的示例显示了如何跳过 CSV 文件中的行。
static void SkipUntilTest()
{
using (var p = new ChoCSVReader("EmptyLines.csv").WithFirstLineHeader()
.Setup(p => p.SkipUntil += (o, e) =>
{
string line = e.Source as string;
e.Skip = !line.StartsWith("** Some Match **")
})
)
{
foreach (var rec in p)
Console.WriteLine(rec.DumpAsJson());
}
}
22.25. 如何有条件地停止 CSV 解析?
如果 CSV 文件包含实际 CSV 数据之外的行,并且您想在遇到无效行时停止解析,Cinchoo 会公开 DoWhile
回调事件来自定义逻辑并在需要时停止解析。下面的示例显示了如何停止 CSV 解析。
static void DoWhileTest()
{
using (var p = new ChoCSVReader("EmptyLines.csv").WithFirstLineHeader()
.Setup(p => p.DoWhile += (o, e) =>
{
string line = e.Source as string;
e.Stop = line.StartsWith("** Some Match **")
})
)
{
foreach (var rec in p)
Console.WriteLine(rec.DumpAsJson());
}
}
22.26. Cinchoo 读取器支持将 CSV 加载到子类吗?
是的,它确实可以。确保字段名是唯一的。如果存在重复,您可以通过使用 DisplayNameAttribute
指定唯一名称来解决它们。此外,DisplayNameAttribute
可用于将类成员映射到特定的 CSV 列。下面的示例显示了如何执行此操作。
对于示例 CSV 文件
SITE_ID,HOUSE,STREET,CITY,STATE,ZIP,APARTMENT 44,545395,PORT ROYAL,CORPUS CHRISTI,TX,,2 44,608646,TEXAS AVE,ODESSA,TX,79762, 44,487460,EVERHART RD,CORPUS CHRISTI,TX,78413, 44,275543,EDWARD GARY,SAN MARCOS,TX,78666,4 44,136811,MAGNOLIA AVE,SAN ANTONIO,TX1,,1
类结构
public class Site
{
public int SiteID { get; set; }
public int House { get; set; }
public SiteAddress SiteAddress { get; set; }
public int Apartment { get; set; }
}
public class SiteAddress
{
public string Street { get; set; }
public string City { get; set; }
public SitePostal SitePostal { get; set; }
}
public class SitePostal
{
public string State { get; set; }
public string Zip { get; set; }
}
解析代码
using (var p = new ChoCSVReader<Site>("Sample3.csv")
.WithFirstLineHeader()
)
{
foreach (var rec in p)
Console.WriteLine(rec.Dump());
}
22.27. 如何在子对象上开启验证?
通过用 ChoValidateObjectAttribute
装饰子类成员,您可以开启对其的验证。
public class Site
{
[Required(ErrorMessage = "SiteID can't be null")]
public int SiteID { get; set; }
[Required]
public int House { get; set; }
[ChoValidateObject]
public SiteAddress SiteAddress { get; set; }
public int Apartment { get; set; }
}
public class SiteAddress
{
[Required]
public string Street { get; set; }
[Required]
[RegularExpression("^[a-zA-Z][a-zA-Z ]*$")]
public string City { get; set; }
}
在上面,SiteAddress 是一个子成员,它用 ChoValidateObjectAttribute
进行了装饰,以便在 CSV 解析期间触发对象验证。
下面的示例显示了启用验证后加载 CSV 文件。
using (var p = new ChoCSVReader<Site>("Sample3.csv")
.WithFirstLineHeader()
.Configure(c => c.ObjectValidationMode = ChoObjectValidationMode.ObjectLevel)
)
{
foreach (var rec in p)
Console.WriteLine(rec.Dump());
}
22.28. 如何为成员指定 CSV 列大小?
您可以使用 ChoCSVRecordFieldAttribute
或 StringLengthAttribute (System.ComponentModel.DataAnnotations)
来指定每个 CSV 列值的长度。默认情况下,如果 CSV 值超出指定大小,CSV 读取器会截断并加载 CSV 值。要关闭截断行为,请将 ChoCSVConfiguration.Truncate
设置为 false。如果截断关闭,如果找到超出大小的 CSV 值,则会引发异常。
public class Site
{
[Required(ErrorMessage = "SiteID can't be null")]
[StringLength(5)]
public int SiteID { get; set; }
[Required]
public int House { get; set; }
[ChoValidateObject]
public SiteAddress SiteAddress { get; set; }
public int Apartment { get; set; }
}
上面的示例中,SiteID 使用 StringLengthAttribute 指定了大小 10。
22.28. 如何为成员指定 CSV 列名?
您可以使用 ChoCSVRecordFieldAttribute
或 DisplayAttribute (System.ComponentModel.DataAnnotations)
将 CSV 列映射到对象成员。
public class Site
{
[Required(ErrorMessage = "SiteID can't be null")]
[Display(Name="ID")]
public int SiteID { get; set; }
[Required]
public int House { get; set; }
[ChoValidateObject]
public SiteAddress SiteAddress { get; set; }
public int Apartment { get; set; }
}
22.29. 如何处理 CSV 中的特殊 null 值?
Cinchoo 通过 Configuration.NullValue
处理 CSV 值中的 null 特殊值。
对于 CSV 文件
Id, Name, City 1, Tom, #NULL# 2, Mark, NJ 3, Lou, FL 4, Smith, PA 5, Raj, DC
在上面的 CSV 中,一些城市值被指定为 #NULL# 来表示 null 值。这可以按如下方式处理。
using (var cp2 = new ChoCSVReader(new StringReader(csv))
.WithFirstLineHeader()
.Configure(c => c.NullValue = "#NULL#")
)
{
foreach (var rec in cp2)
Console.WriteLine(rec.Dump());
}
或者,如果使用 POCO 对象进行解析,则可以在对象级别按如下方式指定。
[ChoCSVRecordObject(NullValue = "#NULL#")]
public class Emp
{
public int Id { get; set; }
public string Name { get; set; }
public string City { get; set; }
}
或在成员级别
public class Emp
{
[ChoCSVRecordField(1)]
public int Id { get; set; }
[ChoCSVRecordField(2)]
public string Name { get; set; }
[ChoCSVRecordField(3, NullValue = "#NULL#")]
public string City { get; set; }
}
22.30 Cinchoo 是否处理货币值?
是的,它确实可以。Cinchoo 会根据当前文化自动发现它们,并默认将它们加载为 ChoCurrency
对象。
对于 CSV 文件
Id, Name, City, Salary 1, Tom, NY, $10000 2, Mark, NJ, $12000 3, Lou, FL, $150000 4, Smith, PA, $132000 5, Raj, DC, $200500
在上面的 CSV 中,Salary 字段将加载为 ChoCurrency 类型。
using (var cp2 = new ChoCSVReader(new StringReader(csv))
.WithFirstLineHeader()
)
{
foreach (var rec in cp2)
Console.WriteLine(rec.Dump());
}
或者,您可以通过将 Configuration.TreatCurrencyAsDecimal
设置为 true 将货币值加载为 double。
foreach (var p in new ChoCSVReader("Sample2.csv").WithFirstLineHeader()
.Configure(c => c.TreatCurrencyAsDecimal = false)
)
{
Console.WriteLine(p.Dump());
}
22.31 Cinchoo 能读取同一个 CSV 中的不同记录类型吗?
是的,它确实可以。您将使用自定义记录选择器来扫描每一行,并将匹配的记录类型返回给驱动程序以加载该行。
对于 CSV 文件
PlaceName,Longitude,Latitude,Elevation NameString,123.456,56.78,40 Date,Count 1/1/2012,1 2/1/2012,3 3/1/2012,10 4/2/2012,6
定义与 CSV 文件中的记录匹配的 POCO 类,如下所示。
public class LocationDef
{
public string PlaceName { get; set; }
public double Longitude { get; set; }
public double Latitude { get; set; }
public double Elevation { get; set; }
}
public class CountDef
{
public DateTime Date { get; set; }
public int Count { get; set; }
}
在上面的 CSV 中,Salary 字段将加载为 ChoCurrency 类型。
using (var p = new ChoCSVReader(new StringReader(csv))
.WithCustomRecordSelector((l) =>
{
Tuple<long, string> kvp = l as Tuple<long, string>;
if (kvp.Item1 == 1 || kvp.Item1 == 3 || kvp.Item1 == 4) //Skip the headers and empty lines
return null;
if (kvp.Item1 < 4)
return typeof(LocationDef);
else
return typeof(CountDef);
}
)
)
{
foreach (var rec in p)
Console.WriteLine(ChoUtility.Dump(rec));
}
22.32. 如何验证 CSV 文件?
您可能需要先验证 CSV 文件,然后再对其执行其他操作(例如,将其保存到数据库)。Cinchoo 提供了用于执行验证的 API。它利用 DataAnnonations/Validation 库来执行 CSV 验证。下面的示例显示了如何执行此操作。
首先,使用验证规则声明 POCO 对象。
public class Site
{
[Required(ErrorMessage = "SiteID can't be null")]
public int SiteID { get; set; }
[Required]
public int House { get; set; }
[ChoValidateObject]
public SiteAddress SiteAddress { get; set; }
public int Apartment { get; set; }
}
要执行验证,
using (var p = new ChoCSVReader<Site>("Sample3.csv")
.WithFirstLineHeader(true)
)
{
Exception ex;
Console.WriteLine("IsValid: " + p.IsValid(out ex));
}
22.33. 如何对大型 CSV 文件进行排序?
当需要排序大型 CSV 文件时,它们会带来挑战。如果您尝试使用 .NET 应用程序进行排序,它会因 OutOfMemoryException
而失败。对于在大型文件上进行排序的 Microsoft Excel,您会立即收到错误。
如果 string
排序对您有用,那么只需使用 Windows SORT
命令。对文件进行排序即可完成。
如果您需要过滤和转换文件,特别是日期/自定义类型字段,那么您需要编写一个小型转换程序,将字段转换为您喜欢的任何内容,然后重写记录。这就是 Cinchoo ETL 框架的作用,它能够以最小的内存占用和超快的性能,轻松地将大型 CSV 文件转换为您想要的任何格式,并满足所有可能的业务需求。
示例 CSV 文件
CustId,Name,Balance,AddedDate
1732,Tom Perez,435.00,05/11/2002
5541,Pedro Gomez,12342.30,06/02/2000
1122,Mark Politti,0.00,01/02/2004
1924,Pablo Ramirez,3321.30,11/24/2002
定义一个 POCO 类
public class Customer
{
public int CustId { get; set; }
public string Name { get; set; }
public decimal Balance { get; set; }
public DateTime AddedDate { get; set; }
}
代码显示了如何按 'AddedDate
' 对馈送进行排序。
public static void POCOSort()
{
using (var dr = new ChoCSVReader<Customer>(@"Test.csv").WithFirstLineHeader())
{
foreach (var rec in dr.ExternalSort(new ChoLamdaComparer<Customer>((e1, e2) => DateTime.Compare(e1.AddedDate, e1.AddedDate))))
{
Console.WriteLine(rec.CustId);
}
}
}
不带 POCO 类的 CSV 文件排序
public static void DynamicSort()
{
using (var dr = new ChoCSVReader(@"Test.csv").WithFirstLineHeader())
{
foreach (var rec in dr.ExternalSort(new ChoLamdaComparer<dynamic>((e1, e2) => DateTime.Compare(e1.AddedDate, e1.AddedDate))))
{
Console.WriteLine(rec.CustId);
}
}
}
22.34 如何从 CSV 加载中忽略成员?
在选择退出模型中,您可以使用 ChoIgnoreMemberAttribute
来指定在 CSV 加载过程中要忽略的成员。
public class Employee
{
public int Id { get; set; }
public int Name { get; set; }
[ChoIgnoreMember]
public string City { get; set; }
}
22.35. 如何处理自定义日期时间格式的值?
有几种方法可以处理 CSV 文件中的自定义日期值。最简单、内置的选项是在 CSV 列的 formatText
中设置自定义格式字符串来处理它。
对于示例 CSV 文件
Id, DateCreated 1, 20180201 2, 20171120
其中 DateCreated
列的日期值格式为 'yyyyMMdd'。下面的示例展示了如何使用动态模型处理此类值并成功加载文件。
using (var p = new ChoCSVReader(new StringReader(csv))
.WithFirstLineHeader()
.WithField("Id", fieldType: typeof(int))
.WithField("Date", fieldType: typeof(DateTime), formatText: "yyyyMMdd")
)
{
foreach (var rec in p)
Console.WriteLine(rec.Dump());
}
在 POCO 选择退出模型中,您必须按如下方式编写类。
[ChoCSVFileHeader]
public class Consumer
{
public int Id { get; set; }
[DisplayFormat(DataFormatString = "yyyyMMdd")]
public DateTime DateCreated { get; set; }
}
其中 DateCreated
字段用 DisplayFormat
属性进行了装饰,其中包含格式文本。
在 POCO 选择加入模型中,您必须定义一个包含所有成员的类,并使用 ChoCSVRecordFieldAttrbute
,如下所示。
[ChoCSVFileHeader]
public class ConsumerOptIn
{
[ChoCSVRecordField(1)]
public int Id { get; set; }
[ChoCSVRecordField(2, FormatText = "yyyyMMdd")]
public DateTime DateCreated { get; set; }
}
在上面,DateCreated
字段在 ChoCSVRecordFieldAttribute
中指定了格式文本,以满足自定义格式化需求。
22.36. 如何处理自定义布尔值
有几种方法可以处理 CSV 文件中的自定义布尔值。最简单、内置的选项是在 CSV 列的 formatText 中设置自定义格式字符串来处理它。
以下是可用的可能标准格式规范(对布尔值执行不区分大小写的比较)。
- YOrN - 'Y' - true,'N' - false
- TOrF - 'T' - true,'F' - false
- TrueOrFalse - 'true' - true,'false' - false
- YesOrNo - 'yes' - true,'no' - false
- ZeroOrOne - '1' - true,'0' - false
对于示例 CSV 文件
Id, DateCreated, IsActive 1, 20180201, A 2, 20171120, B
其中 IsActive
列包含自定义布尔值。'A' - true,其他值 - false。
下面的示例展示了如何使用动态模型处理此类值并成功加载文件。
using (var p = new ChoCSVReader(new StringReader(csv)) .WithFirstLineHeader() .WithField("Id", fieldType: typeof(int)) .WithField("Date", fieldType: typeof(DateTime), formatText: "yyyyMMdd") .WithField("IsActive", fieldType: typeof(bool), formatText: "A") ) { foreach (var rec in p) Console.WriteLine(rec.Dump()); }
在 POCO 选择退出模型中,您必须按如下方式编写类。
[ChoCSVFileHeader] public class Consumer { public int Id { get; set; } [DisplayFormat(DataFormatString = "yyyyMMdd")] public DateTime DateCreated { get; set; } [DisplayFormat(DataFormatString = "A")] public bool IsActive { get; set; } }
其中 IsActive
字段用 DisplayFormat
属性进行了装饰,其中格式文本为 'A'。
在 POCO 选择加入模型中,您必须定义一个包含所有成员的类,并使用 ChoCSVRecordFieldAttrbute
,如下所示。
[ChoCSVFileHeader] public class ConsumerOptIn { [ChoCSVRecordField(1)] public int Id { get; set; } [ChoCSVRecordField(2, FormatText = "yyyyMMdd")] public DateTime DateCreated { get; set; } [ChoCSVRecordField(3, FormatText = "A")] public bool IsActive { get; set; } }
在上面,IsActive
字段在 ChoCSVRecordFieldAttribute
中指定了格式文本,以满足自定义格式化需求。
22.37. 如何处理自定义布尔值
Cinchoo 会无缝处理它。只需定义类结构,并将 CSV 列映射到每个成员。
对于示例 CSV 文件
Id, Name, Street, City 1, Tom, 1 Main Street, New York 2, Mark, 10 River Road, Boston
您可以按字段名匹配 CSV 文件,如下所示定义类结构。
public class StudentInfo
{
[DisplayName("Id")]
public string Id { get; set; }
public Student Student { get; set; }
}
public class Student
{
[DisplayName("Name")]
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
[DisplayName("Street")]
public string Street { get; set; }
[DisplayName("City")]
public string City { get; set; }
}
或者,您可以按字段位置匹配 CSV 文件,如下所示定义类结构。
public class StudentInfo
{
[ChoFieldPosition(1)]
public string Id { get; set; }
public Student Student { get; set; }
}
public class Student
{
[ChoFieldPosition(2)]
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
[ChoFieldPosition(3)]
public string Street { get; set; }
[ChoFieldPosition(4)]
public string City { get; set; }
}
要将 CSV 文件解析到上面的类结构,您可以按如下方式进行。
using (var r = ChoCSVReader<StudentInfo>.LoadText(csv)
.WithFirstLineHeader()
)
{
foreach (var rec in r)
{
Console.WriteLine(rec.Dump());
}
}
22.38. 如何将 CSV 读取到集合/数组成员?
Cinchoo 处理将 CSV 加载到特定类型的数组/集合成员中。只需定义类结构,并将 CSV 列映射到每个成员。
对于示例 CSV 文件,其中 CreId_0
、CreName_0
、CreId_1
、CreName_2
是要加载到集合对象中的值。
Id, Name, CreId_0, CreName_0, CreId_1, CreName_1 1, Tom, CI0, CN0, CI1, CN1 2, Mark, CI20, CN20, CI21, CN21
您可以按字段名匹配 CSV 文件,如下所示定义类结构。
public class StudentInfo
{
public string Id { get; set; }
public string Name { get; set; }
[Range(1, 2)]
public Course[] Courses { get; set; }
[DisplayName("Grade")]
[Range(1, 3)]
public List<string> Grades { get; set; }
public StudentInfo()
{
Courses = new Course[2];
}
}
public class Course
{
[DisplayName("CreId")]
public string CourseId { get; set; }
[DisplayName("CreName")]
public string CourseName { get; set; }
}
在上面,将 Courses 定义为 Course
对象的数组,并使用 RangeAttribute
指定出现次数(此处为 2 个项目 [0, 1])。必须将 Courses
初始化为对象数组。
以下是加载 CSV 到 StudentInfo
对象的示例代码。
string csv = @"Id, Name, CreId_0, CreName_0, CreId_1, CreName_1,Grade_1,Grade_2,Grade_3
1, Tom, CI0, CN0, CI1, CN1,A,B,C
2, Mark, CI20, CN20, CI21, CN21,A,B,C
";
using (var r = ChoCSVReader<StudentInfo>.LoadText(csv)
.WithFirstLineHeader()
)
{
foreach (var rec in r)
{
Console.WriteLine(rec.Dump());
}
}
默认数组分隔符为 '_'。如果您的 CSV 文件使用不同的数组分隔符,您可以在 Configuration.ArrayIndexSeparator
中指定它们。
下面的示例显示了如何使用配置来处理。
string csv = @"Id, Name, CreId_0, CreName_0, CreId_1, CreName_1,Grade_1,Grade_2,Grade_3
1, Tom, CI0, CN0, CI1, CN1,A,B,C
2, Mark, CI20, CN20, CI21, CN21,A,B,C
";
var config = new ChoCSVRecordConfiguration<StudentInfo>()
.Map(f => f.Id)
.Map(f => f.Grades, "Grade")
.IndexMap(f => f.Courses, 0, 1)
.IndexMap(f => f.Grades, 1, 3)
.MapForType<Course>(f => f.CourseId, "CreId")
.MapForType<Course>(f => f.CourseName, "CreName")
.WithFirstLineHeader();
using (var r = ChoCSVReader<StudentInfo1>.LoadText(csv, config))
{
foreach (var rec in r)
{
Console.WriteLine(rec.Dump());
}
}
在上面,使用 Map
、IndexMap
、MapForType
辅助方法配置带有 POCO 对象模型的 CSV 布局,以成功将 CSV 文件加载到定义的结构中。
22.39. 如何正确地在 Excel 中保留前导/填充零?
当您在 Excel 中打开 CSV 文件时,所有数字的前导零都会被删除。这实际上是 Excel 的问题。要解决此问题,必须将 CSV 字段值从 08820
保存为 ="08820"
。这将在您在 Excel 中打开它们时保留前导零。
如果 CSV 文件是 Excel 感知的,您可以使用 ChoCSVReader
来开箱即用地处理它们。您可以通过几种方式指示解析器。
- 通过设置
ExcelField
属性为 true 来处理特定字段。 - 通过将
ImplicitExcelFieldValueHandling
设置为 true 来全局处理。
列表 22.39.1 全局 Excel 字段值处理方法。
static void ExcelFieldTest()
{
string csv = @"Id,Name,Salary
1,Carl,=""10000""
2,Mark,=""5000""
3,Tom,=""2000""";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.Configure(c => c.ImplicitExcelFieldValueHandling = 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));
}
}
}
列表 22.39.2 目标 CSV 字段 Excel 值处理方法。
static void ExcelFieldTest()
{
string csv = @"Id,Name,Salary
1,Carl,=""10000""
2,Mark,=""5000""
3,Tom,=""2000""";
using (var parser = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.WithField("Id")
.WithField("Name")
.WithField("Salary", m => m.Configure(c => c.ExcelField = 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));
}
}
}
23.40. ChoCSVReader 支持多行标题吗?
在极少数情况下,某些 CSV 文件可能带有换行符标题。ChoCSVReader 会无缝处理它。下面的示例 CSV 文件在第 1-2 行带有换行符 CSV 标题。
列表 22.40.1 换行符标题 CSV 文件。
CUSTOMER,CUSTOMER NAME,INVOICE ID,PURCHASE,PRODUCT ID,PURCHASED,PURCHASED QTY,LOCATION ID,DATE,AMOUNT,QUANTITY ID 22160,MANSFIELD BROTHERS HEATING & AIR,sss.001,02/08/2017,193792,69.374,2,30 27849,OWSLEY SUPPLY LLC - EQUIPMENT,sss.001,03/14/2017,123906,70.409,1,2 27849,OWSLEY SUPPLY LLC - EQUIPMENT,sss.001,03/14/2017,40961,10.000,1,2 16794,ALEXANDER GILMORE dba AL'S HEATING,sss.001,01/25/2017,116511,63.016,1,15 16794,ALEXANDER GILMORE dba AL'S HEATING,sss.001,01/25/2017,116511,-63.016,-1,15 16794,ALEXANDER GILMORE dba AL'S HEATING,sss.001,01/25/2017,122636,30.748,1,15 16794,ALEXANDER GILMORE dba AL'S HEATING,sss.001,01/25/2017,137661,432.976,1,15 16794,ALEXANDER GILMORE dba AL'S HEATING,sss.001,01/25/2017,137661,-432.976,-1,15
示例代码展示了如何成功解析上述 CSV 文件。
列表 22.40.2 解析换行符标题 CSV 文件。
static void MultiLineHeaderTest()
{
string csv = @"CUSTOMER,CUSTOMER NAME,INVOICE ID,PURCHASE,PRODUCT ID,PURCHASED,PURCHASED QTY,LOCATION
ID,DATE,AMOUNT,QUANTITY ID
22160,MANSFIELD BROTHERS HEATING & AIR,sss.001,02/08/2017,193792,69.374,2,30
27849,OWSLEY SUPPLY LLC - EQUIPMENT,sss.001,03/14/2017,123906,70.409,1,2
27849,OWSLEY SUPPLY LLC - EQUIPMENT,sss.001,03/14/2017,40961,10.000,1,2
16794,ALEXANDER GILMORE dba AL'S HEATING,sss.001,01/25/2017,116511,63.016,1,15
16794,ALEXANDER GILMORE dba AL'S HEATING,sss.001,01/25/2017,116511,-63.016,-1,15
16794,ALEXANDER GILMORE dba AL'S HEATING,sss.001,01/25/2017,122636,30.748,1,15
16794,ALEXANDER GILMORE dba AL'S HEATING,sss.001,01/25/2017,137661,432.976,1,15
16794,ALEXANDER GILMORE dba AL'S HEATING,sss.001,01/25/2017,137661,-432.976,-1,15";
foreach (var rec in ChoCSVReader.LoadText(csv)
.WithMaxScanRows(2)
.Setup(s =>
{
s.MultiLineHeader += (o, e) =>
{
if (e.LineNo <= 2)
e.IsHeader = true;
else
e.IsHeader = false;
};
})
.Configure(c => c.TurnOnMultiLineHeaderSupport = true)
.ThrowAndStopOnMissingField(false)
)
Console.WriteLine(rec.Dump());
}
23.41. 如何将 CSV 加载到 POCO 类型的字典成员?
如果您的 CSV 文件带有您想加载到字典成员中的列,您可以使用 ChoCSVReader 来实现。对于下面的示例 CSV 文件
Id, Name, K1, K2 1, Tom, A, B 2, Mark, C, D
其中 K1、K2 是您想加载到字典成员中的列。
首先,定义一个 POCO 类,如下所示。
public class StudentInfo
{
public string Id { get; set; }
public string Name { get; set; }
[ChoDictionaryKey("K1, K2")]
public Dictionary<string, string> Grades { get; set; }
}
在上面,Grades
成员用 ChoDictionaryKeyAttribute
进行了装饰,并指定了可能要加载到其中的 CSV 列。
然后,您可以消费 CSV 文件,ChoCSVReader 将负责将数据正确加载到对象中的底层工作。
public static void CSV2DictionaryMemberTest()
{
string csv = @"Id, Name, K1, K2
1, Tom, A, B
2, Mark, C, D
";
using (var r = ChoCSVReader<StudentInfo2>.LoadText(csv))
{
foreach (var rec in r)
Console.WriteLine(rec.Dump());
}
}
接下来,我将向您展示如何使用配置方法来实现此目的。
public static void CSV2DictionaryMemberTest()
{
string csv = @"Id, Name, K1, K2
1, Tom, A, B
2, Mark, C, D
";
var config = new ChoCSVRecordConfiguration<StudentInfo2>()
.Map(f => f.Id)
.Map(f => f.Name)
.DictionaryMap(f => f.Grades, new string[] { "K1", "K2" });
using (var r = ChoCSVReader<StudentInfo2>.LoadText(csv, config))
{
foreach (var rec in r)
{
Console.WriteLine(rec.Dump());
}
}
}
23.42. 读取动态记录时是否可以处理重复的名称?
如果 CSV 文件带有重复的列名,并且您希望解析器自动处理它们,方法是自动递增数字(例如 '_2'、'_3' 等)。是的,ChoCSVReader 会自动执行此操作。
对于下面的示例 CSV 文件
Id, Name, Name 1, Tom, Mark 2, Kevin, Fahey
下面的代码展示了如何将它们加载到动态记录中。
static void DuplicateNameInDynamicModeTest()
{
string csv = @"Id, Name, Name
1, Tom, Mark
2, Kevin, Fahey";
using (var r = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.AutoIncrementDuplicateColumnNames()
.ArrayIndexSeparator('_')
)
{
foreach (var rec in r)
Console.WriteLine(rec.Dump());
}
}
在此,CSV 文件包含 2 列同名。通过使用 AutoIncrementDuplicateColumnNames
指导 ChoCSVReader,您可以成功加载此文件。动态对象将具有 Id、Name、Name_2
作为其成员,并包含相应的值。
23.43. ChoCSVReader
能自动查找 CSV 文件中的分隔符吗?
是的,它确实可以。下面的示例展示了如何操作。
static void AutoDetectDelimiter()
{
string csv = @"Id;Guid
10;cc6f0116-589a-4cf1-8605-a4eb6ab3bd34
20;cc6f0116-589a-4cf1-8605-a4eb6ab3bd34
";
using (var r = ChoCSVReader.LoadText(csv)
.WithFirstLineHeader()
.WithMaxScanRows(1)
.AutoDetectDelimiter()
)
{
foreach (var rec in r)
Console.WriteLine(rec.Dump());
}
}
通过设置 `AutoDetectDelimiter
` 并结合 `WithMaxScanRows
`,您可以指示 ChoCSVReader 自动扫描并检测 CSV 分隔符。
23. ChoTSVReader
ChoTSVReader 是 CSV 读取器的专用版本,用于解析 TSV(制表符分隔)文件。
static void TSV2Xml()
{
string tsv = @"Time Object pmPdDrb pmPdcDlSrb
00:45 EUtranCellFDD=GNL02294_7A_1 2588007 1626
00:45 EUtranCellFDD=GNL02294_7B_1 18550 32
00:45 EUtranCellFDD=GNL02294_7C_1 26199 38
00:45 EUtranCellFDD=GNL02294_9A_1 3857243 751";
StringBuilder xml = new StringBuilder();
using (var r = ChoTSVReader.LoadText(tsv)
.WithFirstLineHeader()
)
{
using (var w = new ChoXmlWriter(xml)
.Configure(c => c.RootName = "xmlnodes")
.Configure(c => c.NodeName = "xmlnode")
)
w.Write(r);
}
Console.WriteLine(xml.ToString());
}
上面的示例展示了如何将 TSV 格式的文件解析为 XML 输出。
25. 参考
以下是 Cinchoo ETL 提供的其他模块列表,供您参考。
1. CSV 读取器/写入器
2. 定长读取器/写入器
3. XML 读取器/写入器
4. JSON 读取器/写入器
- JSON 读取器
- JSON 写入器 - 即将推出
5. 键值对读取器/写入器
- 键值对读取器 - 即将推出
- 键值对写入器 - 即将推出
6. Manifold 读取器/写入器
- Manifold 读取器 - 即将推出
- Manifold 写入器 - 即将推出
有关 Cinchoo ETL 的更多信息,请访问其他 CodeProject 文章。
查找现有问题/在新问题中打开