DataTableの列の並びの変更方法について

## 皆もすなるQiitaといふものを、儂もしてみむとてするなり。

と言う訳で、こん＊＊は！はなっち！です。

【初めに】
ロボットでの醍醐味は、EXCELなどのデータを基に、WEBへ情報投入したり、メールを送信したり、情報を複数のEXCELファイルに分割したりする事ですね。

EXCELから、そのデータを加工して、別のEXCELに転記するような業務もあると思います。
その場合、転記先のレイアウトは、転記元のレイアウトと同じ、、、なんてことは少ないと思います。

今回は、DataTableの列の並びの変更方法に関してまとめてみました。
- - - 
## １）「データ テーブルをフィルター」アクティビティを活用する。
DataTableの列の並びの変更に、なんで「データ テーブルをフィルター」なんだ？と思われる方もいらっしゃるかと思います。
<img width="400" alt="データ テーブルをフィルター" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/a4ef75d6-c0cf-7d38-ef8d-22ca0f151a80.png">

基本的には、「フィルター行」タブに、【列】に対して、その行の【値】の【操作】（条件）を設定し、その条件に合致した行を【保持】、あるいは【削除】を行うアクティビティですが、その条件を指定しない場合、全行を対象に保持を行います（削除をチェックしても、行は削除されず、全行を対象に保持しました！当然か）。

この「データ テーブルをフィルター」アクティビティの「出力列」タブに、出力列のレイアウトを指定する事が出来ます。

<img width="400" alt="出力列" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/1a599134-2505-609e-7c5f-3737248f604f.png">

対象とする【列名】あるいは、【列番号】（最左端を0として数値）を指定し、その列を保持、あるいは削除を指定する事で、列の整形が出来ます。

#### この方法での注意点は以下の通りです。
・アクティビティで直接指定するので、予め決められた列の並びの変更に向いている。ダイナミックに列の並びの変更に対応する事は出来ない。
・保持の場合、入力データテーブル上に存在しない列名（列番号）が指定されていても、無視される。
・削除の場合、入力データテーブル上に存在しない列名（列番号）が指定されていた場合、「メッセージ: 'データ テーブル ウィザードをフィルター処理します。' 引数 '列名' の値が設定されていないかまたは無効です。 内」というエラーで異常終了する（例外の型: System.ArgumentException）。
・一つひとつ設定しなければならないので、巨大なレイアウトとする場合は開発の手間がかかる。
・設定したレイアウトの変更は、[×]と[+]を使って行う。ドラッグ＆ドロップ的な操作は出来ない。

- - - 
## ２）「データテーブルをマージ」アクティビティを活用する。
DataTableの列の並びの変更に、なんで「データテーブルをマージ」なんだ？と思われる方もいらっしゃるかと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/a453a8cd-f759-c7f8-8c40-d7170c781319.png)
<img width="300" alt="マージのプロパティ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/96d3039e-6bc8-31aa-3d1b-99efc50bfdaf.png">
私も同じレイアウト同士のDataTableを縦結合（追記）する機能だと思っていました。が、【スキーマがない場合の動作】に種類がある事に気が付きました！

<img width="300" alt="スキーマがない場合の動作の設定候補値" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/b7bffa34-4284-03a8-5106-a9407914727f.png">

コピー元のスプレッドシートのある列が、コピー先のスプレッドシートに無い場合に、
Add:コピー元のスプレッドシートのある列を、コピー先のスプレッドシートに新設する。
Ignore:コピー元のスプレッドシートのある列を、コピー先のスプレッドシートに新設せず無視する。

コピー元のスプレッドシートのある列が、コピー先のスプレッドシートにある場合で、
Error:それぞれの列属性（型とか）が異なっていたらエラーとする。
（AddWithKey:すべての列に主キー情報を追加し、列を定義する。）

この【スキーマがない場合の動作】の"Ignore"を設定してマージを行えば、コピー先のスプレッドシートに指定したDataTableのレイアウト通りに値がマージ（転記）されます。

しかし、時として、このようなエラーとなる時があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/f80d9a70-f4a2-64de-1134-af71f33143c9.png)

