使用C#读取CSV文件


169

我正在编写一个简单的导入应用程序,需要读取CSV文件,在中显示结果,DataGrid并在另一个网格中显示CSV文件的损坏行。例如,在另一个网格中显示短于5个值的线。我正在尝试这样做:

StreamReader sr = new StreamReader(FilePath);
importingData = new Account();
string line;
string[] row = new string [5];
while ((line = sr.ReadLine()) != null)
{
    row = line.Split(',');

    importingData.Add(new Transaction
    {
        Date = DateTime.Parse(row[0]),
        Reference = row[1],
        Description = row[2],
        Amount = decimal.Parse(row[3]),
        Category = (Category)Enum.Parse(typeof(Category), row[4])
    });
}

但是在这种情况下很难对数组进行操作。有没有更好的方法来分割值?


感谢您的解决方案。考虑将其发布为答案,将其包含在问题中不利于其可读性。
BartoszKP 2015年

Answers:


363

不要重新发明轮子。利用.NET BCL中已有的功能。

  • 添加对的引用Microsoft.VisualBasic(是的,它说是VisualBasic,但它也可以在C#中工作-请记住,最后都只是IL)
  • 使用Microsoft.VisualBasic.FileIO.TextFieldParser该类来解析CSV文件

这是示例代码:

using (TextFieldParser parser = new TextFieldParser(@"c:\temp\test.csv"))
{
    parser.TextFieldType = FieldType.Delimited;
    parser.SetDelimiters(",");
    while (!parser.EndOfData) 
    {
        //Processing row
        string[] fields = parser.ReadFields();
        foreach (string field in fields) 
        {
            //TODO: Process field
        }
    }
}

在我的C#项目中,它对我来说很棒。

以下是一些其他链接/信息:


18
我真的希望有一种不使用VB库的方法,但是这种方法非常有效!谢谢!
gillonba '02

5
+1:我刚刚在一个53Mb的文件上损坏了lumenworks Fast CSV阅读器。看起来行缓存在43,000行之后失败,并扰乱了缓冲区。尝试了VB TextFieldParser并成功了。谢谢
Gone Coding

10
+1很好的答案,因为我发现很多人都不知道该课程的存在。将来的查看者需要注意的一件事是,parser.TextFieldType = FieldType.Delimited;如果您调用parser.SetDelimiters(",");,则不需要进行设置,因为该方法可以TextFieldType为您设置属性。
Brian

10
还请检查以下内容:dotnetperls.com/textfieldparser。TextFieldParser的性能比String.Split和StreamReader差。但是,string.Split和TextFieldParser之间有很大的区别。TextFieldParser处理诸如在列中包含逗号之类的奇怪情况:您可以将列命名为"text with quote"", and comma",并且可以获得正确的值text with quote", and comma而不是错误地将值分开。因此,如果您的csv非常简单,则可能要选择String.Split。
雍永

5
请注意,您可能需要添加对Microsoft.VisualBasic的引用才能使用它。右键单击Visual Studio中的项目,然后选择“添加”>“引用”,然后选中“ Microsoft.VisualBasic”框。
Derek Kurth 2015年

37

我的经验是,有很多不同的csv格式。特别是它们如何处理字段中的引号和定界符转义。

这些是我遇到的变体:

  • 用引号引起来并加倍(excel),即15“-> field1,” 15“”“,field3
  • 除非由于其他原因对该字段加引号,否则引号不会更改。即15“-> field1,15”,fields3
  • 引号用\进行转义。即15“-> field1,” 15 \“”,field3
  • 引号根本没有改变(这并非总是可能正确解析)
  • 引号分隔符(excel)。即a,b-> field1,“ a,b”,field3
  • 分隔符使用\进行转义。即a,b-> field1,a \,b,field3

我已经尝试了许多现有的csv解析器,但是没有一个可以处理我遇到的变体。从文档中很难找到解析器支持的转义变种。

在我的项目中,我现在使用VB TextFieldParser或自定义拆分器。


1
喜欢您提供的测试用例的答案!
马修·罗达图斯

2
主要问题是,大多数实现都不关心描述CSV格式的RFC 4180以及应如何转义分隔符。
珍妮·奥雷利

RFC-4180是2005年的产品,现在看来已经很旧了,但是请记住:.Net框架最早是2001年问世的。而且,RFC并不总是正式的标准,在这种情况下,它的权重与,ISO-8601或RFC-761。
乔尔·科恩荷恩

23

我推荐Nuget的CsvHelper

(添加对Microsoft.VisualBasic的引用感觉并不正确,不仅丑陋,而且甚至可能不是跨平台的。)


2
它与C#一样完全跨平台。
PRMan

错误的是,Linux中的Microsoft.VisualBasic.dll来自Mono来源,其实现与Microsoft的实现不同,并且有些未实现,例如:stackoverflow.com/questions/6644165/…–
knocte

(此外,VB语言从未参与过创建/开发Mono项目的公司,因此与C#生态系统/工具相比,它在工作方面落后了。)
knocte

1
两者都玩过之后,我会CsvHelper在类映射器中添加一个内置行;它允许列标题(如果存在)中的变化,甚至允许列顺序中的变化(尽管我自己尚未测试后者)。总而言之,感觉比“高级”要多得多TextFieldParser
大卫,

1
是的,Microsoft
.VisualBasic

13

有时,当您不想重新发明轮子时,使用库很酷,但是在这种情况下,与使用库相比,使用更少的代码行就能完成相同的工作,并且更易于阅读。这是另一种方法,我发现它非常易于使用。

  1. 在此示例中,我使用StreamReader读取文件
  2. 正则表达式可检测每行中的定界符。
  3. 一个数组,用于收集从索引0到n的列

using (StreamReader reader = new StreamReader(fileName))
    {
        string line; 

        while ((line = reader.ReadLine()) != null)
        {
            //Define pattern
            Regex CSVParser = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");

            //Separating columns to array
            string[] X = CSVParser.Split(line);

            /* Do something with X */
        }
    }

4
数据本身包含换行符肯定有问题吗?
Doogal '16

现在不知道CSV数据文件在数据之间包含空行,但是如果您有这样做的源,在那种情况下,我将简单地做一个简单的正则表达式测试,以在运行阅读器之前删除空格或不包含任何内容的行。点击这里针对不同的例子:stackoverflow.com/questions/7647716/...
法力

1
对于此类问题,基于字符的方法肯定比正则表达式更自然。根据引号的存在,行为可能有所不同。
凯西

6

CSV可以得到复杂的真正快。

使用功能强大且经过良好测试的工具:
FileHelpers: www.filehelpers.net

FileHelpers是一个免费且易于使用的.NET库,用于从文件,字符串或流中的固定长度或定界记录中导入/导出数据。


5
我认为FileHelper试图一劳永逸。解析文件是一个两步过程,在此过程中,您首先将行拆分为字段,然后将这些字段解析为数据。组合功能使处理主细节和行过滤之类的事情变得困难。
adrianm


4

此列表中的另一个Cinchoo ETL一个用于读取和写入CSV文件的开源库

对于下面的示例CSV文件

Id, Name
1, Tom
2, Mark

您可以使用以下库快速加载它们

using (var reader = new ChoCSVReader("test.csv").WithFirstLineHeader())
{
   foreach (dynamic item in reader)
   {
      Console.WriteLine(item.Id);
      Console.WriteLine(item.Name);
   }
}

如果您的POCO类与CSV文件匹配

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

您可以使用它来加载CSV文件,如下所示

using (var reader = new ChoCSVReader<Employee>("test.csv").WithFirstLineHeader())
{
   foreach (var item in reader)
   {
      Console.WriteLine(item.Id);
      Console.WriteLine(item.Name);
   }
}

请查看CodeProject上有关如何使用它的文章。

免责声明:我是这个图书馆的作者


嗨,您可以将CSV加载到Sql表中吗?我不知道CSV表中的标头是否在手。只需将csv中的内容镜像到Sql表
Aggie

是的你可以。请参阅此链接stackoverflow.com/questions/20759302/…–
RajN

2
private static DataTable ConvertCSVtoDataTable(string strFilePath)
        {
            DataTable dt = new DataTable();
            using (StreamReader sr = new StreamReader(strFilePath))
            {
                string[] headers = sr.ReadLine().Split(',');
                foreach (string header in headers)
                {
                    dt.Columns.Add(header);
                }
                while (!sr.EndOfStream)
                {
                    string[] rows = sr.ReadLine().Split(',');
                    DataRow dr = dt.NewRow();
                    for (int i = 0; i < headers.Length; i++)
                    {
                        dr[i] = rows[i];
                    }
                    dt.Rows.Add(dr);
                }

            }

            return dt;
        }

        private static void WriteToDb(DataTable dt)
        {
            string connectionString =
                "Data Source=localhost;" +
                "Initial Catalog=Northwind;" +
                "Integrated Security=SSPI;";

            using (SqlConnection con = new SqlConnection(connectionString))
                {
                    using (SqlCommand cmd = new SqlCommand("spInsertTest", con))
                    {
                        cmd.CommandType = CommandType.StoredProcedure;

                        cmd.Parameters.Add("@policyID", SqlDbType.Int).Value = 12;
                        cmd.Parameters.Add("@statecode", SqlDbType.VarChar).Value = "blagh2";
                        cmd.Parameters.Add("@county", SqlDbType.VarChar).Value = "blagh3";

                        con.Open();
                        cmd.ExecuteNonQuery();
                    }
                }

         }

您从哪里复制了此解决方案?
MindRoasterMir

0

首先需要了解什么是CSV以及如何编写。

  1. 每个下一个字符串(/r/n)是下一个“表格”行。
  2. “表格”单元格由一些定界符分隔。最常用的符号是\t,
  3. 每个单元格都可能包含此定界符(在这种情况下,单元格必须以引号开头并以该符号结尾)
  4. 每个单元格可能包含 /r/n sybol(在这种情况下,单元格必须以引号符号开头,并以该符号结尾)

C#/ Visual Basic处理CSV文件最简单的方法是使用标准Microsoft.VisualBasic库。您只需要在类中添加所需的引用和以下字符串:

using Microsoft.VisualBasic.FileIO;

是的,您可以在C#中使用它,不用担心。该库可以读取相对较大的文件,并支持所有必需的规则,因此您将能够使用所有CSV文件。

前一段时间,我已经基于该库编写了用于CSV读/写的简单类。使用这个简单的类,您将可以像处理2维数组一样使用CSV。您可以通过以下链接找到我的课程:https : //github.com/ukushu/DataExporter

使用的简单示例:

Csv csv = new Csv("\t");//delimiter symbol

csv.FileOpen("c:\\file1.csv");

var row1Cell6Value = csv.Rows[0][5];

csv.AddRow("asdf","asdffffff","5")

csv.FileSave("c:\\file2.csv");

0

要完成前面的答案,可能需要从CSV文件中收集对象(由TextFieldParserstring.Split方法解析),然后通过反射将每一行转换为对象。显然,您首先需要定义一个与CSV文件的各行匹配的类。

我使用了来自Michael Kropat的简单CSV序列化器,该代码位于:通用类为CSV(所有属性) ,并重用了他的方法来获取所需类的字段和属性。

我使用以下方法反序列化CSV文件:

public static IEnumerable<T> ReadCsvFileTextFieldParser<T>(string fileFullPath, string delimiter = ";") where T : new()
{
    if (!File.Exists(fileFullPath))
    {
        return null;
    }

    var list = new List<T>();
    var csvFields = GetAllFieldOfClass<T>();
    var fieldDict = new Dictionary<int, MemberInfo>();

    using (TextFieldParser parser = new TextFieldParser(fileFullPath))
    {
        parser.SetDelimiters(delimiter);

        bool headerParsed = false;

        while (!parser.EndOfData)
        {
            //Processing row
            string[] rowFields = parser.ReadFields();
            if (!headerParsed)
            {
                for (int i = 0; i < rowFields.Length; i++)
                {
                    // First row shall be the header!
                    var csvField = csvFields.Where(f => f.Name == rowFields[i]).FirstOrDefault();
                    if (csvField != null)
                    {
                        fieldDict.Add(i, csvField);
                    }
                }
                headerParsed = true;
            }
            else
            {
                T newObj = new T();
                for (int i = 0; i < rowFields.Length; i++)
                {
                    var csvFied = fieldDict[i];
                    var record = rowFields[i];

                    if (csvFied is FieldInfo)
                    {
                        ((FieldInfo)csvFied).SetValue(newObj, record);
                    }
                    else if (csvFied is PropertyInfo)
                    {
                        var pi = (PropertyInfo)csvFied;
                        pi.SetValue(newObj, Convert.ChangeType(record, pi.PropertyType), null);
                    }
                    else
                    {
                        throw new Exception("Unhandled case.");
                    }
                }
                if (newObj != null)
                {
                    list.Add(newObj);
                }
            }
        }
    }
    return list;
}

public static IEnumerable<MemberInfo> GetAllFieldOfClass<T>()
{
    return
        from mi in typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
        where new[] { MemberTypes.Field, MemberTypes.Property }.Contains(mi.MemberType)
        let orderAttr = (ColumnOrderAttribute)Attribute.GetCustomAttribute(mi, typeof(ColumnOrderAttribute))
        orderby orderAttr == null ? int.MaxValue : orderAttr.Order, mi.Name
        select mi;            
}

0

我强烈建议使用CsvHelper。

这是一个简单的例子:

public class csvExampleClass
{
    public string Id { get; set; }
    public string Firstname { get; set; }
    public string Lastname { get; set; }
}

var items = DeserializeCsvFile<List<csvExampleClass>>( csvText );

public static List<T> DeserializeCsvFile<T>(string text)
{
    CsvReader csv = new CsvReader( new StringReader( text ) );
    csv.Configuration.Delimiter = ",";
    csv.Configuration.HeaderValidated = null;
    csv.Configuration.MissingFieldFound = null;
    return (List<T>)csv.GetRecords<T>();
}

完整的文档可以在以下网址找到:https : //joshclose.github.io/CsvHelper

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.