.Netの厳密に型指定されたデータ構造にCSVファイルをインポートする[終了]


106

厳密に型指定されたデータ構造にCSVファイルをインポートする最良の方法は何ですか?


FileHelpers Open Source Libraryをチェックしてください。
NotMyself 2008


7
これが1103495よりも1年前に作成されたことを考えると、この質問はこれと重複していると思います。
MattH、2011年

2
ありがとう、マット。私はそれらを一緒にリンクしようとしただけで、どちらが最初に来たかを示していません。私はこれを指す他の質問とまったく同じテキストを持っていることがわかります。2つの質問を結び付けるより良い方法はありますか?
Mark Meuer、2011年

回答:


74

MicrosoftのTextFieldParserは安定しており、CSVファイルはRFC 4180に準拠しています。Microsoft.VisualBasic名前空間にうんざりしないでください。これは.NET Frameworkの標準コンポーネントMicrosoft.VisualBasicです。グローバルアセンブリへの参照を追加するだけです。

(Monoとは対照的に)Windows向けにコンパイルしていて、「壊れた」(RFCに準拠していない)CSVファイルを解析する必要がない場合は、これが明らかな選択です。積極的にサポートされており、そのほとんどはFileHelpersには言えません。

関連項目:方法: VBコード例については、Visual Basicコンマ区切りのテキストファイルから読み取る


2
このクラスには、残念ながら名前が付けられた名前空間以外、VB固有のものはありません。「単純な」CSVパーサーだけが必要な場合は、このライブラリを選択します。ダウンロード、配布、または一般的に心配することは何もないためです。そのために、この回答からVBに焦点を当てた表現を編集しました。
アーロンノート、2011年

@Aaronaught私はあなたの編集はほとんど改善だと思います。そのRFCは必ずしも権威があるとは限りませんが、多くのCSVライターはそれに準拠していません。たとえば、Excel は常に「CSV」ファイルでコンマ使用するわけではありません。また、私の以前の答えは、クラスがC#から使用できるとすでに言っていませんか?
MarkJ、2011年

これTextFieldParserは、タブ区切りやその他の奇妙なExcelで生成された残骸でも機能します。私はあなたの前の答えは、ライブラリは、VB-特異的であったと主張していなかった、それだけで、それが本当にされたことを意味しているように私に出くわしたことを実感意味 VBのためではなく、意図した C#から使用する、私はあると思うしませんケース-MSVBには本当に便利なクラスがいくつかあります。
アーロンノート、2011年

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

これにはファイルシステムアクセスが必要です。私が知る限り、メモリ内ストリームでOLEDBを機能させる方法はありません:(
UserControl

3
@UserControl、もちろんファイルシステムへのアクセスが必要です。彼はCSVファイルのインポートについて尋ねました
Kevin

1
私は文句を言っていません。実際、残りの部分よりもOLEDBソリューションを使用したいのですが、ASP.NETアプリケーションでCSVを解析する必要があるときに何度もイライラしていたので、注意してください。
UserControl、

12

CSV解析のかなり複雑なシナリオを想定している場合は、独自のパーサーをロールアップすることさえ考えないでください。優れたツールの多くは次のように、そこにありますFileHelpersから、あるいはものCodeProjectの

ポイントは、これはかなり一般的な問題であり、多くのソフトウェア開発者がすでにこの問題を考えて解決していることでしょう。


このリンクで質問に答えることができますが、回答の重要な部分をここに含め、参照用のリンクを提供することをお勧めします。リンクされたページが変更されると、リンクのみの回答が無効になる可能性があります。- レビューから
techspider 2016

@techspiderに感謝します。この投稿がStackOverflowのベータ期間からのものであったことを覚えていただければ幸いです。
古い

9

ブライアンは、それを強く型付けされたコレクションに変換するための素晴らしいソリューションを提供します。

指定された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;
}