これは同一列名で、コピー元とコピー先の列属性が異なっている為に起こるエラーです。例えば、Int32型のデータをDateTime型には直接代入することはできませんから。

検証してみました！
１）コピー元のスプレットシートとして、４つのDataTableのDataTable変数を用意します。それぞれのDataTableには、"Column1"という列名で、その形式がString型,Int32型,Object型,DateTime型のものを定義し、その型属性の値を設定しておきます（実際にはデータテーブルを構築アクティビティにて）
２）コピー先のスプレットシートとして、４つのDataTableのDataTable変数を用意します。それぞれのDataTableには、"Column1"という列名で、その形式がString型,Int32型,Object型,DateTime型のものを定義します。行は挿入しません。
３）String型のDataTable変数をコピー元のスプレッドシートとして設定し、コピー先のスプレッドシートを、String型,Int32型,Object型,DateTime型のDataTable変数を設定し、それぞれ「データテーブルをマージ」アクティビティで実行します。
４）残りのInt32型,Object型,DateTime型のDataTable変数をコピー元のスプレッドシートとして設定し、３）と同様な処理を実行します。

【結果】
| 受け⇒ | Ｓ | Ｉ | Ｏ | Ｄ |
|:-:|:-:|:-:|:-:|:-:|
|Ｓｔｒ| ○ | × | × | × |
|Ｉｎｔ| × | ○ | × | × |
|Ｏｂｊ| × | × | ○ | × |
|Ｄａｔ| × | × | × | ○ |

「データテーブルをマージ」アクティビティはちゃんと作用していますね(^^♪

【対策】
コピー先のスプレッドシートの列属性を、コピー元のスプレッドシートの列属性に合わせて定義すればいいですね。

「データ列を追加」アクティビティが使えそうですが、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/ce375840-b9b2-88d6-e873-f410f6d792de.png)
<img width="300" alt="TypeArgumentの設定候補値" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/7d2eb811-380a-2ab5-97c1-028dc7f3bce3.png">
ご覧のように、列属性（TypeArgument）は単一選択になっていますので、ちょっと列の並びの変更用に列を作るには不向きかもしれません。

#### こんな時には、DataTable.Columns.Addメソッドを使ってみましょう！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/6dc105f0-957b-6ddc-9d35-15e6b2af6468.png)
TargetObjectには、コピー先のスプレットシートを示すDataTable変数.Columnsを指定します。
MethodNameには、Addを指定します。この時、大文字小文字は処理に影響しますので、正しく指定してください。

このAddメソッドには、パラメーターが必要です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/de6a7c10-8d00-36fd-7a4b-2af52aa152eb.png)
一番目の引数は、コピー元のスブレッドシート上に存在する【列名】。ここでは"Column1"としています。"DT.Columns(0).ColumnName"のような指定でもOKです。
二番目の引数は、コピー元のスブレッドシート上に存在する【列属性】（System.Type型）を指定します。ここの設定値は、コピー元のスブレッドシート上に存在する列の属性ですので、固定とせず、"DT.Columns("Column1").DataType"のように指定します。無論、"DT.Columns(0).DataType"のような指定でもOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/c36de7a2-13d0-3ee7-63b8-00e6523e104d.png)
これを駆使すると、コピー元の列の情報が、コピー先の列の情報として設定される訳ですね。

#### この方法での注意点は以下の通りです。
・１アクティビティで実現できる！
・列間の属性が一致していなければならないが、EXCEL⇒EXCELならそう問題は起きにくいでしょう。

- - - 
## ３）「メソッドを呼び出し」アクティビティを活用する。
DataTableの列の並びの変更に、なんで「メソッドを呼び出し」でどうするんだ？と思われる方もいらっしゃるかと思います。

「メソッドを呼び出し」はそもそも.Netのオブジェクト達に存在するメソッドを呼び出すアクティビティです。DataTable型にもさまざまなメソットが存在していますので、それを直接呼び出してしまう方法です。
実装ベースでお見せするなら、「繰り返し(各行)」アクティビティで、コピー元のスブレットシートのDataTable変数を指定します。一行分のDataRow情報は、rowという変数に格納されますね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/82579895-503a-804c-fca9-105da281f623.png)

