.Net中的CSV文件导入

我意识到这是一个新手问题,但我正在寻找一个简单的解决方案 - 似乎应该有一个。

将CSV文件导入强类型数据结构的最佳方式是什么?再简单=更好。

0
额外 编辑
意见: 2
考虑到这是比1103495早一年创建的,我认为这个问题是这个问题的重复。
额外 作者 MattH,
额外 作者 KMån,
额外 作者 Mark Meuer,
谢谢,马特。我只是试图将它们连接在一起,而不是指明哪一个先出现。你会看到我在这个问题上有完全相同的问题。是否有更好的方法将两个问题联系在一起?
额外 作者 Mark Meuer,

12 答案

如果您可以保证数据中没有逗号,那么最简单的方法可能是使用 String.split

例如:

String[] values = myString.Split(',');
myObject.StringField = values[0];
myObject.IntField = Int32.Parse(values[1]);

可能有些库可以用来提供帮助,但这可能很简单。只要确保数据中不能有逗号,否则您需要更好地解析它。

0
额外
内存使用情况非常糟糕,而且开销很大。小的应该少于几千字节。绝对不适合10mb csv!
额外 作者 ppumkin,
这不是一个最佳解决方案
额外 作者 roundcrisis,
这取决于你的内存和文件的大小。
额外 作者 tonymiao,

Brian提供了一个将其转换为强类型集合的完美解决方案。

大多数给出的CSV解析方法都没有考虑转义字段或CSV文件的其他细节(如修剪字段)。这是我个人使用的代码。它的边缘有点粗糙,几乎没有错误报告。

public static IList> Parse(string content)
{
    IList> records = new List>();

    StringReader stringReader = new StringReader(content);

    bool inQoutedString = false;
    IList record = new List();
    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();

                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在其中包含带引号的字符串。请参阅此帖以获得更好的扩展,以及一些链接一些合适的库。

0
额外

CodeProject上有两篇文章提供了一个解决方案的代码,一个使用 StreamReader导入CSV数据使用 Microsoft Text Driver

0
额外

如果您预计CSV解析相当复杂的场景,那么甚至不会考虑滚动我们自己的解析器。这里有很多优秀的工具,比如 FileHelpers ,甚至还有一些来自 CodeProject

关键是这是一个相当普遍的问题,你可以肯定很多软件开发人员已经考虑过并且解决了这个问题。

0
额外
谢谢@techspider我希望你确实注意到这篇文章来自StackOverflow的测试版:D现在说的CSV工具更好地源自Nuget包 - 所以我不确定链接答案是否可以免于8年技术的演化周期
额外 作者 Jon Limjap,
虽然这个链接可能回答这个问题,但最好在这里包含答案的重要部分,并提供供参考的链接。如果链接页面更改,则仅链接答案可能会失效。 - 来自评论
额外 作者 techspider,
0
额外
+1刚刚实现了这个...真棒
额外 作者 Miyagi Coder,
@dangph我不认为这是真的。 opensource.org/licenses/lgpl-2.1.php 指出“然而,链接一个“使用该库的作品”与该库创建一个可执行文件,该可执行文件是该库的一个衍生产品......因此可执行文件被本许可证所覆盖,第6节规定了这种可执行文件的分发条款。
额外 作者 RYFN,
@dangph我同意,我不知道该怎么做!
额外 作者 RYFN,
@约翰,你为什么这么说? LGPL不要求您发布任何代码,除非您修改库本身。 (在这种情况下,无论如何提交补丁都是有意义的。)
额外 作者 dan-gph,
@Zeus,我仍然认为你不必释放你的“使用图书馆的作品”的来源。您需要发布“目标代码和/或源代码”。我不确定在.Net环境中这意味着什么。但你是对的。第6节的要求非常繁琐。多么荒谬的许可证。
额外 作者 dan-gph,
FileHelpers的另一个问题是,自2007年以来,它的开发似乎完全停滞。不幸的是,它包含错误。 (可能对于简单的情况很好。)即使它是开源的,作者正在接受补丁也不清楚。
额外 作者 dan-gph,
不幸的是,这是LGPL,这在企业环境中并不理想......
额外 作者 John Weldon,
为了便于比较,NHibernate的也是LGPL,它已在无数的商业应用程序中使用。所以没有什么可担心的。
额外 作者 Mauricio Scheffer,
@dangph @Zeus @John @Martin Section 6b表示你可以“使用一个合适的共享库机制来与库链接。一个合适的机制是(1)在运行时使用一个已经存在于用户的计算机系统,而不是将库函数复制到可执行文件中,并且(2)如果用户安装了一个,那么只要修改后的版本与作品的版本是界面兼容的是用...做成的。“一个.Net DLL程序集符合这些标准,你的源代码不需要发布。
额外 作者 MarkJ,
事实证明,这个问题的合法性足以导致这场讨论可能会为很多人解决这个问题。这当然表明情况不确定。大多数人没有时间或金钱去获得“leagal团队”来验证情况。也就是说,除非你打算将这个库文库包含在一个包装解决方案的船舶上,以满足千人的需要,否则任何人都无法执行许可证。
额外 作者 Martin Brown,
FileHelpers主页说:“FileHelpers Library是@Copyright 2005-2006 Marcos Meli,但它的源代码和二进制文件是免费的商业和非商业用途。”
额外 作者 Carl Hörberg,

一个简单的方法是打开文件,并将每行读入数组,链接列表和数据结构。但要小心处理第一行。

这可能会超出你的想象,但似乎有一种直接的方式可以使用连接字符串

为什么不尝试使用Python而不是C#或VB?它有一个很好的CSV模块导入,为你做所有繁重的工作。

0
额外
为了CSV解析器的目的,不要从VB跳到Python。 VB中有一个。虽然很奇怪,但似乎在这个问题的答案中被忽略了。 msdn.microsoft.com/en-us/library/ …
额外 作者 MarkJ,

我同意@ NotMyselfFileHelpers 已经过很好的测试,可以处理各种边缘情况,如果您需要处理最终必须处理的情况它自己。看一看FileHelpers的功能,只有在你确信(1)你永远不需要处理FileHelpers所做的边缘案例时,或者(2)你喜欢写这种东西,并且将要当你必须解析像这样的东西时会大喜过望:

1,“比尔”,“史密斯”,“主管”,“无评论”

2,'德雷克','奥马利',“看门人,

哎呀,我没有被引用,我正在换一个新的行!

0
额外

我很无聊,所以我修改了一些我写的东西。它尝试以OO方式封装解析,并减少遍历文件的迭代次数,它只在顶级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 persons = new List();
            // 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 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 GetPersons()
        {
            foreach (String[] record in this.Parser.GetRecords())
            {
                yield return new Person()
                {
                    Name = record[0],
                    Address = record[1],
                    DOB = DateTime.Parse(record[2]),
                };
            }
        }
    }
}
0
额外

