LINQ to CSV 库






4.97/5 (214投票s)
易于使用的库,可使用 LINQ 查询处理 CSV 和制表符分隔的文件。
目录
引言
此库可轻松地将 CSV 文件与 LINQ 查询结合使用。其功能包括:
- 遵循 最常见的 CSV 文件规则。正确处理包含逗号和换行符的数据字段。
- 除了逗号,还可以使用大多数分隔符,包括用于制表符分隔字段的制表符。
- 可与匿名类的
IEnumarable
一起使用,这通常是 LINQ 查询返回的结果。 - 支持延迟读取。
- 支持处理具有国际日期和数字格式的文件。
- 支持不同的字符编码(如果需要)。
- 读取文件时可识别各种日期和数字格式。
- 写入文件时可精细控制日期和数字格式。
- 强大的错误处理功能,可帮助您快速查找和修复大型输入文件中的问题。
要求
- 要编译该库,您需要 C# 2010 编译器或更高版本,例如 Visual Studio 2010 或 Visual C# 2010 Express Edition。
- 要运行库代码,您需要安装 .NET 4.0 框架。
安装
只需安装 NuGet 包。
快速入门
从文件读取
- 在您的项目中,添加对您在 安装过程中生成的 LINQtoCSV.dll 的引用。
- 文件将被读取到一个
IEnumerable<T>
中,其中T
是您定义的数据类。从文件中读取的数据记录将存储在此数据类的对象中。您可以定义类似如下的数据类:using LINQtoCSV; using System; class Product { [CsvColumn(Name = "ProductName", FieldIndex = 1)] public string Name { get; set; } [CsvColumn(FieldIndex = 2, OutputFormat = "dd MMM HH:mm:ss")] public DateTime LaunchDate { get; set; } [CsvColumn(FieldIndex = 3, CanBeNull = false, OutputFormat = "C")] public decimal Price { get; set; } [CsvColumn(FieldIndex = 4)] public string Country { get; set; } [CsvColumn(FieldIndex = 5)] public string Description { get; set; } }
使用此定义,您可以读取到
IEnumerable<Product>
中。尽管此示例仅使用属性,但库方法也能识别简单的字段。只需确保您的字段/属性是公共的。
可选的
CsvColumn
属性允许您指定字段/属性是否必需,以及它应如何写入输出文件等。完整详细信息请参见 此处。 - 在您将要读取文件的源文件顶部导入
LINQtoCSV
命名空间。using LINQtoCSV;
- 创建一个
CsvFileDescription
对象,并使用有关您将要读取的文件的详细信息进行初始化。它将如下所示:CsvFileDescription inputFileDescription = new CsvFileDescription { SeparatorChar = ',', FirstLineHasColumnNames = true };
这允许您指定用于分隔数据字段的字符(逗号、制表符等)、文件中的第一条记录是否包含列名,以及更多内容(完整详细信息)。
- 创建一个
CsvContext
对象。CsvContext cc = new CsvContext();
该对象公开了您将用于读取和写入文件的
Read
和Write
方法。 - 使用
CsvContext
对象的Read
方法将文件读取到IEnumerable<T>
中,如下所示:IEnumerable<Product> products = cc.Read<Product>("products.csv", inputFileDescription);
这将文件 products.csv 读取到变量
products
中,该变量的类型为IEnumerable<Product>
。 - 您现在可以通过 LINQ 查询、
foreach
循环等方式访问products
。var productsByName = from p in products orderby p.Name select new { p.Name, p.LaunchDate, p.Price, p.Description }; // or ... foreach (Product item in products) { .... }
为了更方便地进行概览,这里再次显示了从文件读取的代码,但现在是一次性完成的:
CsvFileDescription inputFileDescription = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = true
};
CsvContext cc = new CsvContext();
IEnumerable<Product> products =
cc.Read<Product>("products.csv", inputFileDescription);
// Data is now available via variable products.
var productsByName =
from p in products
orderby p.Name
select new { p.Name, p.LaunchDate, p.Price, p.Description };
// or ...
foreach (Product item in products) { .... }
您将在 源代码的 SampleCode 项目中找到相同的代码。
写入文件
这与 读取文件非常相似。
- 在您的项目中,添加对 LINQtoCSV.dll 的引用。
Write
方法接受一个IEnumerable<T>
,并将IEnumerable<T>
中的每个 T 类型对象作为数据记录写入文件。您的数据类的定义可以如下所示:using LINQtoCSV; using System; class Product { [CsvColumn(Name = "ProductName", FieldIndex = 1)] public string Name { get; set; } [CsvColumn(FieldIndex = 2, OutputFormat = "dd MMM HH:mm:ss")] public DateTime LaunchDate { get; set; } [CsvColumn(FieldIndex = 3, CanBeNull = false, OutputFormat = "C")] public decimal Price { get; set; } [CsvColumn(FieldIndex = 4)] public string Country { get; set; } [CsvColumn(FieldIndex = 5)] public string Description { get; set; } }
可选的
CsvColumn
属性允许您指定诸如日期和数字字段的输出格式等内容。所有 CsvColumn 属性(CanBeNull
、OutputFormat
等)的详细信息可在此 处找到。尽管此示例仅使用属性,但您也可以使用简单的字段。
Write
方法可以愉快地使用匿名类型作为T
,因此您可以将 LINQ 查询的输出直接写入文件。在这种情况下,您显然不会自己定义T
。稍后您将看到一个此类型的示例。- 在您将要写入文件的源文件顶部导入
LINQtoCSV
命名空间。using LINQtoCSV;
- 确保数据存储在实现了
IEnumerable<T>
的对象中,例如List<T>
,或者Read
方法返回的IEnumerable<T>
。List<Product> products2 = new List<Product>(); // Fill the list with products // ...
- 创建一个
CsvFileDescription
对象,并使用关于您将要写入的文件的详细信息对其进行初始化,如下所示:CsvFileDescription outputFileDescription = new CsvFileDescription { SeparatorChar = '\t', // tab delimited FirstLineHasColumnNames = false, // no column names in first record FileCultureName = "nl-NL" // use formats used in The Netherlands };
- 创建一个
CsvContext
对象。CsvContext cc = new CsvContext();
- 调用
CsvContext
对象公开的Write
方法将IEnumerable<T>
的内容写入文件:cc.Write( products2, "products2.csv", outputFileDescription);
这将变量
products2
中的Product
对象写入文件 "products2.csv"。
这里再次显示了写入文件的代码,但现在是一次性完成的:
List<Product> products2 = new List<Product>();
// Fill the list with products
// ...
CsvFileDescription outputFileDescription = new CsvFileDescription
{
SeparatorChar = '\t', // tab delimited
FirstLineHasColumnNames = false, // no column names in first record
FileCultureName = "nl-NL" // use formats used in The Netherlands
};
CsvContext cc = new CsvContext();
cc.Write(
products2,
"products2.csv",
outputFileDescription);
写入匿名类型的 IEnumerable
如果您有一个 LINQ 查询生成匿名类型的 IEnumerable
,将该 IEnumerable
写入文件不成问题。
CsvFileDescription outputFileDescription = new CsvFileDescription
{
.....
};
CsvContext cc = new CsvContext();
// LINQ query returning IEnumerable of anonymous type
// into productsNetherlands
var productsNetherlands =
from p in products
where p.Country == "Netherlands"
select new { p.Name, p.LaunchDate, p.Price, p.Description };
// Write contents of productsNetherlands to file
cc.Write(
productsNetherlands,
"products-Netherlands.csv",
outputFileDescription);
这里,一个 LINQ 查询从变量 products
中选择“Netherlands”的所有产品,并返回一个 IEnumerable
,其中包含具有 Name
、LaunchDate
、Price
和 Description
字段的匿名类型的对象。然后,Write
方法将这些对象写入文件 products-Netherlands.csv。
CsvContext.Write 重载
Write<T>(IEnumerable<T> values, string fileName)
Write<T>(IEnumerable<T> values, string fileName, CsvFileDescription fileDescription)
Write<T>(IEnumerable<T> values, TextWriter stream)
Write<T>(IEnumerable<T> values, TextWriter stream, CsvFileDescription fileDescription)
关于这些重载的一些有趣的事实:
- 没有一个重载返回值。
- 与
Read
方法不同,Write
不需要T
具有无参构造函数。 - 接受流的重载会将数据写入流。接受文件名作为参数的重载会将数据写入文件。
- 不接受
CsvFileDescription
对象的重载会自行创建一个,并使用CsvFileDescription
属性的默认值。
CsvContext.Read 重载
Read<T>(string fileName)
Read<T>(string fileName, CsvFileDescription fileDescription)
Read<T>(StreamReader stream)
Read<T>(StreamReader stream, CsvFileDescription fileDescription)
关于这些重载的一些有趣的事实:
- 每个重载都返回一个
IEnumerable<T>
。 T
必须有一个无参构造函数。如果您不为T
定义构造函数,编译器会为您生成一个无参构造函数。- 接受流的重载从流中读取数据。接受文件名的重载从文件中读取数据。但是,请参阅 延迟读取部分。
- 不接受
CsvFileDescription
对象的重载会自行创建一个,并使用CsvFileDescription
属性的默认值。
读取原始数据行
有时,直接读取 CSV 文件中的原始数据字段比让库处理它们要容易。例如,如果不同的行具有不同的格式,或者您在编译时不知道哪个字段将包含什么数据。
您可以通过让您的类型 T 实现 IDataRow
接口来实现这一点。该接口包含在库中,因此您不必自己编写。它本质上只是描述了一个 DataRowItem
对象集合。
public interface IDataRow
{
// Number of data row items in the row.
int Count { get; }
// Clear the collection of data row items.
void Clear();
// Add a data row item to the collection.
void Add(DataRowItem item);
// Allows you to access each data row item with an array index, such as
// row[i]
DataRowItem this[int index] { get; set; }
}
DataRowItem
类也定义在库中。它描述了数据行中的每个单独字段。
public class DataRowItem
{
...
// Line number of the field
public int LineNbr { get { ... } }
// Value of the field
public string Value { get { ... } }
}
行号包含在 DataRowItem
类中,因为数据行可能跨越多行。
创建实现 IDataRow
的类的最简单方法是继承自 List<DataRowItem>
。
using LINQtoCSV;
internal class MyDataRow : List<DataRowItem>, IDataRow
{
}
现在,您可以将 CSV 文件读取到 MyDataRow
对象集合中。
IEnumerable<MyDataRow> products =
cc.Read<MyDataRow>("products.csv", inputFileDescription);
然后,您可以访问每个数据行中的每个单独字段。
foreach (MyDataRow dataRow in products)
{
string firstFieldValue = dataRow[0].Value;
int firstFieldLineNbr = dataRow[0].LineNbr;
string secondFieldValue = dataRow[1].Value;
int secondFieldLineNbr = dataRow[1].LineNbr;
...
}
延迟读取
以下是 Read
重载如何实现延迟读取:
- 当您调用
Read
方法(它返回一个IEnumerable<T>
)时,还没有读取任何数据。如果是从文件读取,文件尚未打开。 - 当从
IEnumerable<T>
获取枚举器时(例如,当开始foreach
循环时),文件将打开以供读取。如果是从流读取,流将被倒回(定位到流的开头)。 - 每次从枚举器获取新对象时(例如,在
foreach
循环中),都会从文件或流中读取一条新记录。 - 当您关闭枚举器时(例如,当
foreach
循环结束或您跳出循环时),文件将被关闭。如果是从流读取,流将保持不变。
这意味着:
- 如果是从文件读取,则在
foreach
循环中访问IEnumerable<T>
时,文件将保持打开状态。 - 可以在访问之间更新文件。您可以在
foreach
循环中访问IEnumerable<T>
,然后更新文件,然后再在foreach
循环中访问IEnumerable<T>
以获取新数据,依此类推。您只需在开始时调用一次Read
即可获取IEnumerable<T>
。
CsvFileDescription
Read
和 Write
方法需要有关它们正在读取或写入的文件的一些详细信息,例如第一条记录是否包含列名。
如 从文件读取和 写入文件 示例所示,您将这些详细信息放入一个 CsvFileDescription
类型的对象中,然后将其传递给 Read
或 Write
方法。这可以避免冗长的参数列表,并允许您为多个文件使用相同的详细信息。
CsvFileDescription
对象具有以下属性:
SeparatorChar
QuoteAllFields
FirstLineHasColumnNames
EnforceCsvColumnAttribute
FileCultureName
TextEncoding
DetectEncodingFromByteOrderMarks
MaximumNbrExceptions
NoSeparatorChar
UseFieldIndexForReadingData
IgnoreTrailingSeparatorChar
IgnoreUnknownColumns
SeparatorChar
类型 | char |
默认值 | ',' |
适用于 | 读取和写入 |
示例
CsvFileDescription fd = new CsvFileDescription();
fd.SeparatorChar = '\t'; // use tab delimited file
CsvContext cc = new CsvContext();
cc.Write(data, "file.csv", fd);
用于分隔文件中字段的字符。对于 CSV 文件,这是逗号;对于制表符分隔文件,这是 '\t'。
您可以使用任何您喜欢的字符,但空格字符或双引号(")除外。
QuoteAllFields
类型 | bool |
默认值 | false |
适用于 | 仅写入 |
示例
fd.QuoteAllFields = true; // forces quotes around all fields
当 false
时,Write
仅在必要时才为数据字段加上引号,以避免混淆,例如,当字段包含 SeparatorChar
或换行符时。
当 true
时,Write
会用引号将所有数据字段括起来。
FirstLineHasColumnNames
类型 | bool |
默认值 | true |
适用于 | 读取和写入 |
示例
fd.FirstLineHasColumnNames = false; // first record does not have column headers
读取文件时,指示 Read
是否将文件中的第一条记录中的数据字段解释为列标题。
写入文件时,指示 Write
是否将列标题作为文件的第一条记录写入。
EnforceCsvColumnAttribute
类型 | bool |
默认值 | false |
适用于 | 读取和写入 |
示例
fd.EnforceCsvColumnAttribute = true; // only use fields with [CsvColumn] attribute
当 true
时,Read
只会将数据字段读取到具有 [CsvColumn]
属性的公共字段和属性中,忽略所有其他字段和属性。并且,Write
只会将具有 [CsvColumn]
属性的公共字段和属性的内容写入文件。
当 false
时,使用所有公共字段和属性。
FileCultureName
类型 | 字符串 |
默认值 | 当前系统设置 |
适用于 | 读取和写入 |
示例
fd.FileCultureName = "en-US"; // use US style dates and numbers
不同的文化使用不同的日期和数字书写方式。2008 年 5 月 23 日在美国 (en-US) 是 5/23/2008,在德国 (de-DE) 是 23/5/2008。使用 FileCultureName
字段来告诉 Read
如何解释它从文件中读取的日期和数字,并告诉 Write
如何将日期和数字写入文件。
默认情况下,该库使用您系统上的当前语言/国家/地区设置。因此,如果您的系统使用法语-加拿大 (fr-CA),则该库将使用该文化,除非您使用 FileCultureName
覆盖它。
该库使用的文化名称与 .NET 的“CultureInfo
”类相同(完整名称列表)。
TextEncoding
类型 | 编码 |
默认值 | Encoding.UTF8 |
适用于 | 读取和写入 |
示例
fd.TextEncoding = Encoding.Unicode; // use Unicode character encoding
如果读取或写入的文件是英文的,则无需设置 TextEncoding
。
但是,如果您使用的语言不是英语,您文件中的字符编码方式可能会成为一个问题。您需要确保库使用的编码与任何其他访问您文件的程序(编辑器、电子表格)使用的编码相匹配。
特别是,如果您写入包含欧元符号的文件,您可能需要使用 Unicode 编码,如示例所示。
DetectEncodingFromByteOrderMarks
类型 | bool |
默认值 | true |
适用于 | 仅读取 |
示例
fd.DetectEncodingFromByteOrderMarks = false; // suppress encoding detection
与 TextEncoding
相关。默认情况下通常工作良好。
指示 Read
是否通过查看文件的前三个字节来检测输入文件的编码。否则,它将使用 TextEncoding
属性中指定的编码。
MaximumNbrExceptions
类型 | int |
默认值 | 100 |
适用于 | 仅读取 |
示例
fd.MaximumNbrExceptions = -1; // always read entire file before throwing AggregatedException
设置将聚合到 AggregatedException
中的最大异常数。
要没有限制并读取整个文件,无论您遇到多少异常,请将 AggregatedException
设置为 -1。
有关聚合异常的详细信息,请参阅 错误处理 部分。
NoSeparatorChar
类型 | bool |
默认值 | false |
适用于 | 读取 |
示例
fd.NoSeparatorChar = true; // Fields are fixed width
当 CSV 文件使用固定宽度字段而不是分隔符字符时,将此设置为 true。
字符数使用 CharLength 属性在 CsvColumnAttribute
类中指定。
UseFieldIndexForReadingData
类型 | bool |
默认值 | false |
适用于 | 仅读取 |
示例
fd.UseFieldIndexForReadingData = true;
修改 CsvColumnAttribute
类的 FieldIndex 属性的行为,使其适用于固定宽度字段。有关详细信息,请参阅 FieldIndex 属性的说明。
IgnoreTrailingSeparatorChar
类型 | bool |
默认值 | false |
适用于 | 仅读取 |
示例
fd.IgnoreTrailingSeparatorChar = true; // ignore separator character at the end of the line
考虑以下文件:
column1;column2;column3;
尽管这不是 CSV 文件的典型表示,但 IgnoreTrailingSeparatorChar
属性会指示 Read
忽略行末的分隔符字符。
IgnoreUnknownColumns
类型 | bool |
默认值 | false |
适用于 | 仅读取 |
示例
有时您不需要读取所有列,只需要其中的一部分。考虑以下包含人员列表的 CSV 文件示例:
ID | 名称 | 姓氏 | 年龄 | 城市 |
---|---|---|---|---|
1 | John | Doe | 15 | Washington |
2 | Jane | Doe | 20 | New York |
假设您有以下类:
class Person
{
[CsvColumn(Name = "Name")]
public string Name { get ; set; }
[CsvColumn(Name = "Last Name")]
public string LastName { get; set; }
[CsvColumn(Name = "Age")]
public int Age { get; set; }
}
请注意,输入文件有“Id”和“City”列,而这些列未在类中列出。这种差异通常会导致异常。
但是,如果您设置:
fd.IgnoreUnknownColumns = true;
那么“Id”和“City”列将被忽略,而不会发生异常。
CsvColumn 属性
如 从文件读取 和 写入文件 示例所示,您可以使用 CsvColumn
属性修饰您的数据类的公共字段和属性,以指定诸如日期和数字字段的输出格式等内容。
CsvColumn
属性的使用是可选的。只要您传递给 Read
或 Write
的 CsvFileDescription
对象的 EnforceCsvColumnAttribute
属性为 false
,这些方法就会查看数据类中的所有公共字段和属性。然后,它们将简单地使用下面每个 CsvColumn
属性所示的默认值。
CsvColumn
属性具有以下属性:
名称
类型 | 字符串 |
默认值 | 字段或属性的名称 |
适用于 | 读取和写入 |
示例
[CsvColumn(Name = "StartDate")]
public DateTime LaunchDate { get; set; }
Read
和 Write
方法通常假设文件中的数据字段与类中的相应字段或属性同名。使用 Name
属性可为数据字段指定其他名称。
CanBeNull
类型 | bool |
默认值 | true |
适用于 | 仅读取 |
[CsvColumn(CanBeNull = false)]
public DateTime LaunchDate { get; set; }
如果为 false
,并且输入文件中的记录对此字段或属性没有值,则 Read
方法会生成一个 MissingRequiredFieldException
异常。
FieldIndex
类型 | bool |
默认值 | Int32.MaxValue |
适用于 | 仅读取 |
示例
[CsvColumn(FieldIndex = 1)]
public DateTime LaunchDate { get; set; }
此属性用于读取和写入,但方式略有不同。
读取 - Read
方法需要某种方式将输入文件中的数据字段与数据类中的字段和属性关联起来。如果文件在第一条记录中有列名,这很容易 - Read
会将列名与数据类中的字段和属性名称进行匹配。
但是,如果文件在第一条记录中没有列名,Read
需要查看数据记录中数据字段的顺序,以便将它们与数据类中的字段和属性进行匹配。不幸的是,.NET 框架没有提供一种可靠的方法从类定义中检索该顺序。因此,您必须通过为字段和属性提供带有 FieldIndex
属性的 CsvColumn
属性来指定哪个字段/属性排在哪个字段/属性之前。
FieldIndex
不必从 1 开始。它们不必是连续的。Read
和 Write
方法将简单地假设某个字段/属性排在另一个字段/属性之前,如果它的 FieldIndex
更低。
当 CsvFileDescription 类的 UseFieldIndexForReadingData 属性为 true 时,FieldIndex
指定值开始的索引。这是基于 1 的,因此如果您设置 FieldIndex 为 3,则值从第 3 个字符开始。
写入 - Write
方法使用每个字段或属性的 FieldIndex
来确定将数据字段写入输出文件的顺序。没有 FieldIndex
的字段和属性将最后写入,顺序随机。
CharLength
类型 | int |
默认值 | 0 |
适用于 | 仅读取 |
示例
[CsvColumn(CharLength=12)]
public string Name { get; set; }
允许您指定 Name 字段在数据行项中有一个占用数据行 12 个字符的项。
与...结合使用
UseFieldIndexForReadingData = true
NumberStyle
类型 | NumberStyles |
默认值 | NumberStyles.Any |
适用于 | 仅读取数字字段 |
示例
[CsvColumn(NumberStyle = NumberStyles.HexNumber)]
public DateTime LaunchDate { get; set; }
允许您确定输入文件允许哪些数字样式(选项列表)。
默认情况下,允许所有样式,但有一个特殊情况。为了接受不以 0x 开头的十六进制数,请使用 NumberStyles.HexNumber
,如示例所示。
OutputFormat
类型 | 字符串 |
默认值 | "G" |
适用于 | 仅写入 |
示例
[CsvColumn(OutputFormat = "dd MMM yy")]
public DateTime LaunchDate { get; set; }
允许您设置数字和日期/时间的输出格式。默认的“G”格式通常对日期和数字都很好。
写入日期/时间或数字字段时,Write
方法首先确定字段的类型(DateTime
、decimal
、double
等),然后使用给定的 OutputFormat
调用该类型的 ToString
方法。因此,在上面的示例中,如果 LaunchDate
是 2008 年 11 月 23 日,写入文件中的字段将是“23 Nov 08”。
对于许多格式,最终结果取决于文件的语言/国家/地区,如 CsvFileDescription
对象中的 FileCultureName
属性所设置。因此,如果 LaunchDate
是 2008 年 11 月 23 日,并且您指定了短日期格式:
[CsvColumn(OutputFormat = "d")]
public DateTime LaunchDate { get; set; }
那么,如果使用美国的日期(FileCultureName
设置为“en-US”),写入输出文件的最终值将是“11/23/08”;如果使用德国的日期(FileCultureName
设置为“de-DE”),则为“23/11/08”。
错误处理
- 异常
LINQtoCSVException
BadStreamException
CsvColumnAttributeRequiredException
DuplicateFieldIndexException
RequiredButMissingFieldIndexException
ToBeWrittenButMissingFieldIndexException
NameNotInTypeException
MissingCsvColumnAttributeException
TooManyDataFieldsException
TooManyNonCsvColumnDataFieldsException
MissingFieldIndexException
MissingRequiredFieldException
WrongDataFormatException
AggregatedException
当 Read
和 Write
方法检测到错误情况时,它们会抛出一个异常,其中包含解决问题所需的所有信息。正如您所料,所有异常都派生自 .NET 类 Exception
。
检索错误信息
除了 StackTrace
和 Message
等属性外,Exception
类还公开了 Data
属性。Read
和 Write
方法使用该属性以易于代码读取的方式提供异常信息,同时它们通过 Message
属性提供面向人类的错误消息。
每个异常的描述(如下文)显示了存储在 Data
属性中的信息。
聚合异常
当 Read
方法在读取文件数据时检测到错误,它不会立即抛出异常,而是将其存储在 List<Exception>
类型的列表中。然后,在处理完文件后,它会抛出一个 AggregatedException
类型的异常,其 Data["InnerExceptionsList"]
属性中包含异常列表。这允许您一次性修复输入文件中的所有问题,而不是一个接一个地修复。
您可以通过设置传递给 Read
方法的 CsvFileDescription
对象的 MaximumNbrExceptions
属性来限制可以这样聚合的异常数量。默认情况下,MaximumNbrExceptions
设置为 100。当达到限制时,AggregatedException
会立即抛出,其中包含迄今为止聚合的异常列表。
并非所有异常都会被聚合!在 Read
开始从文件读取数据之前,它会先处理列名、CsvColumn
属性等。如果在该初步阶段出现问题,它会立即抛出异常。
延迟读取
请记住,由于 延迟读取,您不仅可以在调用 Read
方法时,还可以在访问 Read
方法返回的 IEnumerable<T>
时收到异常。
示例
以下代码读取文件并处理异常。为了展示如何使用 Data
属性,它包含了一些针对 DuplicateFieldIndexException
的特殊处理 - 当 Read
和 Write
方法检测到两个具有相同 FieldIndex
的字段或属性时抛出。
public static void ShowErrorMessage(string errorMessage)
{
// show errorMessage to user
// .....
}
public static void ReadFileWithExceptionHandling()
{
try
{
CsvContext cc = new CsvContext();
CsvFileDescription inputFileDescription = new CsvFileDescription
{
MaximumNbrExceptions = 50
// limit number of aggregated exceptions to 50
};
IEnumerable<Product> products =
cc.Read<Product>("products.csv", inputFileDescription);
// Do data processing
// ...........
}
catch(AggregatedException ae)
{
// Process all exceptions generated while processing the file
List<Exception> innerExceptionsList =
(List<Exception>)ae.Data["InnerExceptionsList"];
foreach (Exception e in innerExceptionsList)
{
ShowErrorMessage(e.Message);
}
}
catch(DuplicateFieldIndexException dfie)
{
// name of the class used with the Read method - in this case "Product"
string typeName = Convert.ToString(dfie.Data["TypeName"]);
// Names of the two fields or properties that have the same FieldIndex
string fieldName = Convert.ToString(dfie.Data["FieldName"]);
string fieldName2 = Convert.ToString(dfie.Data["FieldName2"]);
// Actual FieldIndex that the two fields have in common
int commonFieldIndex = Convert.ToInt32(dfie.Data["Index"]);
// Do some processing with this information
// .........
// Inform user of error situation
ShowErrorMessage(dfie.Message);
}
catch(Exception e)
{
ShowErrorMessage(e.Message);
}
}
BadStreamException
此异常公开的属性与 Exception
相同。
当将一个 null
或不支持 Seek
的流传递给 Read
时抛出。流必须支持 Seek
,否则在访问 Read
返回的 IEnumarable
时无法将其倒回。
CsvColumnAttributeRequiredException
此异常公开的属性与 Exception
相同。
当传递给 Read
的 CsvFileDescription
对象同时将 FirstLineHasColumnNames
和 EnforceCsvColumnAttribute
设置为 false
时抛出。
如果文件中没有列名,则 Read
依赖数据类中每个字段或属性的 FieldIndex
来将其与文件中的数据字段进行匹配。但是,如果 EnforceCsvColumnAttribute
为 false
,则意味着没有 CsvColumn
属性的字段或属性也可以用于接受数据,而它们没有 FieldIndex
。
DuplicateFieldIndexException
其他属性 - 此异常公开的属性与 Exception
相同,另外还有以下附加属性:
属性 | 类型 | 描述 |
Data["TypeName"] |
字符串 |
具有错误字段/属性的类的名称 |
Data["FieldName"] |
字符串 |
具有重复 FieldIndex 的字段或属性 |
Data["FieldName2"] |
||
Data["Index"] |
int |
通用 FieldIndex |
当两个或多个字段或属性具有相同的 FieldIndex
时抛出。
RequiredButMissingFieldIndexException
其他属性 - 此异常公开的属性与 Exception
相同,另外还有以下附加属性:
属性 | 类型 | 描述 |
Data["TypeName"] |
字符串 |
具有错误字段/属性的类的名称 |
Data["FieldName"] |
字符串 |
没有 FieldIndex 的字段或属性 |
当文件第一条记录中没有列名时(FirstLineHasColumnNames
为 false
),每个必需字段(CanBeNull
属性设置为 false
)都必须具有 FieldIndex
属性,否则无法从文件读取。
ToBeWrittenButMissingFieldIndexException
其他属性 - 此异常公开的属性与 Exception
相同,另外还有以下附加属性:
属性 | 类型 | 描述 |
Data["TypeName"] |
字符串 |
具有错误字段/属性的类的名称 |
Data["FieldName"] |
字符串 |
没有 FieldIndex 的字段或属性 |
写入不包含列名的文件时,您需要确保数据字段在每行中的顺序是明确定义的。如果顺序是随机的,那么其他程序将无法可靠地处理文件。
因此,当 Write
方法给出 FirstLineHasColumnNames
为 false
的 CsvFileDescription
,并且找到没有 FieldIndex
的字段或属性时,它会抛出 ToBeWrittenButMissingFieldIndexException
。
NameNotInTypeException
其他属性 - 此异常公开的属性与 Exception
相同,另外还有以下附加属性:
属性 | 类型 | 描述 |
Data["TypeName"] |
字符串 |
缺少字段/属性的类的名称 |
Data["FieldName"] |
字符串 |
未找到的字段或属性 |
Data["FileName"] |
字符串 |
输入文件名 |
如果 Read
方法收到一个 FirstLineHasColumnNames
为 true
的 CsvFileDescription
,并且文件第一条记录中的某个列名与字段或属性不匹配,它会抛出 NameNotInTypeException
。
MissingCsvColumnAttributeException
其他属性 - 此异常公开的属性与 Exception
相同,另外还有以下附加属性:
属性 | 类型 | 描述 |
Data["TypeName"] |
字符串 |
具有错误字段/属性的类的名称 |
Data["FieldName"] |
字符串 |
没有 CsvColumn 属性的字段或属性 |
Data["FileName"] |
字符串 |
输入文件名 |
当 Read
方法收到一个 FirstLineHasColumnNames
和 EnforceCsvColumnAttribute
都为 true
的 CsvFileDescription
时,可能会抛出此异常。当 Read
从第一条记录读取列名时,其中一个列名可能与没有 CsvColumn
属性的字段或属性匹配,尽管只有具有 CsvColumn
属性的字段和属性才能使用。发生这种情况时,Read
会抛出 MissingCsvColumnAttributeException
。
TooManyDataFieldsException
其他属性 - 此异常公开的属性与 Exception
相同,另外还有以下附加属性:
属性 | 类型 | 描述 |
Data["TypeName"] |
字符串 |
数据类的名称 |
Data["LineNbr"] |
int |
输入文件中具有过多数据字段的行号 |
Data["FileName"] |
字符串 |
输入文件名 |
当输入文件中的记录包含比数据类中的公共字段和属性更多的数据字段时抛出。
TooManyNonCsvColumnDataFieldsException
其他属性 - 此异常公开的属性与 Exception
相同,另外还有以下附加属性:
属性 | 类型 | 描述 |
Data["TypeName"] |
字符串 |
数据类的名称 |
Data["LineNbr"] |
int |
输入文件中具有过多数据字段的行号 |
Data["FileName"] |
字符串 |
输入文件名 |
当只使用具有 CsvColumn
属性的字段或属性时(Read
收到 EnforceCsvColumnAttribute
为 true
的 CsvFileDescription
),并且输入文件中的记录包含比具有 CsvColumn
属性的字段和属性更多的数据字段时,会抛出 TooManyNonCsvColumnDataFieldsException
。
MissingFieldIndexException
其他属性 - 此异常公开的属性与 Exception
相同,另外还有以下附加属性:
属性 | 类型 | 描述 |
Data["TypeName"] |
字符串 |
数据类的名称 |
Data["LineNbr"] |
int |
包含错误字段的行 |
Data["FileName"] |
字符串 |
输入文件名 |
如果输入文件第一条记录中没有列名(Read
收到 FirstLineHasColumnNames
为 false
的 CsvFileDescription
),则 Read
依赖数据类中字段和属性的 FieldIndex
来将它们与文件中的数据字段进行匹配。
当输入文件中的记录包含比数据类中具有 FieldIndex
的字段和属性更多的数据字段时,会抛出 MissingFieldIndexException
。
MissingRequiredFieldException
其他属性 - 此异常公开的属性与 Exception
相同,另外还有以下附加属性:
属性 | 类型 | 描述 |
Data["TypeName"] |
字符串 |
具有必需字段/属性的类的名称 |
Data["FieldName"] |
字符串 |
必需字段/属性的名称 |
Data["LineNbr"] |
int |
应包含缺失字段的行 |
Data["FileName"] |
字符串 |
输入文件名 |
当输入文件中的记录缺少必需字段或属性的值时抛出(CsvColumn
属性的 CanBeNull
属性设置为 false
)。
null 和空字符串的区别
空字符串和仅包含空格的字符串需要用引号括起来,以便它们被识别为非 null
。
这些输入行都有数据字段“abc”、null 和“def”:
abc,,def
abc, ,def
而这一行有数据字段“abc”,后跟空字符串,再后跟“def”:
abc,"",def
这一行有数据字段“abc”,后跟一个包含三个空格的字符串,再后跟“def”:
abc," ",def
WrongDataFormatException
其他属性 - 此异常公开的属性与 Exception
相同,另外还有以下附加属性:
属性 | 类型 | 描述 |
Data["TypeName"] |
字符串 |
包含字段/属性的类的名称 |
Data["FieldName"] |
字符串 |
字段/属性的名称 |
Data["FieldValue"] |
字符串 |
引起错误的数值 |
Data["LineNbr"] |
int |
包含引起错误数值的行 |
Data["FileName"] |
字符串 |
输入文件名 |
当字段格式错误时抛出。例如,一个名为“abc”的数字字段。
AggregatedException
其他属性 - 此异常公开的属性与 Exception
相同,另外还有以下附加属性:
属性 | 类型 | 描述 |
Data["TypeName"] |
字符串 |
Read 使用的数据类的名称 |
Data["FileName"] |
字符串 |
输入文件名 |
Data["InnerExceptionsList"] |
List<Exception> |
异常列表 |
用于聚合读取文件时生成的异常(更多详情)。
历史
版本 | 发布日期 | 描述 |
---|---|---|
1.0 | 2008 年 4 月 11 日 | 初始发布。 |
1.1 | 2011 年 9 月 19 日 | 添加了读取原始数据行的功能。 |
1.2 | 2012 年 2 月 18 日 | 错误修复 - 从流读取时,CsvFileDescription 现在得到正确处理。 |
1.3 | 2014 年 2 月 14 日 | 引入了固定宽度列。由 lvaleriu 提供。 |
1.4 | 2014 年 2 月 18 日 | 引入了忽略行尾分隔符字符的选项。由 Roman 提供。 |
1.5 | 2014 年 3 月 4 日 | 引入了忽略输入文件中未使用的列的选项。由 Oscar Mederos 提供。 |
新功能和错误修复
如果您发现了错误或有新功能想法,请随时为本项目做出贡献。详情:https://github.com/mperdeck/LINQtoCSV