【问题标题】:Import CSV file to strongly typed data structure in .Net [closed]将CSV文件导入.Net中的强类型数据结构[关闭]
【发布时间】:2010-09-05 08:08:08
【问题描述】:

将 CSV 文件导入强类型数据结构的最佳方法是什么?

【问题讨论】:

  • 考虑到这是比 1103495 早一年创建的,我认为这个问题与这个问题是重复的。
  • 谢谢,马特。我只是试图将它们联系在一起,而不是指出哪个先出现。你会看到我在指向这个问题的另一个问题上有完全相同的文字。有没有更好的方法将两个问题联系在一起?
  • Reading a CSV file in .NET? 是这个问题的公开副本

标签: c# vb.net file csv import


【解决方案1】:

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

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

另请参阅:How to: Read From Comma-Delimited Text Files in Visual Basic 以获得 VB 代码示例。

【讨论】:

  • 除了不幸命名的命名空间之外,这个类实际上没有任何 VB 特定的东西。如果我只需要一个“简单”的 CSV 解析器,我肯定会选择这个库,因为一般来说没有什么可下载、分发或担心的。为此,我从这个答案中编辑了以 VB 为重点的措辞。
  • @Aaronaught 我认为您的编辑主要是一种改进。尽管该 RFC 不一定具有权威性,但许多 CSV 编写者并不遵守它,例如“CSV”文件中的 Excel does not always use a comma。我之前的回答不是已经说过可以从 C# 中使用该类吗?
  • TextFieldParser 也适用于制表符分隔和其他奇怪的 Excel 生成的杂物。我意识到您之前的回答并没有声称该库是特定于 VB 的,它只是在我看来暗示它真的 意味着 用于 VB,而不是 有意 从 C# 中使用,我不认为是这种情况 - MSVB 中有一些非常有用的类。
【解决方案2】:

使用 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();

【讨论】:

  • 这需要文件系统访问权限。据我所知,没有办法让 OLEDB 与内存流一起工作:(
  • @UserControl,当然它需要文件系统访问权限。他询问有关导入 CSV 文件的问题
  • 我没有抱怨。事实上,我更喜欢 OLEDB 解决方案而不是其他解决方案,但是当需要在 ASP.NET 应用程序中解析 CSV 时,我多次感到沮丧,所以想记下它。
【解决方案3】:

如果您期望 CSV 解析的场景相当复杂,甚至不要考虑推出我们自己的解析器。那里有很多优秀的工具,比如 FileHelpers,甚至是来自 CodeProject 的工具。

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

【讨论】:

  • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review
  • 谢谢@techspider 我希望你注意到这篇文章来自 StackOverflow 的 beta 阶段:D 据说现在 CSV 工具更好地来自 Nuget 包——所以我不确定是否链接答案不受 8 年技术进化周期的影响
【解决方案4】:

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

【讨论】:

    【解决方案5】:

    我同意@NotMyselfFileHelpers 经过充分测试,可以处理各种边缘情况,如果您自己动手,您最终将不得不处理这些情况。看看 FileHelpers 做了什么,并且只有在您绝对确定以下两种情况之一时才编写自己的:(1)您永远不需要处理 FileHelpers 所做的边缘情况,或者(2)您喜欢编写这种东西并且会当你必须解析这样的东西时会大喜过望:

    1,"Bill","Smith","Supervisor","No Comment"

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

    糟糕,我没有被引用,我换了新行!

    【讨论】:

      【解决方案6】:

      我很无聊,所以我修改了一些我写的东西。它试图以 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<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]),
                      };
                  }
              }
          }
      }
      

      【讨论】:

        【解决方案7】:

        CodeProject 上有两篇文章为解决方案提供代码,一篇使用StreamReader,另一篇使用Microsoft Text Driverimports CSV data

        【讨论】:

          【解决方案8】:

          一个很好的简单方法是打开文件,并将每一行读入数组、链表、数据结构。不过要小心处理第一行。

          这可能会让您不知所措,但似乎也可以使用connection string 直接访问它们。

          为什么不尝试使用 Python 而不是 C# 或 VB?它有一个不错的 CSV 模块可以导入,可以为您完成所有繁重的工作。

          【讨论】:

          • 不要为了 CSV 解析器而从 VB 跳转到 python。 VB中有一个。尽管奇怪的是,它似乎在这个问题的答案中被忽略了。 msdn.microsoft.com/en-us/library/…
          【解决方案9】:

          今年夏天,我不得不在一个项目中使用 .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 并且不想指定每一列,只指定行为不端的列。

          【讨论】:

          【解决方案10】:

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

          【讨论】:

            【解决方案11】:

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

            例如:

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

            您可能会使用一些库来提供帮助,但这可能是您能得到的最简单的方法。只要确保数据中不能有逗号,否则您需要更好地解析它。

            【讨论】:

            • 这不是最佳解决方案
            • 内存使用非常糟糕,而且开销很大。小应该少谢几千字节。绝对不适合 10mb 的 csv!
            • 这取决于你的内存和文件的大小。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2011-06-16
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-12-21
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多