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

LINQ to CSV 库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (214投票s)

2008 年 4 月 11 日

Apache

23分钟阅读

viewsIcon

1148022

downloadIcon

7351

易于使用的库,可使用 LINQ 查询处理 CSV 和制表符分隔的文件。

目录

引言

此库可轻松地将 CSV 文件与 LINQ 查询结合使用。其功能包括:

  • 遵循 最常见的 CSV 文件规则。正确处理包含逗号和换行符的数据字段。
  • 除了逗号,还可以使用大多数分隔符,包括用于制表符分隔字段的制表符。
  • 可与匿名类的 IEnumarable 一起使用,这通常是 LINQ 查询返回的结果。
  • 支持延迟读取。
  • 支持处理具有国际日期和数字格式的文件。
  • 支持不同的字符编码(如果需要)。
  • 读取文件时可识别各种日期和数字格式。
  • 写入文件时可精细控制日期和数字格式。
  • 强大的错误处理功能,可帮助您快速查找和修复大型输入文件中的问题。

要求

  • 要编译该库,您需要 C# 2010 编译器或更高版本,例如 Visual Studio 2010 或 Visual C# 2010 Express Edition。
  • 要运行库代码,您需要安装 .NET 4.0 框架。

安装

只需安装 NuGet 包

快速入门

从文件读取

  1. 在您的项目中,添加对您在 安装过程中生成的 LINQtoCSV.dll 的引用。
  2. 文件将被读取到一个 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 属性允许您指定字段/属性是否必需,以及它应如何写入输出文件等。完整详细信息请参见 此处

  3. 在您将要读取文件的源文件顶部导入 LINQtoCSV 命名空间。
    using LINQtoCSV;
  4. 创建一个 CsvFileDescription 对象,并使用有关您将要读取的文件的详细信息进行初始化。它将如下所示:
    CsvFileDescription inputFileDescription = new CsvFileDescription
    {
        SeparatorChar = ',', 
        FirstLineHasColumnNames = true
    };

    这允许您指定用于分隔数据字段的字符(逗号、制表符等)、文件中的第一条记录是否包含列名,以及更多内容(完整详细信息)。

  5. 创建一个 CsvContext 对象。
    CsvContext cc = new CsvContext();

    该对象公开了您将用于读取和写入文件的 ReadWrite 方法。

  6. 使用 CsvContext 对象的 Read 方法将文件读取到 IEnumerable<T> 中,如下所示:
    IEnumerable<Product> products =
        cc.Read<Product>("products.csv", inputFileDescription);

    这将文件 products.csv 读取到变量 products 中,该变量的类型为 IEnumerable<Product>

  7. 您现在可以通过 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 项目中找到相同的代码。

写入文件

这与 读取文件非常相似。

  1. 在您的项目中,添加对 LINQtoCSV.dll 的引用。
  2. 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 属性(CanBeNullOutputFormat 等)的详细信息可在此 找到。

    尽管此示例仅使用属性,但您也可以使用简单的字段。

    Write 方法可以愉快地使用匿名类型作为 T,因此您可以将 LINQ 查询的输出直接写入文件。在这种情况下,您显然不会自己定义 T稍后您将看到一个此类型的示例。

  3. 在您将要写入文件的源文件顶部导入 LINQtoCSV 命名空间。
    using LINQtoCSV;
  4. 确保数据存储在实现了 IEnumerable<T> 的对象中,例如 List<T>,或者 Read 方法返回的 IEnumerable<T>
    List<Product> products2 = new List<Product>();
    // Fill the list with products
    // ...
  5. 创建一个 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
    };
  6. 创建一个 CsvContext 对象。
    CsvContext cc = new CsvContext();
  7. 调用 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,其中包含具有 NameLaunchDatePriceDescription 字段的匿名类型的对象。然后,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

ReadWrite 方法需要有关它们正在读取或写入的文件的一些详细信息,例如第一条记录是否包含列名。

从文件读取写入文件 示例所示,您将这些详细信息放入一个 CsvFileDescription 类型的对象中,然后将其传递给 ReadWrite 方法。这可以避免冗长的参数列表,并允许您为多个文件使用相同的详细信息。

CsvFileDescription 对象具有以下属性:

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 属性的使用是可选的。只要您传递给 ReadWriteCsvFileDescription 对象的 EnforceCsvColumnAttribute 属性为 false,这些方法就会查看数据类中的所有公共字段和属性。然后,它们将简单地使用下面每个 CsvColumn 属性所示的默认值。

CsvColumn 属性具有以下属性:

名称

类型 字符串
默认值 字段或属性的名称
适用于 读取和写入

示例

[CsvColumn(Name = "StartDate")]
public DateTime LaunchDate { get; set; }

ReadWrite 方法通常假设文件中的数据字段与类中的相应字段或属性同名。使用 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 开始。它们不必是连续的。ReadWrite 方法将简单地假设某个字段/属性排在另一个字段/属性之前,如果它的 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 方法首先确定字段的类型(DateTimedecimaldouble 等),然后使用给定的 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”。

错误处理

ReadWrite 方法检测到错误情况时,它们会抛出一个异常,其中包含解决问题所需的所有信息。正如您所料,所有异常都派生自 .NET 类 Exception

检索错误信息

