C#でCSVの読み書きするためにCsvHelperを使用していますがその際にバリデーションチェックをしたくなったのでメモしておきます。
環境
- VisualStudio 2022 Version 17.3.0 Preview 5.0
- .NET 6.0
- CsvHelper 28.0.1
バリデーションチェックの実装
必要なものはCSVの読み込み処理、読み込んだデータの保存先となるプロパティクラス、バリデーションを行うClassMapクラス。
使用するCSVは以下。
1行目がヘッダ行、2行目以降がデータです。
"番号","メッセージ"
"99","バリデーション成功レコード"
"100","バリデーション失敗レコードxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
"101","バリデーション失敗レコード"
プロパティクラス
internal class SampleCsvModel
{
[Name("番号")] // ヘッダの文字列
public int No { get; set; }
[Name("メッセージ")] // ヘッダの文字列
public string Message { get; set; }
}
バリデーションの実装を行うクラスです。ClassMapを継承し、ジェネリックとしてプロパティクラスを指定します。
バリデーションのチェック内容は以下。
1カラム目の番号は数値、且つ1以上100以下、それ以外はエラー
2カラム目のメッセージは文字数が1文字以上50文字以下、それ以外はエラー
internal class SampleCsvMap : ClassMap
{
public SampleCsvMap()
{
Map(m => m.No).Name("番号").Validate(f =>
{
if (f.Field.Length > 0)
{
try
{
var no = Int32.Parse(f.Field);
return no > 0 && no <= 100;
}
catch
{
return false;
}
}
return false;
});
Map(m => m.Message).Name("メッセージ").Validate(f =>
{
return f.Field.Length > 0 && f.Field.Length <= 50;
});
}
}
最後に読み込み処理です。大事なのはcsvReader.Context.RegisterClassMapです。これでバリデーションを行うClassMapを設定します。
今回はエラー行数をクライアントに通知する必要があったので1レコードずつ読み込んでます。どこがエラーかを気にする必要がない場合はcsvReader.GetRecords()で読んでもOK。また、エラーのカラムも出すことが可能です。
catch内の処理で e.Context.Reader.CurrentIndex でカラムの番号を取得し e.Context.Reader.HeaderRecord[index] でカラム名を取得できます。
ただし、1レコード内に複数バリデーションエラーが存在した場合は最初に検出したカラムしか取得できません。
エラーを検出した時点でそのレコードの処理は終わりですので注意してください。
できれば全てチェックした上でクライアントに通知したかったのですが方法がわかりませんでした。
public static void Main(string[] args)
{
using var reader = new StreamReader("./write.csv");
using var csvReader = new CsvReader(reader, new CsvConfiguration(CultureInfo.InvariantCulture));
csvReader.Context.RegisterClassMap(typeof(SampleCsvMap));
int readIndex = 1;
while (csvReader.Read())
{
try
{
var record = csvReader.GetRecord();
Debug.WriteLine($"{readIndex} -> {record.No}, {record.Message}");
}
catch (FieldValidationException e)
{
var index = e.Context.Reader.CurrentIndex;
var header = e.Context.Reader.HeaderRecord;
Debug.WriteLine($"{readIndex} -> NG. headerName = {header[index]}");
}
readIndex++;
}
}
上記を実行した結果です。
1 -> 99, バリデーション成功レコード
2 -> NG. headerName = メッセージ
3 -> NG. headerName = 番号
コメント