これは、二重引用符で区切られていないフィールドのエッジケースを処理しないことに注意してください。少し良い説明といくつかの適切なライブラリへのリンクについては、この投稿を参照してください。


9

@NotMyselfに同意します。FileHelpersは十分にテストされており、自分で行う場合には最終的に対処しなければならないあらゆる種類のエッジケースを処理します。FileHelpersが何をするかを見て、(1)FileHelpersが行うエッジケースを処理する必要がまったくないか、(2)この種のものを書くのが大好きで、このようなものを解析しなければならないときは大喜びしてください:

1、「ビル」、「スミス」、「スーパーバイザー」、「コメントなし」

2、「ドレイク」、「オマレー」、「用務員、

おっと、私は引用されておらず、新しい行にいます!


6

退屈だったので、書いたものをいくつか修正しました。ファイル全体の反復回数を削減しながら、解析をOO方式でカプセル化しようとします。これは、最上位のforeachで1回だけ繰り返されます。

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


2

これを行う簡単な方法は、ファイルを開いて、各行を配列、リンクリスト、選択したデータ構造に読み込むことです。ただし、最初の行の取り扱いには注意してください。

これはあなたの頭の上のかもしれませんが、接続文字列を使用してそれらにアクセスするための直接的な方法があるようです。

C#やVBの代わりにPythonを使用してみませんか?それはあなたのためにすべての重労働を行うインポートする素晴らしいCSVモジュールを持っています。


1
CSVパーサーのためにVBからpythonにジャンプしないでください。VBに1つあります。奇妙なことに、この質問への回答では無視されているようです。msdn.microsoft.com/en-us/library/...
MarkJ

1

この夏のプロジェクトでは、.NETでCSVパーサーを使用する必要があり、Microsoft Jet Text Driverで解決しました。接続文字列を使用してフォルダーを指定し、SQL Selectステートメントを使用してファイルをクエリします。schema.iniファイルを使用して、強い型を指定できます。最初はこれを実行しませんでしたが、IP番号や「XYQ 3.9 SP1」のようなエントリなど、データのタイプがすぐに明らかにならないという悪い結果が出ていました。

私が遭遇した1つの制限は、64文字を超える列名を処理できないことです。切り捨てられます。これは問題になりません。ただし、設計が非常に不十分な入力データを扱っていた場合を除きます。ADO.NET DataSetを返します。

これは私が見つけた最良の解決策でした。私はおそらく自分のCSVパーサーをロールすることに警戒しているでしょう。おそらく、いくつかの最終ケースを見逃してしまい、.NET用の他の無料のCSV解析パッケージがそこにないためです。

編集:また、必要な列を強く入力するために、ディレクトリごとにschema.iniファイルは1つしか存在できないため、動的に追加します。指定された列のみを厳密に型指定し、指定されていないフィールドを推測します。70以上の流動的なCSVのインポートを扱っていて、各列を指定するのではなく、誤動作している列のみをインポートしたので、私はこれに本当に感謝しています。


VB.NETがCSVパーサーに組み込まれていないのはなぜですか?msdn.microsoft.com/en-us/library/...
MarkJ

1

コードを入力しました。datagridviewerの結果は良好に見えました。1行のテキストを解析して、オブジェクトのarraylistを作成します。

    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

データにコンマがないことが保証できる場合、おそらく最も簡単な方法はString.splitを使用することです

例えば:

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

あなたが助けるために使用できるライブラリがあるかもしれませんが、それはおそらくあなたが得ることができるのと同じくらい簡単です。データにカンマが含まれていないことを確認してください。そうでない場合は、データをより適切に解析する必要があります。


これは最適なソリューションではありません
roundcrisis

メモリ使用量とオーバーヘッドの多くで非常に悪い。小は数キロバイトに感謝する必要があります。間違いなく、10MBのCSVには適していません。
Piotr Kula 2012

それはあなたのメモリとファイルのサイズに依存します。
tonymiao
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.