除了 StackTraceMessage 等属性外,Exception 类还公开了 Data 属性。ReadWrite 方法使用该属性以易于代码读取的方式提供异常信息,同时它们通过 Message 属性提供面向人类的错误消息。

每个异常的描述(如下文)显示了存储在 Data 属性中的信息。

聚合异常

Read 方法在读取文件数据时检测到错误,它不会立即抛出异常,而是将其存储在 List<Exception> 类型的列表中。然后,在处理完文件后,它会抛出一个 AggregatedException 类型的异常,其 Data["InnerExceptionsList"] 属性中包含异常列表。这允许您一次性修复输入文件中的所有问题,而不是一个接一个地修复。

您可以通过设置传递给 Read 方法的 CsvFileDescription 对象的 MaximumNbrExceptions 属性来限制可以这样聚合的异常数量。默认情况下,MaximumNbrExceptions 设置为 100。当达到限制时,AggregatedException 会立即抛出,其中包含迄今为止聚合的异常列表。

并非所有异常都会被聚合!在 Read 开始从文件读取数据之前,它会先处理列名、CsvColumn 属性等。如果在该初步阶段出现问题,它会立即抛出异常。

延迟读取

请记住,由于 延迟读取,您不仅可以在调用 Read 方法时,还可以在访问 Read 方法返回的 IEnumerable<T> 时收到异常。

示例

以下代码读取文件并处理异常。为了展示如何使用 Data 属性,它包含了一些针对 DuplicateFieldIndexException 的特殊处理 - 当 ReadWrite 方法检测到两个具有相同 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 相同。

当传递给 ReadCsvFileDescription 对象同时将 FirstLineHasColumnNamesEnforceCsvColumnAttribute 设置为 false 时抛出。

如果文件中没有列名,则 Read 依赖数据类中每个字段或属性的 FieldIndex 来将其与文件中的数据字段进行匹配。但是,如果 EnforceCsvColumnAttributefalse,则意味着没有 CsvColumn 属性的字段或属性也可以用于接受数据,而它们没有 FieldIndex

DuplicateFieldIndexException

其他属性 - 此异常公开的属性与 Exception 相同,另外还有以下附加属性:

属性 类型 描述
Data["TypeName"] 字符串 具有错误字段/属性的类的名称
Data["FieldName"] 字符串 具有重复 FieldIndex 的字段或属性
Data["FieldName2"]
Data["Index"] int 通用 FieldIndex

当两个或多个字段或属性具有相同的 FieldIndex 时抛出。

RequiredButMissingFieldIndexException

其他属性 - 此异常公开的属性与 Exception 相同,另外还有以下附加属性:

属性 类型 描述
Data["TypeName"] 字符串 具有错误字段/属性的类的名称
Data["FieldName"] 字符串 没有 FieldIndex 的字段或属性

当文件第一条记录中没有列名时(FirstLineHasColumnNamesfalse),每个必需字段(CanBeNull 属性设置为 false)都必须具有 FieldIndex 属性,否则无法从文件读取。

ToBeWrittenButMissingFieldIndexException

其他属性 - 此异常公开的属性与 Exception 相同,另外还有以下附加属性:

属性 类型 描述
Data["TypeName"] 字符串 具有错误字段/属性的类的名称
Data["FieldName"] 字符串 没有 FieldIndex 的字段或属性

写入不包含列名的文件时,您需要确保数据字段在每行中的顺序是明确定义的。如果顺序是随机的,那么其他程序将无法可靠地处理文件。

因此,当 Write 方法给出 FirstLineHasColumnNamesfalseCsvFileDescription,并且找到没有 FieldIndex 的字段或属性时,它会抛出 ToBeWrittenButMissingFieldIndexException

NameNotInTypeException

其他属性 - 此异常公开的属性与 Exception 相同,另外还有以下附加属性:

属性 类型 描述
Data["TypeName"] 字符串 缺少字段/属性的类的名称
Data["FieldName"] 字符串 未找到的字段或属性
Data["FileName"] 字符串 输入文件名

如果 Read 方法收到一个 FirstLineHasColumnNamestrueCsvFileDescription,并且文件第一条记录中的某个列名与字段或属性不匹配,它会抛出 NameNotInTypeException

MissingCsvColumnAttributeException

其他属性 - 此异常公开的属性与 Exception 相同,另外还有以下附加属性:

属性 类型 描述
Data["TypeName"] 字符串 具有错误字段/属性的类的名称
Data["FieldName"] 字符串 没有 CsvColumn 属性的字段或属性
Data["FileName"] 字符串 输入文件名

Read 方法收到一个 FirstLineHasColumnNamesEnforceCsvColumnAttribute 都为 trueCsvFileDescription 时,可能会抛出此异常。当 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 收到 EnforceCsvColumnAttributetrueCsvFileDescription),并且输入文件中的记录包含比具有 CsvColumn 属性的字段和属性更多的数据字段时,会抛出 TooManyNonCsvColumnDataFieldsException

MissingFieldIndexException

其他属性 - 此异常公开的属性与 Exception 相同,另外还有以下附加属性:

属性 类型 描述
Data["TypeName"] 字符串 数据类的名称
Data["LineNbr"] int 包含错误字段的行
Data["FileName"] 字符串 输入文件名

如果输入文件第一条记录中没有列名(Read 收到 FirstLineHasColumnNamesfalseCsvFileDescription),则 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

© . All rights reserved.