この記事はUiPathブログ発信チャレンジ2022サマーの24日目の記事です。
昨日の記事は
- PP_RPA(ぴぴ)さんの RPAの運用について
- Kiyoko Kikuchihara de Rosas (Koko)さんのUiPath Automation Cloud の言語を日本語に設定する方法
- みやぎさんの【UiPath】データテーブルの操作(8)重複行の削除
-
さおのMBA JOURNEY〜経営学修士課程への道〜さんのAWSをUiPathで動かしてみたい_第3話(AWSスコープに変数を入れる)
です。多いな!?
明日の記事はいわさき りょうすけ 🤖 #UiPathFriendsさんです。
概要とか
UiPathでRPAの開発をするとき、98%1ぐらいの割合で「DataTable型」を使用することになります。
単純なUI操作に終始するだけのWorkflowなら兎も角、ちょっと凝ったことをするには、何かしらの理由でお世話になることがほとんどでしょう。
この記事は、その「DataTableが何なのか」的な部分にスポットライトを当てて、UiPathを使う上で関係しそうなことを書いていきます。つまり「この記事を読むと●●●ができる」というよりは、何かDataTableを使っていて、ハマったり困ったりしたときに、なんとなく思い出すことで解決の糸口になるかも、的な解説になります。
ちなみに、よく「DataTable型」と言いますが、正式には「System.Data.DataTable」です。これは「Systemという名前空間の中の、Dataという名前空間の中の、DataTable型」という意味です。ちょっと冗長ですが、そんなもんです。
なので、UiPathが使用している .NET の仕様では、たとえば「Hogehoge.Funifuni.DataTable」という型を、別に作ることも可能です。とはいえ、名前を重複させても混乱の元なので、明確な理由がなければ、やるべきではないですが。
さて、DataTableを使っていて出会う「型」は、DataTable型だけではありません。この記事では、DataTableだけでなく、その周辺の型も絡めて、ざっくりと解説しようと思います。
具体的な型
System.Data.DataTable
身も蓋もない言い方ですが「0個以上の列が定義された、0個以上の行を持つテーブルに該当する」型です。
配列との違いは、列単位で型を持っていることです。
UiPathを使っている範囲だと、何か「Excelでの入出力、あとは表抽出(昔で言うData Scraping)のデータ」ぐらいなイメージですが、.NET Frameworkな世界だと、どちらかと言えばRDB(AccessとかSQL ServerとかOracleとかみたいな、いわゆるデータベース)の値を扱うことを想定したメソッドやプロパティが結構あります。CaseSensitiveプロパティ
とかPrimaryKeyプロパティ
とか。こういったRDBとの連携に使いそうな要素は、DataTableだけでなく、それに関連する型にも含まれます。
とは言っても、たとえばUiPathでExcelから範囲を読み込み
で持ってきたDataTableに、PrimaryKeyも何もあったものじゃないので、Excelとデータをやりとりする範囲では意識する必要はないのですが。
裏を返すと、RDBとの連携なんかをやるようになると、単純に「2次元配列のちょっとすごいヤツ」と思っていると、ものすごい勢いで足下をすくわれるので気をつけてください。
それだけで1つの記事になる分量なので、ここでは詳しくは触れませんが、RDB絡みの処理でDataTableを扱うときは、UiPathでExcelから持ってきたDataTableとは別物と思ってください。
DataTable内の値を書き換えてAcceptChanges()するとDB側にも影響することもあります。
あるいは、ロジックレベルの問題として、Workflowや使い方によっては、ダーティーリード等を引き起こす可能性とか、いろいろ考慮する必要が出てくるのです。
↑これを読んで「なんのこっちゃ?」と思った人は、UiPath使ってRDB関連の操作をDataTableでやることがあったら、詳しい人に相談して設計・実装しましょう。マジで事故ります。
System.Data.DataRow
DataTableを使う上で、一番意識することになる型かもしれません。DataTableが「表全体」だとすれば、DataRowは「行」に該当します。つまり、1行分のデータが入っている型、という理解で、通常は問題ありません。
For Each Row / 繰り返し(データ テーブルの各行)
アクティビティで、繰り返しごとに渡される変数(デフォルトではCurrentRow
)は、この型になります。
DataRowは独立して存在できません
少しだけ重要な点として、DataRowは常にDataTableに紐付きます。言い換えると、DataTableから独立しては存在できません。また、異なるDataTable間では直接移動はできません。
単純な配列の値なら、気楽の他の配列に代入できますが、行(DataRow)単位では、それはできないということです。
DataTableの行をまるごと、他のDataTableに移動したい場合、DataTable型変数.ImportRow(対象DataRow型変数)
すると、実行したDataTable型変数の方にImportすることはできます。ImportRowはvoid型(戻り値はなくて、引数にしたDataRowに直接影響を与える)なので、UiPathで使うときはInvoke Method / メソッドを呼び出し
してあげましょう。
C#読める人向けの情報になりますが、constructorを見ると、internal指定な上、引数のDataRowBuilder型は元DataTableをReadonlyで取ってくる内容になってるのがわかります。
このあたりが、普通の2次元配列とDataTableの違いとも言えるかもです。
この性質を知っておくと、たとえば「行ごとの処理をするために、DataRowを入力の引数にするWorkflow」を作るとき、そのWorkflow内部でDataRowが含まれているテーブルを見る必要があっても、引数で「対象のDataTableも渡す」必要がなくなります。DataRow型変数.Table
プロパティを見れば、親のDataTableが取得できるからです。(むしろ、間違って別のDataTableとか渡すと混乱の元です)
DataColumnとも紐付いています
DataColumn(列)については後述しますが、DataRowはDataColumnとも紐付いています。
DataTableへのアクセスは、最終的には「どの行のどの列」みたいな形で値にアクセスすることが圧倒的に多いので当たり前ですが。
個々のカラムの値は、開発画面上ではObject型と判定されます
「Rowの何列目(あるいはどのDataColumn)」というアクセスをした場合、UiPath Studio等、開発画面で認識される戻り値はObject型になります。 これはDataRowに対して、Workflowの設計の時点では、開発ソフトウェアが該当する型情報を付与する方法が無いので、仕方ない挙動といえます。
……。
と、書くと、難解になってしまうのですが、たとえば、Assign / 代入
で、
Int型変数 = DataRow型変数(0)
とかやったとき、UiPath Studioではエラーになります。という経験は、意外と皆さんあるんじゃないでしょうか?
たとえWorkflowのロジック上、DataRow型変数の0カラム目は必ずInt32になるように作っていたとしても、.NET Frameworkで定義されている型的にはあくまでObject型で、そこを開発環境は把握できないからです。
配列との違いという視点で、配列型の変数であれば、「Int32型の配列」みたいにできますが、DataTableに対して、「0カラム目がInt32で、1カラム目がStringで……」のような定義は、開発時点ではできません。ここが上に書いた「全部Objectとして扱われる」の原因になります。
UiPath的には、Build Data Table / データテーブルを構築
アクティビティなら、事前にカラムの情報わかってるよね?と感じてしまうかもしれません。が、残念ながら、それはあくまでUiPathの世界だけの話で、UiPathの基盤になっている.NET Frameworkの世界には、そういうものはないのです。
(もうちょっと厳密に書くと、.NET Framework的には、Build Data Table / データテーブルを構築
アクティビティは、「何やらDataTableを出力する機能」でしかありません。なので、同アクティビティの引数になっている、個々のカラムの情報は、Workflowを開発する段階では、出力のDataTableと紐付けができないのです)2
System.Data.DataRowCollection
文字通りDataRowのCollection、つまり複数のDataRowを内包する型です。
「じゃあDataRowの配列でいいんじゃない?」と思うかもしれませんが、配列だと困るから、わざわざ型として用意されているのです。
DataTableと紐付いています
個々のDataRowがDataTableと紐付いていることはDataRowのところに書きましたが、DataRowCollectionもまた、DataTableと紐付いています。
これは割と重要なことで、そのDataRowCollectionに含まれるDataRowはすべて同じテーブルに含まれることが、システム的に担保されていることになります。
「DataRowの配列」で代用しようとすると、個々のDataRowの集合体以上の意味を持たないため、親のテーブルがバラバラで処理に手間がかかるケースが起きうることを考えると、これは大きなメリットです3。というか、それがDataRowCollectionの存在意義と言っても良いかもしれません。
DataTableのRowsはDataRowCollectionです
実際のところ、(UiPathのアクティビティではなく).NET Frameworkでの「DataTableに行を挿入する」とか「DataTableの行を削除する」とかは、DataTable型で呼び出すメソッドではなく、DataRowCollectionのメソッドとして実装されています。
比較的発生しうるユースケースとして、直接、.NET Frameworkのメソッドを呼ぶような状況は「DataTableの途中に行を挿入したい」ケースぐらいだと思いますが、DataRowCollection型のメソッドを調べておくと、Invoke Method / メソッドを呼び出し
でサクッと呼び出して解決できることがあったりします。
System.Data.DataColumn
DataRowが「行」なら、DataColumnは「列」です。
こちらは、ColumnName(列名)や、DataType(中のデータ型)などがあります。
そこまで意識する必要がある要素でもありませんが、DataColumnは、DataRowと違ってDataTableに紐付かずに存在できるという特徴(?)があります。ちょっと面白いですね。
DataTableを使わないWorkflowで、変数でDataColumn型の値を作って、New System.Data.DataColumn("列の名前")
とかして、そのままWorkflowを終了してもエラーになりません。だから何だって話ですが。
総じてDataColumnはDataRowよりも紐付きや制限が緩い、ぐらいに思っておくと良いと思います。
System.Data.DataColumnCollection
DataColumnの集合です。それだけ。
あんまり書くこと思いつかなかったんだもん、マジで。
強いて言えば、こちらは仕様上、DataTableに紐付きます。そういう意味では単体のDataColumnとは異なります。
ただ、「列と行」という性質の違いを考えると、そもそもが「複数のDataTableの列がごっちゃになってる状態」みたいな想定そのものに無理があるというか、どうしてそうなったそんな格納の仕方をする時点で根本的に何かおかしいので、考慮も影響もないんじゃないかなー、と思います。
さいごに
DataTableってUiPathを使う上で、割と序盤から意識する必要のある「.NET Frameworkが剥き出しの部分」なので、ややとっつきにくかったり、知らないとハマる要素が多いと思ってます。
特にプログラミング経験がない人だと、とっつきにくい部分も少なからずあるかもしれませんが、DataTableさんをしっかり理解すると、一般的なデータ処理ではさほど困らなくなると思うので、ゆっくりでも覚えていきましょう!
-
個人の経験です。 ↩
-
UiPathユーザー的には定義できると嬉しいんですが、たとえば「1カラム目がString、2カラム目がInt32、3カラム目が・・・な感じの行データを配列にしたい」という場合、.NET Frameworkとかの主戦場になるプログラミング言語では「構造体を定義して配列にすればいいよね」で一蹴されるので、そーいう機能が追加される可能性は絶望的です。 ↩
-
まー内部的なDataTableの中身はrow[]なので配列じゃんと言えばそうなんですけどね?そこに配列以上の意味を持たせてるってことですよ? ↩