[英]Import CSV file to strongly typed data structure in .Net [closed]
将 CSV 文件导入强类型数据结构的最佳方法是什么?
Microsoft 的TextFieldParser是稳定的并且遵循RFC 4180的 CSV 文件。 不要被Microsoft.VisualBasic
命名空间所拖延; 它是 .NET Framework 中的标准组件,只需添加对全局Microsoft.VisualBasic
程序集的引用即可。
如果您正在为 Windows 编译(而不是 Mono)并且不需要解析“损坏的”(非 RFC 兼容)CSV 文件,那么这将是显而易见的选择,因为它是免费的、不受限制的、稳定的,并积极支持,其中大部分不能说 FileHelpers。
另请参阅:如何:在 Visual Basic 中从逗号分隔的文本文件中读取VB 代码示例。
查看FileHelpers开源库 。
使用 OleDB 连接。
String sConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\InputDirectory\\;Extended Properties='text;HDR=Yes;FMT=Delimited'";
OleDbConnection objConn = new OleDbConnection(sConnectionString);
objConn.Open();
DataTable dt = new DataTable();
OleDbCommand objCmdSelect = new OleDbCommand("SELECT * FROM file.csv", objConn);
OleDbDataAdapter objAdapter1 = new OleDbDataAdapter();
objAdapter1.SelectCommand = objCmdSelect;
objAdapter1.Fill(dt);
objConn.Close();
如果您期待 CSV 解析相当复杂的场景,甚至不要考虑滚动我们自己的解析器。 有很多优秀的工具,比如FileHelpers ,甚至来自CodeProject 的工具。
关键是这是一个相当普遍的问题,您可以打赌,很多软件开发人员已经考虑并解决了这个问题。
我同意@NotMyself 。 FileHelpers已经过充分测试,可以处理如果您自己动手,最终将不得不处理的各种边缘情况。 看看 FileHelpers 做了什么,并且只有在您绝对确定(1)您永远不需要处理 FileHelpers 所做的边缘情况,或者(2)您喜欢编写此类东西并且将要编写自己的文件时才编写自己的当你必须解析这样的东西时会欣喜若狂:
1、“比尔”、“史密斯”、“主管”、“无可奉告”
2、'德雷克'、'奥马利'、“看门人”
哎呀,我没有被引用,我在一个新的行!
Brian 提供了一个很好的解决方案,可以将其转换为强类型集合。
给出的大多数 CSV 解析方法都没有考虑转义字段或 CSV 文件的其他一些细微之处(如修剪字段)。 这是我个人使用的代码。 它的边缘有点粗糙,几乎没有错误报告。
public static IList<IList<string>> Parse(string content)
{
IList<IList<string>> records = new List<IList<string>>();
StringReader stringReader = new StringReader(content);
bool inQoutedString = false;
IList<string> record = new List<string>();
StringBuilder fieldBuilder = new StringBuilder();
while (stringReader.Peek() != -1)
{
char readChar = (char)stringReader.Read();
if (readChar == '\n' || (readChar == '\r' && stringReader.Peek() == '\n'))
{
// If it's a \r\n combo consume the \n part and throw it away.
if (readChar == '\r')
{
stringReader.Read();
}
if (inQoutedString)
{
if (readChar == '\r')
{
fieldBuilder.Append('\r');
}
fieldBuilder.Append('\n');
}
else
{
record.Add(fieldBuilder.ToString().TrimEnd());
fieldBuilder = new StringBuilder();
records.Add(record);
record = new List<string>();
inQoutedString = false;
}
}
else if (fieldBuilder.Length == 0 && !inQoutedString)
{
if (char.IsWhiteSpace(readChar))
{
// Ignore leading whitespace
}
else if (readChar == '"')
{
inQoutedString = true;
}
else if (readChar == ',')
{
record.Add(fieldBuilder.ToString().TrimEnd());
fieldBuilder = new StringBuilder();
}
else
{
fieldBuilder.Append(readChar);
}
}
else if (readChar == ',')
{
if (inQoutedString)
{
fieldBuilder.Append(',');
}
else
{
record.Add(fieldBuilder.ToString().TrimEnd());
fieldBuilder = new StringBuilder();
}
}
else if (readChar == '"')
{
if (inQoutedString)
{
if (stringReader.Peek() == '"')
{
stringReader.Read();
fieldBuilder.Append('"');
}
else
{
inQoutedString = false;
}
}
else
{
fieldBuilder.Append(readChar);
}
}
else
{
fieldBuilder.Append(readChar);
}
}
record.Add(fieldBuilder.ToString().TrimEnd());
records.Add(record);
return records;
}
请注意,这不会处理未被双引号分隔的字段的边缘情况,但 meerley 内部有一个带引号的字符串。 请参阅这篇文章以获得更好的解释以及一些适当库的链接。
我很无聊所以我修改了一些我写的东西。 它尝试以面向对象的方式封装解析,同时减少通过文件的迭代量,它只在顶部 foreach 迭代一次。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// usage:
// note this wont run as getting streams is not Implemented
// but will get you started
CSVFileParser fileParser = new CSVFileParser();
// TO Do: configure fileparser
PersonParser personParser = new PersonParser(fileParser);
List<Person> persons = new List<Person>();
// if the file is large and there is a good way to limit
// without having to reparse the whole file you can use a
// linq query if you desire
foreach (Person person in personParser.GetPersons())
{
persons.Add(person);
}
// now we have a list of Person objects
}
}
public abstract class CSVParser
{
protected String[] deliniators = { "," };
protected internal IEnumerable<String[]> GetRecords()
{
Stream stream = GetStream();
StreamReader reader = new StreamReader(stream);
String[] aRecord;
while (!reader.EndOfStream)
{
aRecord = reader.ReadLine().Split(deliniators,
StringSplitOptions.None);
yield return aRecord;
}
}
protected abstract Stream GetStream();
}
public class CSVFileParser : CSVParser
{
// to do: add logic to get a stream from a file
protected override Stream GetStream()
{
throw new NotImplementedException();
}
}
public class CSVWebParser : CSVParser
{
// to do: add logic to get a stream from a web request
protected override Stream GetStream()
{
throw new NotImplementedException();
}
}
public class Person
{
public String Name { get; set; }
public String Address { get; set; }
public DateTime DOB { get; set; }
}
public class PersonParser
{
public PersonParser(CSVParser parser)
{
this.Parser = parser;
}
public CSVParser Parser { get; set; }
public IEnumerable<Person> GetPersons()
{
foreach (String[] record in this.Parser.GetRecords())
{
yield return new Person()
{
Name = record[0],
Address = record[1],
DOB = DateTime.Parse(record[2]),
};
}
}
}
}
CodeProject 上有两篇文章提供了解决方案的代码,一篇使用StreamReader ,另一篇使用Microsoft Text Driver导入 CSV 数据。
一个很好的简单方法是打开文件,并将每一行读入一个数组、链表、您选择的数据结构。 不过要小心处理第一行。
这可能超出您的想象,但似乎也有一种直接的方法可以使用连接字符串来访问它们。
为什么不尝试使用 Python 而不是 C# 或 VB? 它有一个很好的 CSV 模块可以导入,可以为您完成所有繁重的工作。
今年夏天,我不得不在 .NET 中为一个项目使用 CSV 解析器,并选择了 Microsoft Jet Text Driver。 您使用连接字符串指定文件夹,然后使用 SQL Select 语句查询文件。 您可以使用 schema.ini 文件指定强类型。 一开始我没有这样做,但后来我得到了糟糕的结果,其中数据的类型不是很明显,例如 IP 号码或像“XYQ 3.9 SP1”这样的条目。
我遇到的一个限制是它不能处理超过 64 个字符的列名; 它截断。 这应该不是问题,除非我正在处理设计非常糟糕的输入数据。 它返回一个 ADO.NET 数据集。
这是我找到的最好的解决方案。 我会谨慎使用自己的 CSV 解析器,因为我可能会错过一些最终情况,而且我没有找到任何其他免费的 .NET CSV 解析包。
编辑:此外,每个目录只能有一个 schema.ini 文件,因此我动态附加到它以强类型所需的列。 它只会强类型指定的列,并推断任何未指定的字段。 我真的很感激这一点,因为我正在处理导入一个 70+ 列的 CSV 并且不想指定每一列,只指定行为不端的列。
我输入了一些代码。 datagridviewer 中的结果看起来不错。 它将单行文本解析为对象数组列表。
enum quotestatus
{
none,
firstquote,
secondquote
}
public static System.Collections.ArrayList Parse(string line,string delimiter)
{
System.Collections.ArrayList ar = new System.Collections.ArrayList();
StringBuilder field = new StringBuilder();
quotestatus status = quotestatus.none;
foreach (char ch in line.ToCharArray())
{
string chOmsch = "char";
if (ch == Convert.ToChar(delimiter))
{
if (status== quotestatus.firstquote)
{
chOmsch = "char";
}
else
{
chOmsch = "delimiter";
}
}
if (ch == Convert.ToChar(34))
{
chOmsch = "quotes";
if (status == quotestatus.firstquote)
{
status = quotestatus.secondquote;
}
if (status == quotestatus.none )
{
status = quotestatus.firstquote;
}
}
switch (chOmsch)
{
case "char":
field.Append(ch);
break;
case "delimiter":
ar.Add(field.ToString());
field.Clear();
break;
case "quotes":
if (status==quotestatus.firstquote)
{
field.Clear();
}
if (status== quotestatus.secondquote)
{
status =quotestatus.none;
}
break;
}
}
if (field.Length != 0)
{
ar.Add(field.ToString());
}
return ar;
}
如果您可以保证数据中没有逗号,那么最简单的方法可能是使用String.split 。
例如:
String[] values = myString.Split(',');
myObject.StringField = values[0];
myObject.IntField = Int32.Parse(values[1]);
可能有一些库可以提供帮助,但这可能很简单。 只要确保数据中不能有逗号,否则您将需要更好地解析它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.