使用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]) }); }
但在这种情况下对arrays进行操作是非常困难的。 有没有更好的方法来分割值?
不要重新发明轮子。 利用.NET BCL中已有的function。
- 添加一个引用到
Microsoft.VisualBasic
(是的,它说的是VisualBasic,但它也适用于C# – 记住,最后它只是IL) - 使用
Microsoft.VisualBasic.FileIO.TextFieldParser
类来parsingCSV文件
这里是示例代码:
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#项目中非常适合我。
以下是一些链接/信息:
- MSDN:读取从Visual Basic中的逗号分隔文本文件
- MSDN:TextFieldParser类
我的经验是,有许多不同的CSV格式。 特别是他们如何处理字段中的引号和分隔符的转义。
这些是我遇到的变种:
- 引号引用并加倍(excel)即15“ – > field1,”15“”“,field3
- 报价不会改变,除非该字段是由于其他原因引用。 即15“ – > field1,15”,fields3
- 引号与\转义。 即15“ – > field1,”15 \“”,field3
- 引号不会改变(这不总是可以正确parsing)
- 分隔符被引用(excel)。 即a,b – > field1,“a,b”,field3
- 分隔符用\转义。 即a,b – > field1,a \,b,field3
我已经尝试了很多现有的csvparsing器,但没有一个可以处理我遇到的变体。 从文档中发现parsing器支持的转义变体也很困难。
在我的项目中,我现在使用VB TextFieldParser或自定义分离器。
我build议从Nuget的CsvHelper 。
(添加对Microsoft.VisualBasic的引用只是感觉不对,它不仅丑陋,甚至可能不是跨平台的。)
有时使用库很酷,当你不想重新发明轮子,但在这种情况下,与使用库相比,可以用更less的代码行来完成相同的工作,并且更容易阅读。 这是一个不同的方法,我觉得很容易使用。
- 在这个例子中,我使用StreamReader来读取文件
- 正则expression式来检测每行的分隔符。
- 一个数组,用于收集从索引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 */ } }
CSV可以快速复杂化。
使用一些健壮的和经过充分testing的:
FileHelpers: http://www.filehelpers.net
FileHelpers是一个免费且易于使用的.NET库,用于从文件,string或stream中的固定长度或分隔logging导入/导出数据。
另一个名单是Cinchoo ETL – 一个用于读取和写入CSV文件的开源库
using (var reader = new ChoCSVReader("test.csv").WithFirstLineHeader() .WithField('Field1') .WithField('Field2') ) { foreach (dynamic item in reader) { Console.WriteLine(item.Field1); Console.WriteLine(item.Field2); } }
请在CodeProject上查看如何使用它的文章。
免责声明:我是这个图书馆的作者
首先需要了解什么是CSV以及如何编写它。
- 每个下一个string(
/r/n
)是下一个“表”行。 - “表格”单元格由一些分隔符号分隔。 最经常使用的符号是
\t
或,
- 每个单元格都可能包含此分隔符号(单元格必须以引号符号开始,并以此符号结束)
- 每个单元格可能包含
/r/n
sybols(在这种情况下,单元格必须以引号符号开头并以此符号结尾)
C#/ Visual Basic处理CSV文件的最简单方法是使用标准的Microsoft.VisualBasic
库。 你只需要添加需要的引用,并将下面的string添加到你的类中:
using Microsoft.VisualBasic.FileIO;
是的,你可以在C#中使用它,不用担心。 这个库可以读取相对较大的文件并支持所有需要的规则,因此您可以使用所有的CSV文件。
前段时间我写了基于这个库的CSV读写简单类。 使用这个简单的类,你将能够像使用2维数组一样使用CSV。 您可以通过以下链接find我的课程: 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");
要完成以前的答案,可能需要从CSV文件中获取对象的集合,或者通过TextFieldParser
或string.Split
方法进行分析,然后通过Reflection将每行转换为对象。 您显然首先需要定义一个与CSV文件的行匹配的类。
我使用从这里find的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; }
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(); } } }