C#を使用したCSVファイルの読み取り


169

単純なインポートアプリケーションを作成していて、CSVファイルを読み取り、結果をにDataGrid表示し、CSVファイルの破損した行を別のグリッドに表示する必要があります。たとえば、5つの値よりも短い線を別のグリッドに表示します。私はそれをこのようにしようとしています:

StreamReader sr = new StreamReader(FilePath);
importingData = new Account();
string line;
string[] row = new string [5];
while ((line = sr.ReadLine()) != null)
{
    row = line.Split(',');

    importingData.Add(new Transaction
    {
        Date = DateTime.Parse(row[0]),
        Reference = row[1],
        Description = row[2],
        Amount = decimal.Parse(row[3]),
        Category = (Category)Enum.Parse(typeof(Category), row[4])
    });
}

この場合、配列を操作するのは非常に困難です。値を分割するより良い方法はありますか?


解決策をありがとう。それを回答投稿として投稿することを検討してください-質問に含めても、その読みやすさに役立ちません。
BartoszKP 2015年

回答:


363

車輪を再発明しないでください。.NET BCLの既存の機能を活用します。

  • への参照を追加しますMicrosoft.VisualBasic(はい、VisualBasicと表示されていますが、C#でも同様に機能します。最後に、すべてがILであることを覚えておいてください)
  • Microsoft.VisualBasic.FileIO.TextFieldParserクラスを使用してCSVファイルを解析する

これがサンプルコードです:

using (TextFieldParser parser = new TextFieldParser(@"c:\temp\test.csv"))
{
    parser.TextFieldType = FieldType.Delimited;
    parser.SetDelimiters(",");
    while (!parser.EndOfData) 
    {
        //Processing row
        string[] fields = parser.ReadFields();
        foreach (string field in fields) 
        {
            //TODO: Process field
        }
    }
}

私のC#プロジェクトでうまく機能します。

ここにいくつかのリンク/情報があります:


18
VBライブラリを使用しない方法があればいいのにと思いますが、これは完璧に機能しました!ありがとうございました!
gillonba

5
+1:53Mbファイルでルーメンワークスの高速CSVリーダーを壊しました。43,000行の後でラインキャッシュが失敗し、バッファをスクランブルしたようです。VB TextFieldParserを試してみました。ありがとう
コーディング終了

10
+1すばらしい答えです。多くの人がこのクラスが存在することを知らないことがわかったからです。今後の視聴者が注意すべき点の1つは、メソッドがプロパティを設定するparser.TextFieldType = FieldType.Delimited;ため、を呼び出した場合は設定が不要になることです。parser.SetDelimiters(",");TextFieldType
ブライアン

10
こちらもご覧ください:dotnetperls.com/textfieldparser。TextFieldParserは、String.SplitおよびStreamReaderよりもパフォーマンスが劣ります。ただし、string.SplitとTextFieldParserの間には大きな違いがあります。TextFieldParserは、列にカンマを持つような奇妙な例を処理します。あなたのような列に名前を付けることができ"text with quote"", and comma"、あなたは正しい値を得ることができますtext with quote", and comma代わりに誤って区切り値のを。したがって、csvが非常に単純な場合は、String.Splitを選択することをお勧めします。
Yongwei Wu

5
これを使用するには、Microsoft.VisualBasicへの参照を追加する必要がある場合があります。Visual Studioでプロジェクトを右クリックし、[追加]> [参照]を選択して、Microsoft.VisualBasicのチェックボックスをオンにします。
Derek Kurth、2015年

37

私の経験では、さまざまなcsv形式があります。特に、フィールド内での引用符と区切り文字のエスケープの処理方法。

これらは私が遭遇したバリアントです:

  • 引用符は二重引用符で囲まれ、二重になっています(Excel)15 "-> field1、" 15 "" "、field3
  • 他の理由でフィールドが引用されていない限り、引用は変更されません。つまり、15 "-> field1,15"、fields3
  • 引用符は\でエスケープされます。つまり、15 "-> field1、" 15 \ ""、field3
  • 引用符はまったく変更されません(これは常に正しく解析できるとは限りません)
  • 区切り文字は引用符で囲まれています(Excel)。つまり、a、b-> field1、 "a、b"、field3
  • 区切り文字は\でエスケープされます。つまり、a、b-> field1、a \、b、field3

既存のcsvパーサーの多くを試しましたが、遭遇したバリアントを処理できる単一のパーサーはありません。パーサーがサポートするエスケープバリアントをドキュメントから見つけることも困難です。

私のプロジェクトでは、VB TextFieldParserまたはカスタムスプリッターを使用しています。


1
あなたが提供したテストケースのこの答えが大好きです!
Matthew Rodatus 2013年

2
主な問題は、ほとんどの実装がCSV形式とデリミタのエスケープ方法を記述するRFC 4180を気にしないことです。
Jenny O'Reilly、2015年

RFC-4180は2005年のものですが、今では古いようですが、覚えておいてください。.Netフレームワークは2001年に最初にリリースされました。 、ISO-8601またはRFC-761。
Joel Coehoorn

23

NugetのCsvHelperをお勧めします

(Microsoft.VisualBasicへの参照を追加するのは正しくありません。醜いだけでなく、おそらくクロスプラットフォームでもありません。)


2
C#とまったく同じようにクロスプラットフォームです。
PRMan

間違っています。LinuxのMicrosoft.VisualBasic.dllはMonoソースから来ています
。Mono

(さらに、VB言語は、Monoプロジェクトの作成/開発に関与している企業のもとではまったく注目されていなかったため、C#エコシステム/ツールと比較して、取り組みの点で
はるかに遅れてい

1
両方で遊んだらCsvHelper、組み込みの行からクラスへのマッパーが付属していることを付け加えておきます。これにより、列ヘッダー(存在する場合)のバリエーションが可能になり、明らかに列の順序のバリエーションさえ可能になります(ただし、私は後者をテストしていません)。全体的に見て、はより「高レベル」だと感じていますTextFieldParser
デビッド

1
そうです、Microsoft.VisualBasic名前空間は.NET Core 2.1では使用できません
N4ppeL

13

ホイールを作り直したくない場合は、ライブラリを使用すると便利な場合がありますが、この場合、ライブラリを使用するよりも少ないコード行で読みやすく、同じ作業を行うことができます。これは私が非常に使いやすいと思う別のアプローチです。

  1. この例では、StreamReaderを使用してファイルを読み取ります
  2. 各行からの区切り文字を検出する正規表現。
  3. インデックス0からnまでの列を収集する配列

using (StreamReader reader = new StreamReader(fileName))
    {
        string line; 

        while ((line = reader.ReadLine()) != null)
        {
            //Define pattern
            Regex CSVParser = new Regex(",(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))");

            //Separating columns to array
            string[] X = CSVParser.Split(line);

            /* Do something with X */
        }
    }

4
確かにそれ自体に新しい行が含まれているデータに問題がありますか?
Doogal

現在、CSVデータファイルはデータ間に空の行が含まれていることを認識していませんが、それを行うソースがある場合は、リーダーを実行する前に、空白または何も含まれていない行を削除する単純な正規表現テストを実行します。別の例については、こちらをご確認ください:stackoverflow.com/questions/7647716/...
マナ

1
この種の問題では、正規表現よりもcharベースのアプローチの方が自然です。引用符の存在に応じて、動作は異なるはずです。
ケーシー

6

CSVは非常に複雑になりがちです。

堅牢で十分にテストされたものを使用してください:
FileHelpers: www.filehelpers.net

FileHelpersは無料で使いやすい.NETライブラリで、ファイル、文字列、またはストリームの固定長または区切られたレコードからデータをインポート/エクスポートできます。


5
FileHelperは一度に多くのことを実行しようとしていると思います。ファイルの解析は、最初に行をフィールドに分割し、次にフィールドをデータに解析する2ステップのプロセスです。関数を組み合わせると、マスター/ディテールやラインフィルタリングなどの処理が難しくなります。
adrianm 2010


4

このリストの別の1つ、Cinchoo ETL -CSVファイルの読み取りと書き込みを行うオープンソースライブラリ

以下のサンプルCSVファイルの場合

Id, Name
1, Tom
2, Mark

以下のようにライブラリを使用してすばやくロードできます

using (var reader = new ChoCSVReader("test.csv").WithFirstLineHeader())
{
   foreach (dynamic item in reader)
   {
      Console.WriteLine(item.Id);
      Console.WriteLine(item.Name);
   }
}

CSVファイルに一致するPOCOクラスがある場合

public class Employee
{
   public int Id { get; set; }
   public string Name { get; set; }
}

これを使用して、以下のようにCSVファイルをロードできます。

using (var reader = new ChoCSVReader<Employee>("test.csv").WithFirstLineHeader())
{
   foreach (var item in reader)
   {
      Console.WriteLine(item.Id);
      Console.WriteLine(item.Name);
   }
}

使い方はCodeProjectの記事をご覧ください。

免責事項:私はこのライブラリの作者です


こんにちは、CSVをSQLテーブルにロードできますか-事前にCSVテーブルのヘッダーを知りません。csvのwhatsをSQLテーブルにミラーリングする
aggie

はい、できます。このリンクを参照してくださいstackoverflow.com/questions/20759302/...
RajN

2
private static DataTable ConvertCSVtoDataTable(string strFilePath)
        {
            DataTable dt = new DataTable();
            using (StreamReader sr = new StreamReader(strFilePath))
            {
                string[] headers = sr.ReadLine().Split(',');
                foreach (string header in headers)
                {
                    dt.Columns.Add(header);
                }
                while (!sr.EndOfStream)
                {
                    string[] rows = sr.ReadLine().Split(',');
                    DataRow dr = dt.NewRow();
                    for (int i = 0; i < headers.Length; i++)
                    {
                        dr[i] = rows[i];
                    }
                    dt.Rows.Add(dr);
                }

            }

            return dt;
        }

        private static void WriteToDb(DataTable dt)
        {
            string connectionString =
                "Data Source=localhost;" +
                "Initial Catalog=Northwind;" +
                "Integrated Security=SSPI;";

            using (SqlConnection con = new SqlConnection(connectionString))
                {
                    using (SqlCommand cmd = new SqlCommand("spInsertTest", con))
                    {
                        cmd.CommandType = CommandType.StoredProcedure;

                        cmd.Parameters.Add("@policyID", SqlDbType.Int).Value = 12;
                        cmd.Parameters.Add("@statecode", SqlDbType.VarChar).Value = "blagh2";
                        cmd.Parameters.Add("@county", SqlDbType.VarChar).Value = "blagh3";

                        con.Open();
                        cmd.ExecuteNonQuery();
                    }
                }

         }

このソリューションをどこからコピーしましたか?
MindRoasterMir

0

まず第一に、CSVとは何か、どのように記述するかを理解する必要があります。

  1. 次のすべての文字列(/r/n)は、次の「テーブル」行です。
  2. 「表」セルは、区切り記号で区切られています。最も頻繁に使用される記号は\tまたは,
  3. すべてのセルにこの区切り記号を含めることができます(この場合、セルは引用符記号で始まり、この記号で終わる必要があります)
  4. すべてのセルに/r/n記号を含めることができます(セルは引用符記号で始まり、この場合この記号で終わる必要があります)

C#/ Visual BasicがCSVファイルを操作する最も簡単な方法は、標準Microsoft.VisualBasicライブラリを使用することです。必要な参照と次の文字列をクラスに追加するだけです。

using Microsoft.VisualBasic.FileIO;

はい、C#で使用できます。心配しないでください。このライブラリは比較的大きなファイルを読み取ることができ、必要なすべてのルールをサポートしているため、すべてのCSVファイルで作業できます。

少し前に、このライブラリに基づいてCSVの読み取り/書き込み用の単純なクラスを作成しました。この単純なクラスを使用すると、2次元配列のようにCSVを操作できます。次のリンクで私のクラスを見つけることができます:https : //github.com/ukushu/DataExporter

使用の簡単な例:

Csv csv = new Csv("\t");//delimiter symbol

csv.FileOpen("c:\\file1.csv");

var row1Cell6Value = csv.Rows[0][5];

csv.AddRow("asdf","asdffffff","5")

csv.FileSave("c:\\file2.csv");

0

前の回答を完了するには、TextFieldParserまたはstring.Splitメソッドで解析されたCSVファイルのオブジェクトのコレクションが必要な場合があり、その後、各行がReflectionを介してオブジェクトに変換されます。明らかに、最初にCSVファイルの行と一致するクラスを定義する必要があります。

ここにあるMichael Kropatの単純なCSVシリアライザを使用しました:Generic class to CSV(すべてのプロパティ) と彼のメソッドを再利用して、希望するクラスのフィールドとプロパティを取得しました。

次の方法でCSVファイルを逆シリアル化します。

public static IEnumerable<T> ReadCsvFileTextFieldParser<T>(string fileFullPath, string delimiter = ";") where T : new()
{
    if (!File.Exists(fileFullPath))
    {
        return null;
    }

    var list = new List<T>();
    var csvFields = GetAllFieldOfClass<T>();
    var fieldDict = new Dictionary<int, MemberInfo>();

    using (TextFieldParser parser = new TextFieldParser(fileFullPath))
    {
        parser.SetDelimiters(delimiter);

        bool headerParsed = false;

        while (!parser.EndOfData)
        {
            //Processing row
            string[] rowFields = parser.ReadFields();
            if (!headerParsed)
            {
                for (int i = 0; i < rowFields.Length; i++)
                {
                    // First row shall be the header!
                    var csvField = csvFields.Where(f => f.Name == rowFields[i]).FirstOrDefault();
                    if (csvField != null)
                    {
                        fieldDict.Add(i, csvField);
                    }
                }
                headerParsed = true;
            }
            else
            {
                T newObj = new T();
                for (int i = 0; i < rowFields.Length; i++)
                {
                    var csvFied = fieldDict[i];
                    var record = rowFields[i];

                    if (csvFied is FieldInfo)
                    {
                        ((FieldInfo)csvFied).SetValue(newObj, record);
                    }
                    else if (csvFied is PropertyInfo)
                    {
                        var pi = (PropertyInfo)csvFied;
                        pi.SetValue(newObj, Convert.ChangeType(record, pi.PropertyType), null);
                    }
                    else
                    {
                        throw new Exception("Unhandled case.");
                    }
                }
                if (newObj != null)
                {
                    list.Add(newObj);
                }
            }
        }
    }
    return list;
}

public static IEnumerable<MemberInfo> GetAllFieldOfClass<T>()
{
    return
        from mi in typeof(T).GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
        where new[] { MemberTypes.Field, MemberTypes.Property }.Contains(mi.MemberType)
        let orderAttr = (ColumnOrderAttribute)Attribute.GetCustomAttribute(mi, typeof(ColumnOrderAttribute))
        orderby orderAttr == null ? int.MaxValue : orderAttr.Order, mi.Name
        select mi;            
}

0

CsvHelperを使用することを強くお勧めします。

ここに簡単な例があります:

public class csvExampleClass
{
    public string Id { get; set; }
    public string Firstname { get; set; }
    public string Lastname { get; set; }
}

var items = DeserializeCsvFile<List<csvExampleClass>>( csvText );

public static List<T> DeserializeCsvFile<T>(string text)
{
    CsvReader csv = new CsvReader( new StringReader( text ) );
    csv.Configuration.Delimiter = ",";
    csv.Configuration.HeaderValidated = null;
    csv.Configuration.MissingFieldFound = null;
    return (List<T>)csv.GetRecords<T>();
}

完全なドキュメントは、https//joshclose.github.io/CsvHelperにあります。

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