数が変動するExcelの内容をマージして、dt_Datatableに代入したい

おはようございます。
UiPath Studio 2024.10.0 Community editionのユーザです。

1.やりたいこと

LogフォルダにLogをExcel出力した2つのExcelファイルがあります。
LogFile-2024-06-10.xlsx
LogFile-2024-06-11.xlsx

この2つのExcelの内容をマージして、dt_Datatableに代入したいです。
Logファイルの数は変動するため、DataTableのマージはしたくありません。

アルゴリズムは下記のとおりです。
1.最初に、dt_DatatableをBuild Datatableアクティビティで構築します。
2.リスト変数lis_PathにLogフォルダのファイル一覧を代入します。
①の構文
System.IO.Directory.GetFiles(str_ログファイル格納フォルダ).ToList

3.For Eachアクティビティの中でリスト変数を回して、Read Range(Workbook)アクティビティで2つのExcelの内容をDataTable変数dtに取得します。
4.dtをDataRow配列arr_DataRowに変換し、
②の構文
arr_DataRow=dt.AsEnumerable().ToArray()
For Eachアクティビティの中でarr_DataRowを回して、DataRowを1行ずつdt_Datatableに代入しています。

2.悩んでいること

LogFile-2024-06-10.xlsxの内容しか、dt_Datatableに取得されません。
ブレイクポイントを設定してみると、dtの内容は2つのExcelで2度上書きされているのが確認できたので不思議です。有識者の方、デバッグご支援をお願い致します。

今回ご相談した箇所はMain.xamlの中にあります。
プロジェクト内にある設定.xlsxのValueを設定すればどの環境でも動くと思います。(添付画像参照)

次のように実装できます。画面写真に見切れている部分があるので、疑似コードも添付します。お試しください。

IEnumerable<DataRow> mergedRows = Enumerable.Empty(Of DataRow)

ForEachFile (var CurrentFile in "c:\folder")
{
    DataTable dt = ReadRange(CurrentFile.FullName)
    mergedRows = mergedRows.Concat(dt.AsEnumerable)
}

DataTable dt_Datatable = mergedRows.CopyToDataTable
// WriteRange("merged.xlsx", mergedRows.CopyToDataTable)

2 Likes

回答ありがとうございます。

3点質問をさせてください。

1.今回For Each Fileアクティビティを使って回答を作成いただきましたが、For Eachアクティビティを使っても回答は作成できますか。
もしできない場合は理由をお聞かせいただけるとありがたいです。
私の経験ですと、For Each Fileアクティビティを使うアルゴリズムは、すべてFor Eachアクティビティに置換できたため、できないとすれば理由を知りたいです。

2.ワークフローのmergedRowsの変数の型はおそらく IEnumerable”<”DataRow”>” だと思いますが初めて見ました。DataRowをConcatメソッドでつなげる場合は、必ずこの変数の型を使えと覚えれば良いでしょうか。またこの変数の型はFor Eachアクティビティの中でも使えますか。

3.IEnumerable”<”DataRow”>”と DataRow (DataRowの配列です。) は両方ともコレクションで似ていますが、どう違いますか。言葉で正確に説明が難しい場合はイメージでも良いのでご教示ください。

ご質問をありがとうございます。

1.について
私見では、フォルダ内のファイルを順に取り出すなら、ForEach よりも ForEachFile をお使い頂くことをお勧めします。これは、同じ処理を ForEach で効率的に実装するのは市民開発者にとって少々難しいからです。実際、添付頂いた .zip では、ForEach の利用が最適な形になっていませんでした。(機能的にはこのままでも問題なく動作しますが、)

  • 添付頂いた .zip に含まれるコードでは、Directory.GetFiles() で実装されていましたが、これは Directory.EnumerateFiles() を使う方が効率的な実装となります。その根拠は、web をお探し頂ければすぐ見つかると思います。あるいは、ChatGPT に訊ねてみてください。
  • ForEach では、繰り返す対象に応じて TypeArgument プロパティを適切に設定することが強く推奨されます。Directory.GetFiles() や Directory.EnumerateFiles() を ForEach で繰り返すのであれば、TypeArgument プロパティには String を指定する必要があります。添付頂いた .zip では、このプロパティが適切に設定されていましたが、ループ内先頭の「範囲を読み込み (ワークブック)」に「item.ToString」が指定されていました。TypeArgument プロパティにより、item の型は String となっているため、ここには「item」を指定すれば十分であり、ToString の呼び出しは冗長です。
  • ForEachFile では、ファイル拡張子のマスクを簡単に指定できますが、ForEach では簡単に指定できません。実際のところ、Directory.EnumerateFiles() に引数として拡張子のマスクを指定できますが、これは市民開発者にとって簡単とはいえないでしょう。

端的には、ForEachFile をお使い頂く方が、保守しやすく効率的な実装が簡単に得られるということです。ただ「For Eachアクティビティを使った回答は作成できないのか?」というご質問に対しては、「できる」という回答になります。前述のとおり、ForEach を使っても、同じ処理を同程度に効率的に実装することができます。

2.について
ForEach で繰り返すことができる式は、すべて IEnumerable<T> 型です。T の配列や List<T> などは、すべて IEnumerable<T> の仲間です。当然のことながら、IEnumerable<DataRow> 型の値も ForEach で繰り返すことができます。このとき、TypeArgument プロパティには DataRow を指定してお使い頂くことになります。同様に Linq のメソッドは、すべての IEnumerable<T> に対して呼び出せるようになっています。

ご質問にお答えすると、

DataRowをConcatメソッドでつなげる場合は、必ずこの変数の型を使えと覚えれば良いでしょうか。

はい、それで良いです。DataTable.AsEnumerable が返す型も、IEnumerable<DataRow> となっています。

3.について
DataRow や List<DataRow> などは、すべて IEnumerable<DataRow> として扱えます。IEnumerable<T> は、複数の T 型の要素を管理するコレクションの上位の型であり、T 型の要素を先頭から取り出すためのインターフェイスです。IEnumerable<T> は、先頭から要素を順に取り出す以外の機能(配列のインデックスでアクセスしたり、要素を途中に挿入したりするなど)は提供しません。とても素朴なインターフェイスであるといえます。(だからこそ、すべての T 型のコレクションがこのインターフェイスを実装できるのです。)

拙著の p.331 や p.356 などで紹介していますので、もしよかったらご参照下さい。