Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9328

DynamicObjectとClosedXMLによるExcelファイルのデータ読み取り

$
0
0

C# その2 Advent Calendar 2019の 11 日目の記事です。

はじめに

今まで、記事とか書いたこともないし、他の方々と違いレベルの低い話題なので、そこのところご承知おきください。

経緯

業務上、Excel ファイル にエクスポートされたデータを取り扱うことが多く、VBA でマクロを書くのもVBE がアレすぎて面倒なため、C#で処理したいなーと

今回作るにあたっては、読み込んで値を変える程度ですが、業務によっては読み込んだものを加工して転記したり、DB に登録したりすると思うので自分用にメモ

なお、今回ClosedXMLについては、2019/12/01時点の最新版を使ってます

サンプルデータ

こんな感じでサンプルデータを XLSX ファイルとして作成

IdNameAffiliatedAgePosition
1A1総務部28部長
2B2人事部19係長
3C3開発部34課長
4D4開発部23係長
5E5製造部18部長
6F6製造部69一般

サンプルに使えそうな人な名前が思いつかない

早速作成

ということで、DynamicObject を継承する形で作成

基本的には、TryGetMember と TrySetMember を override する
なお、ベースは IDictionaryとしてフィールド名にあたるものとその値を用意 (状況によっては、TrySetIndex、TrySetIndex も override してもいいのかも)

DataRecord.cs
privatereadonlyIDictionary<string,object>dictionary;publicDataRecord(IDictionary<string,object>dictionary)=>this.dictionary=dictionary;publicoverrideboolTrySetMember(SetMemberBinderbinder,objectvalue){if(!IsTypeCheck(binder.Name,value))returnfalse;dictionary[binder.Name]=value;returntrue;}privateboolIsTypeCheck(stringkey,objectvalue){// キーがないならNGif(!dictionary.TryGetValue(key,outvarresult))returnfalse;// 型が一致しない場合はNGreturnIsTypeMatch(result.GetType(),value.GetType());}privateboolIsTypeMatch(TypebaseType,TypevalueType)=>valueType.Equals(baseType)||valueType.IsSubclassOf(baseType);publicoverrideboolTryGetMember(GetMemberBinderbinder,outobjectresult)=>dictionary.TryGetValue(binder.Name,outresult);

読み取りとか行うものを

XLSX を ClosedXML で読み込み

項目名称の取得やデータ取得の仕方は Excel ファイルのデータ次第なので、そこは適宜に

今回は RangeUsed メソッドからテーブル変換し、 Fields プロパティから項目名称を、データについては DataRange プロパティを用いて取得します。

ExcelControl.cs
publicIEnumerable<dynamic>ReadExcelData(){using(IXLWorkbookworkbook=newXLWorkbook(Path)){IXLWorksheetworksheet=workbook.Worksheet(1);// 項目名称の取得vartables=worksheet.RangeUsed().AsTable();varcolumnNames=tables.Fields.Select(field=>field.Name);varvalues=tables.DataRange.Rows();// 生成開始vargenerator=newDataRecordGenerator(columnNames,values);returngenerator.Generate();}}
DataRecordGenerator.cs
publicIEnumerable<dynamic>Generate(){foreach(varrowinrows){vardic=columnNames.Select((name,index)=>(name,index)).Select(x=>(x.name,row.Cell(x.index+1).Value)).ToDictionary(k=>k.name,v=>v.Value);yieldreturnnewDataRecord(dic);}}

んで、テストコード

[TestMethod]publicvoidTestMethod1(){varexcelControl=newExcelControl(path);varresult=excelControl.ReadExcelData().ToArray();foreach(variteminresult){Console.WriteLine($"{item.Id},{item.Name},{item.Affiliated},{item.Age},{item.Position}");}// 数値に関してはClosedXMLの読み取るとdoubleで取得されるresult[0].Age=(double)50;result[0].Affiliated="役員";result[0].Position="執行役員";Console.WriteLine($"{result[0].Id},{result[0].Name},{result[0].Affiliated},{result[0].Age},{result[0].Position}");}

実行結果

1,A1,総務部,28,部長
2,B2,人事部,19,係長
3,C3,システム開発部,34,課長
4,D4,システム開発部,23,係長
5,E5,製造部,18,部長
6,F6,製造部,69,一般
1,A1,役員,50,執行役員

Excel の内容を読み込みすることに成功し、また書き換えた後の出力も問題ありません。

感想

今回はテキストにしただけですが、読み込みして書き換えできるようにすることができました。

そもそも、ClosedXML だと自動マッピングできるかといわれるとそこらへんは勉強不足です。(できるんだったらそっち使ったほうがいいかもです)

リフレクション使ってのマッピングということで自分で実装するのがベターなのかもしれません、というより実際にテストした際も実行速度については、普通に型マッピングしたほうが早かったですし

ただ、業務で使うとなると、データ都合上Excel 中に 50 列くらい普通に並ぶものもあると思うので、マッピングしたい項目数が多い場合だったり、とりあえず意識しとうないときとかは dynamic を使うこともありなのかもしれません。
(そもそも、項目が多い時点で「孟徳!なぜ俺がこんなものを見なきゃならん!」的な作業なので)

サンプルは以下
https://github.com/exactead/ExcelReaderDynamic

参考ソース・記事

・ClosedXML (https://github.com/ClosedXML/ClosedXML )

・「C# DynamicObjectの基本と細かい部分について」
(http://neue.cc/2010/05/06_257.html )

・「【C#】ClosedXML で Excel テーブルを IEnumerableオブジェクトに変換」
https://qiita.com/penguinshunya/items/dd586b1e42b7a66e552e


Viewing all articles
Browse latest Browse all 9328

Latest Images

Trending Articles