CsvHelperの何カラム目でエラーが出たか知りたくてAutoMappingを諦めた
[追記]
exception.Data["CsvData"]に必要な情報が入っていたので
この記事はほとんどそのまま無駄記事になりました!
CsvHelperの使い方のサンプル程度に残しておきますが、
読んだ人は読み終わったら「exception.Data["CsvData"]でいいじゃん」と唱えましょう。
[追記ここまで]
[元記事ここから]
C#でCSVを読みたかったんですが、CSVって意外と厄介で、標準であるわけでもなく。
そこで、CsvHelperをNugetしてきて使いまして。
レコードをクラスとして定義してCsvClassMap
例えばint型のフィールドに文字列や空文字が入ってると、Exceptionが飛んでくるわけですね。
そうなると使ってる人間としては、「一体どこのカラムに問題があったのか」を知りたいじゃないですか。
100カラムもあるようなcsvから目で探すのは馬鹿らしいじゃないですか。
ところがこのException、何行目・何カラム目が問題だったかの情報が含まれてない。
あまりC#のお作法に慣れてないというかException使うのも初めてなんですが、
そういうもんなんですかね?ここに入れてくれれば万事解決だったのに。
まぁ、そもそも1行ずつ読み込みするので行数は把握できるんですが、カラムはそうもいかない。
…ので、大人しく自動Mappingをやめて、手動にしました(めんどくさい)!
手動ならTryGetField()という便利なものがあったので、
こいつで1カラムずつ調べながら読み込んで、失敗したら自作の例外を投げる。
コードはこんな感じです。
// こんなExceptionを作っておいて… class CsvReadException : Exception { public CsvReadException() { } public CsvReadException(string message) : base(message) { } public CsvReadException(int row, int col) : base("CSV読み込みエラー。行:" + row + ", カラム:" + col) { } public CsvReadException(string message, Exception inner) : base(message) { } } // CsvHelper.CsvReaderのサブクラスを作っちゃう class MyCsvReader : CsvHelper.CsvReader { // コンストラクタは今回のブログに関係ないけど参考までに。 // こうやって継承クラスのコンストラクタでオプション指定したほうが使いまわしやすい public MyCsvReader(string path) : base(new StreamReader(path)) { // 先頭が'/'の行はコメント扱い。デフォルトは'#' this.Configuration.Comment = '/'; // コメントを有効に this.Configuration.AllowComments = true; // ヘッダー行の無いCSVですよ指定 this.Configuration.HasHeaderRecord = false; } // GetField<T>をオーバーライドして、自作例外投げるバージョンにする override public T GetField<T>(int col) { T field; if (!this.TryGetField(col, out field)) { throw new CsvReadException(this.Row, col + 1); } return field; } } class Record { // こんな6カラムがある想定のCSVですよ public int hogeInt1 { get; set; } public int hogeInt2 { get; set; } public int hogeInt3 { get; set; } public string hogeString1 { get; set; } public string hogeString2 { get; set; } public string hogeString3 { get; set; } // 使う側でtry-catchしてね public Record(MyCsvReader csvReader) { int col = 0; this.hogeInt1 = csvReader.GetField<int>(col++); this.hogeInt2 = csvReader.GetField<int>(col++); this.hogeInt3 = csvReader.GetField<int>(col++); this.hogeString1 = csvReader.GetField<string>(col++); this.hogeString2 = csvReader.GetField<string>(col++); this.hogeString3 = csvReader.GetField<string>(col++); } }
記事はここまで。
最初は↓こんなに汚いコードを書いたんだけど、
ブログを更新した直後にうまいやり方が思いつくという黄金パターンでした。
オブジェクト指向もちゃんと使うの初めてに近いからね、仕方ないね。
自らの思考のトレースとして敢えて残しておく。
// Exceptionは上と同じ // CsvReaderのサブクラスを作っていない class Record { // こんな6カラムがある想定のCSVですよ public int hogeInt1 { get; set; } public int hogeInt2 { get; set; } public int hogeInt3 { get; set; } public string hogeString1 { get; set; } public string hogeString2 { get; set; } public string hogeString3 { get; set; } public Record(CsvHelper.CsvReader csvReader) { int col = 0; // out引数にプロパティを渡せなかったので、一度別変数で受け取る(めんどくさい!) int intFiled; string stringFiled; // TryGetFieldで調べつつ、失敗したら例外投げ、うまくいけば対応プロパティに格納 // 他資料から矩形貼り付けとかしたい関係で、無理やり1行にしている if (!csvReader.TryGetField(col++, out intFiled)) { throw new CsvReadException(csvReader.Row, col); } this.hogeInt1 = intFiled; if (!csvReader.TryGetField(col++, out intFiled)) { throw new CsvReadException(csvReader.Row, col); } this.hogeInt2 = intFiled; if (!csvReader.TryGetField(col++, out intFiled)) { throw new CsvReadException(csvReader.Row, col); } this.hogeInt3 = intFiled; if (!csvReader.TryGetField(col++, out stringFiled)) { throw new CsvReadException(csvReader.Row, col); } this.hogeString1 = stringFiled; if (!csvReader.TryGetField(col++, out stringFiled)) { throw new CsvReadException(csvReader.Row, col); } this.hogeString2 = stringFiled; if (!csvReader.TryGetField(col++, out stringFiled)) { throw new CsvReadException(csvReader.Row, col); } this.hogeString3 = stringFiled; } }