使用C#读取CSV文件

时间:2010-08-17 22:30:22

标签: c# csv

我正在编写一个简单的导入应用程序,需要读取一个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])
    });
}

但在这种情况下对阵列进行操作非常困难。有没有更好的方法来分割价值?

12 个答案:

答案 0 :(得分:334)

不要重新发明轮子。利用.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#项目中,它对我很有用。

以下是一些链接/信息:

答案 1 :(得分:31)

我的经验是有许多不同的csv格式。特别是它们如何处理字段内的引号和分隔符的转义。

这些是我遇到的变种:

    引用
  • 引号并加倍(excel),即15“ - > field1,”15“”“,field3
  • 引号不会更改,除非出于某些其他原因引用该字段。即15“ - > field1,15”,fields3
  • 引号用\ _进行转义。即15“ - > field1,”15 \“”,field3
  • 引号根本没有更改(这并不总是可以正确解析)
  • 引用
  • 分隔符(excel)。即a,b - > FIELD1, “A,B”,字段3
  • 分隔符用\来转义。即a,b - > FIELD1中,\,B,字段3

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

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

答案 2 :(得分:19)

我建议CsvHelper from Nuget

(添加对Microsoft.VisualBasic的引用感觉不对,它不仅难看,甚至可能不是跨平台的。)

答案 3 :(得分:11)

当您不想重新发明轮子时,有时使用库很酷,但在这种情况下,与使用库相比,可以用更少的代码行完成相同的工作并且更容易阅读。 这是一种我觉得很容易使用的不同方法。

  1. 在此示例中,我使用StreamReader读取文件
  2. 正则表达式检测每行的分隔符。
  3. 用于收集索引0到n
  4. 的列的数组
    <uses-permission android:name="android.permission.CALL_PHONE" />
    

答案 4 :(得分:6)

CSV可以快速复制真实

使用强大且经过充分测试的东西:
FileHelpers: www.filehelpers.net

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

答案 5 :(得分:3)

我在这里使用它:

http://www.codeproject.com/KB/database/GenericParser.aspx

上次我正在寻找这样的东西时,我发现它是对此question的回答。

答案 6 :(得分:3)

此列表中的另一个,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上有关如何使用文章的文章。

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

答案 7 :(得分:1)

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();
                    }
                }

         }

答案 8 :(得分:1)

这是我今天编写的解决方案,用于需要不依赖外部库而解析CSV的情况。我尚未测试大型文件的性能,因为它与我的特定用例无关,但我希望它在大多数情况下都能表现良好。

        static List<List<string>> ParseCsv(string csv) {
            var parsedCsv = new List<List<string>>();
            var row = new List<string>();
            string field = "";
            bool inQuotedField = false;

            for (int i = 0; i < csv.Length; i++) {
                char current = csv[i];
                char next = i == csv.Length - 1 ? ' ' : csv[i + 1];

                // if current character is not a quote or comma or carriage return or newline (or not a quote and currently in an a quoted field), just add the character to the current field text
                if ((current != '"' && current != ',' && current != '\r' && current != '\n') || (current != '"' && inQuotedField)) {
                    field += current;
                } else if (current == ' ' || current == '\t') {
                    continue; // ignore whitespace outside a quoted field
                } else if (current == '"') {
                    if (inQuotedField && next == '"') { // quote is escaping a quote within a quoted field
                        i++; // skip escaping quote
                        field += current;
                    } else if (inQuotedField) { // quote signifies the end of a quoted field
                        row.Add(field);
                        if (next == ',') {
                            i++; // skip the comma separator since we've already found the end of the field
                        }
                        field = "";
                        inQuotedField = false;
                    } else { // quote signifies the beginning of a quoted field
                        inQuotedField = true; 
                    }
                } else if (current == ',') { //
                    row.Add(field);
                    field = "";
                } else if (current == '\n') {
                    row.Add(field);
                    parsedCsv.Add(new List<string>(row));
                    field = "";
                    row.Clear();
                }
            }

            return parsedCsv;
        }

答案 9 :(得分:0)

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

我使用了Michael Kropat的简单CSV序列化程序:Generic class to CSV (all properties) 并重用他的方法来获得所希望的阶级的领域和属性。

我使用以下方法反序列化我的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;            
}

答案 10 :(得分:0)

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

  1. 每个下一个字符串(/r/n)都是下一个“table”行。
  2. “表格”单元格由一些分隔符号分隔。最常用的符号是\t,
  3. 每个单元格都可能包含此分隔符号(单元格必须以引号开头,在这种情况下以此符号结尾)
  4. 每个单元格可能包含/r/n个sybols(单元格必须以引号开头,在这种情况下以此符号结尾)
  5. 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");
    

答案 11 :(得分:0)

我强烈建议使用CsvHelper。

这是一个简单的示例:

Thumbnails.of(background).forceSize(1100, 500).watermark(Positions.CENTER, avatarPlusBorder, 1f).addFilter(filter)
                            .outputFormat("png").toOutputStream(os);

完整的文档可以在https://joshclose.github.io/CsvHelper

中找到。