使用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();
0
额外
这需要文件系统访问。据我所知,没有办法使OLEDB与内存流:(
额外 作者 UserControl,
我没有抱怨。事实上,我更喜欢OLEDB解决方案,但在需要解析CSV应用程序中的CSV时,我非常沮丧,所以想要注意它。
额外 作者 UserControl,
@UserControl当然需要文件系统访问。他询问有关导入CSV文件的问题
额外 作者 Kevin,

微软的 TextFieldParser 是稳定的,并且遵循 RFC 4180 。不要被 Microsoft.VisualBasic 命名空间拖延;它是.NET Framework中的标准组件,只需添加对全局 Microsoft.VisualBasic 程序集的引用即可。

如果您正在编译Windows(而不是Mono),并且预计不需要解析“损坏”(不符合RFC的)CSV文件,那么这将是明显的选择,因为它是免费的,不受限制的,稳定的,并积极支持,其中大部分不能说FileHelpers。

另请参阅:如何:从Visual Basic中的逗号分隔文本文件读取为VB代码示例。

0
额外
TextFieldParser 也适用于制表符分隔的和其他奇怪的Excel生成的cruft。我意识到你之前的回答并不是声称该库是VB特定的,它只是在我看来,这意味着它确实对于VB来说意味着,而不是打算从C#中使用,我不认为是这种情况 - MSVB中有一些非常有用的类。
额外 作者 Aaronaught,
除了其不幸命名的命名空间之外,实际上没有关于这个类的VB特有的东西。如果我只需要一个“简单的”CSV解析器,我一定会选择这个库,因为没有什么可以下载,分发或担心的。为此,我编辑了这个答案中的VB重点短语。
额外 作者 Aaronaught,
@Aarounaught我认为你的编辑主要是一种改进。虽然该RFC不一定是权威的,但许多CSV编写者不符合它,例如Excel 在“CSV”文件中并不总是使用逗号。也没有我以前的答案已经表示,该类可以使用从C#?
额外 作者 MarkJ,

今年夏天,我不得不在.NET中使用CSV解析器进行项目,并在Microsoft Jet文本驱动程序中解决问题。使用连接字符串指定文件夹,然后使用SQL Select语句查询文件。您可以使用schema.ini文件指定强类型。起初我没有这样做,但后来我的数据类型不是很明显,例如IP编号或“XYQ 3.9 SP1”等条目,结果很糟糕。

我遇到的一个限制是它不能处理64个字符以上的列名;它截断。这不应该是一个问题,除非我正在处理设计非常糟糕的输入数据。它返回一个ADO.NET数据集。

这是我找到的最佳解决方案。我会警惕地推出我自己的CSV解析器,因为我可能会错过一些最终案例,并且我没有发现任何其他免费的CSV解析包。

编辑:另外,每个目录只能有一个schema.ini文件,所以我动态地附加到它以强烈键入所需的列。它只会强制键入指定的列,并推断任何未指定的字段。我非常赞赏这一点,因为我正在处理导入流体70+列CSV的问题,并且不想指定每一列,而仅仅是那些行为不当的列。

0
额外
为什么不用VB.NET构建CSV解析器? msdn.microsoft.com/en-us/library/ …
额外 作者 MarkJ,

我输入了一些代码。 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;
    }
0
额外