TargetObjectには、コピー先のスプレットシートを示すDataTable変数を指定します。
MethodNameには、ImportRowを指定します。この時、大文字小文字は処理に影響しますので、正しく指定してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/59dca2c5-003c-58af-835c-e4542806bc8a.png)

このImportRowメソッドも、パラメーターが必要です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/669015/5f046109-aad5-a7d6-09d0-24fa8374ef32.png)
一番目の引数は、コピー元の一行分の情報が格納されている変数（DataRow型）。ここでは"row"ですね。この方法ですと、コピー先のスプレッドシートのDataTable上の列名が、コピー元のスプレッドシートのDataTable上の一致する列の値が転記されます。

はい、ここでも、列の属性に関して、転記できない場合がありますので、検証してみましょう。
１）コピー元のスプレットシートとして、４つのDataTableのDataTable変数を用意します。それぞれのDataTableには、"Column1"という列名で、その形式がString型,Int32型,Object型,DateTime型のものを定義し、その型属性の値を設定しておきます（実際にはデータテーブルを構築アクティビティにて）
２）コピー先のスプレットシートとして、４つのDataTableのDataTable変数を用意します。それぞれのDataTableには、"Column1"という列名で、その形式がString型,Int32型,Object型,DateTime型のものを定義します。行は挿入しません。
３）String型のDataTable変数をコピー元のスプレッドシートとして設定し、コピー先のスプレッドシートを、String型,Int32型,Object型,DateTime型のDataTable変数を設定し、それぞれ「メソッドを呼び出し」アクティビティで、ImportRowメソッドを実行します。
４）残りのInt32型,Object型,DateTime型のDataTable変数をコピー元のスプレッドシートとして設定し、３）と同様な処理を実行します。

【結果】表側は、属性(設定値)
| 受け⇒ | Ｓ | Ｉ | Ｏ | Ｄ |
|:-:|:-:|:-:|:-:|:-:|
|Str(1)| ○ | ○ | ○ | × |
|Int(2)| ○ | ○ | ○ | × |
|Obj(3)| ○ | ○ | ○ | × |
|Dat(t)| ○ | × | ○ | ○ |

先ほどの「データテーブルをマージ」アクティビティを使った方法と結果が違いますね。どうやら、列の転送の際に、コピー先のスプレッドシートのDataTable上の列属性に合わせて、型変換してくれるようです。試しに設定値を文字列にしてやってみましょう。
| 受け⇒ | Ｓ | Ｉ | Ｏ | Ｄ |
|:-:|:-:|:-:|:-:|:-:|
|Str(A)| ○ | × | ○ | × |
|Int(2)| ○ | ○ | ○ | × |
|Obj(C)| ○ | × | ○ | × |
|Dat(t)| ○ | × | ○ | ○ |

やはり、String型中の文字列はInt32型に型変換できませんので、ImportRowメソッドは失敗してしまいました。

#### この方法での注意点は以下の通りです。
・１アクティビティで実現できる！
・ただ、「繰り返し(各行)」アクティビティで一行一行実施しなければならない。
・それを逆手にとって、対象行かどうかを「条件分岐」アクティビティで判断し、そのロジックの中で加工した行を転記出来る。
・流石に文字属性⇔数値属性は難しいけど、EXCEL⇒CSV、CSV⇒EXCELならそう問題は起きにくいでしょう。





## 【まとめ】
・列の並びの変更にはいろんな方法があり、いろんな癖があり、開発者の好みによる事でしょう。
・列の並びの変更後のDataTable域は、変更前のDataTable域の発生源と同じ方式で作成した方が楽。例えばCSVだったら、変更後の列の並びだけのCSVを読み込み、0行のDataTable域を準備する。

- - - 

##おわりに
いかがでした？

今回も読んでいただきありがとうございました！
是非UiPathでのロボ開発の一助になればと思っています。

ありがとうございました！