2008-08-05 61 views
97

我意識到這是一個新手問題,但我正在尋找一個簡單的解決方案 - 看起來應該有一個。.net中的CSV文件導入

將CSV文件導入強類型數據結構的最佳方式是什麼?再簡單=更好。

+0

這是http://stackoverflow.com/questions/1103495/is-there-a-proper-way-to-read-csv-files – 2011-02-18 16:46:53

+7

的副本考慮這是不是1103495創建上年同期,我認爲這個問題是這個問題的重複。 – MattH 2011-05-26 05:16:23

+2

謝謝,馬特。我只是試圖將它們連接在一起,而不是指明哪一個先出現。你會看到我在這個問題上有完全相同的問題。是否有更好的方法將兩個問題聯繫在一起? – 2011-05-27 13:48:28

回答

48
+1

不幸的是,這是LGPL,這是不理想的企業環境... – 2009-05-14 01:44:07

+5

@John,你爲什麼這麼說?LGPL不要求您發佈任何代碼,除非您修改庫本身。 (在這種情況下,無論如何提交補丁都是有意義的。) – 2009-08-24 06:19:43

+0

+1剛纔實現了這個...真棒 – 2010-02-03 20:30:00

2

一個很好的簡單方法是打開文件,並將每行讀入數組,鏈表,數據結構選擇。但要小心處理第一行。

這可能會在你的頭上,但似乎有一種直接的方式來訪問它們以及使用connection string

爲什麼不嘗試使用Python而不是C#或VB?它有一個很好的CSV模塊導入,爲你做所有繁重的工作。

0

如果您可以保證數據中沒有逗號,那麼最簡單的方法可能是使用String.split

例如:

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

有可能是你可以用它來幫助圖書館,但是這可能是因爲你可以得到簡單。只要確保數據中不能有逗號,否則您需要更好地解析它。

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]), 
       }; 
      } 
     } 
    } 
} 
9

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

注意這不會處理不被雙引號deliminated領域的邊緣情況,但有它內部的引用字符串meerley。請參閱this post以獲得更好的擴展,以及一些鏈接到某些正確的庫。

1

我必須在.NET中使用.NET的CSV解析器來處理今年夏天的項目,並在Microsoft Jet文本驅動程序中解決。使用連接字符串指定文件夾,然後使用SQL Select語句查詢文件。您可以使用schema.ini文件指定強類型。起初我沒有這樣做,但後來我的數據類型不是很明顯,例如IP號碼或像「XYQ 3.9 SP1」這樣的條目,結果很糟糕。

我碰到的一個限制是它無法處理64個字符以上的列名;它截斷。這不應該是一個問題,除非我正在處理設計非常糟糕的輸入數據。它返回一個ADO.NET數據集。

這是我找到的最佳解決方案。我會警惕地推出我自己的CSV解析器,因爲我可能會錯過一些最終案例,並且我沒有發現任何其他免費的CSV解析包。

編輯:另外,每個目錄只能有一個schema.ini文件,所以我動態地附加到它以強烈鍵入所需的列。它只會強制鍵入指定的列,並推斷任何未指定的字段。我非常欣賞這一點,因爲我正在處理導入流體70+列CSV的問題,並且不想指定每一列,而僅僅是那些行爲不當的列。

12

如果您預計複雜的CSV解析方案,甚至不會想到自己翻譯我們自己的解析器。那裏有很多優秀的工具,如FileHelpers,甚至是CodeProject

關鍵是這是一個相當普遍的問題,你可以打賭很多軟件開發人員已經考慮和解決了這個問題。

9

我同意@NotMyselfFileHelpers已經過很好的測試,可以處理各種邊緣情況,如果你自己動手處理,你最終必須處理這些情況。看一看FileHelpers的功能,只有在你確信(1)你永遠不需要處理FileHelpers所做的邊緣案例時,或者(2)你喜歡寫這種東西,並且將要喜出望外,當你要解析這樣的東西:

1, 「條例」, 「史密斯」, 「監督員」,

2, '德雷克', '奧馬利' 「無可奉告」, 「看門人,

哎呀,我沒有報價,我在一個新行!

21

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

微軟的TextFieldParser是穩定的,並且CSV文件的RFC 4180如下。不要被Microsoft.VisualBasic命名空間推遲;它是.NET Framework中的標準組件,只需添加對全局組件的引用即可。

如果您正在編譯Windows(而不是Mono),並且預計不需要解析「損壞」(不符合RFC的)CSV文件,那麼這將是明顯的選擇,因爲它是免費的,無限制,穩定和積極支持,其中大部分不能用於FileHelpers。

另請參閱:How to: Read From Comma-Delimited Text Files in Visual Basic瞭解VB代碼示例。

1

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