ヒント4:データのフィルタリング
ADO.NETでデータをフィルタ(抽出)する方法は2つあります。1つはDataViewとRowFilterを使う方法、もう1つはDataTable.Select()を使う方法です。.NET開発に不慣れな人から、どちらの方法を使うのが良いかという質問を受けることがあります。その答えは、状況に応じて変わります。その点を見ていくことにしましょう。
一般的には、DataViewを使用して、フィルタした結果をバインドします。そして、DataRowの配列を使用して、フィルタした結果をたどり、処理や計算などを実行します。そもそも、DataViewとは、特定のフィルタと並べ替え順序が適用された、DataTableのビューの一つにすぎません。次のコードは、さまざまなフィルタや並べ替えの例です。
string cFilter; // Examples of RowFilter cFilter = " Amount > 1000 "; cFilter = "FirstName = 'Ken' OR EmpFlag = true"; cFilter = " City LIKE '%whatever%' "; cFilter = "EmployeeID IN (12,144,54)"; // This assumes a Parent Relation cFilter = "Parent(RelName) = 'National'"; string cSort = "Location ASC, Salary DESC"; DataView Dv = new DataView(MyTable, cFilter, cSort, DataViewRowState.CurrentRows);
上記のコードでは、DataViewのオーバーロードの1つを使用して、フィルタと並べ替えをまとめて指定しています。開発者によっては、DataTableだけを指定するオーバーロードを使用し、後続のコードでRowFilterプロパティとSortプロパティを設定するという方法を用いる場合もあります。実のところ、その方法では効率が下がり、無駄な処理が余分に加わることになります。1つの呼び出しにする方が良いでしょう。また、上記の例の中には、親リレーションを使用しているものがあります。DataRelationが定義されており、関連付けられた親(または子)の行に基づいてフィルタを実行したい場合には、Parent(またはChild)キーワードとDataRelationの名前を指定します。
それともう1点、DataViewは、同じDataTableに対して複数作成でき、それぞれ別個のフィルタと並べ替えを適用できます。一方、DataTable.Select()では、DataRowオブジェクトのコレクションが結果として得られます。フィルタと並べ替えの構文は、DataViewの場合と同じものが使用できます。
DataRow[] aRows = MyTable.Select(cFilter,cSort);
それでは、DataViewを使用するか否かはどのように判断すればよいのでしょうか。答えは簡単です。フィルタの結果をバインドしたいときにはDataViewを使用します。DataViewは、「IEnumerable」「ICollection」「IList」を実装しており、データバインドに最適です。DataTable.Select()が返すDataRowのコレクションはバインドできません。
しかしその一方で、DataRowのコレクションでは可能なのに、DataViewでは不可能な処理もあります。型指定されたデータセットの中の、厳密に型指定されたオブジェクトのいずれかとして、各行をキャストするという処理です。
dsOrders odsOrder = new dsOrders(); // Run some process to populate orders // Now perform a select and filter DataRow[] aRows = odsOrder.dtOrderHdr.Select(cFilter, cOrder); // We can cast the collection of rows // as the strongly-typed rows from our typed DS foreach (dsOrders.dtOrderHdrRow oRow in aRows) { cOrderNumber = oHeaderRow.OrderNumber; }
従って、一般論としては、フィルタしたデータをバインドする場合にはDataViewを使用し、各行をたどって計算などの処理を実行する場合には行の配列を使用するのが良いでしょう。
時として、DataViewをDataTableにコピーし戻すことが必要になる場合があります。DataViewをXMLにコピーすることや、DataViewを認識しない.NETライブラリや製品(Crystal Reportsなど)にDataViewの結果をバインドすることが必要になるかもしれません。Visual Studio .NET 2003には、DataViewをDataTableに変換する機能は備わっていませんが、幸い、小粒ながらも便利な汎用的関数を作成することで、その処理を実現できます。
public DataTable ViewToTable(DataView dv) { // Create a new table structure from the table // object of the view, then loop through the view // and import the rows into the new table DataTable DtReturn = dv.Table.Clone(); foreach (DataRowView drv in dv) DtReturn.ImportRow(drv.Row); // Note that we have to use ImportRow // There's no such thing as Row.Copy return DtReturn; }
このコードに手を加えれば、DataRowの配列からDataTableを作成する処理も簡単に実現できます。
最後にもう1つ、重要な情報です。Visual Studio 2005には、ToTable()という便利なADO.NET関数が新たに用意されています。DataViewをDataTableに変換する関数です。
DataTable dtFromView = dv.ToTable();
ToTableには、個別の値ごとにフィルタを実行したい開発者に朗報となる機能も備わっています。Visual Studio 2003で開発者からよく挙がっていたのが、ADO.NETにはSQLのSELECT DISTINCTに相当する機能はあるかという質問でした。Visual Studio 2003では、その機能はネイティブには備わっていなかったため、その機能はなしで我慢するか、その処理を実現する独自のコードを作成するかのいずれかの方法しかありませんでした。ToTableはオーバーロードされており、SELECT DISTINCTステートメントと同じような形で列のリストを指定できます。
bool lDistinct = true; DataTable dtFromView = dv.ToTable(lDistinct, "Column1", "Column2"); // You can even create a unique list from the // DefaultViewof a DataTable DataTable dtUnique = dtMyTable.DefaultView.ToTable(true, "Column1", "Column2");
ヒント5:DataSetでの演算
データセットの演算は2つのレベルで実装できます。1つは行ごとの演算で、その場合は集計列としてDataColumnを定義します。もう1つはDataTable全体(または行のコレクション全体)を対象とした演算で、その場合は「SUM」または「COUNT」を計算します。
集計列は次のように作成します。
string cExpr = "HourlyRate * Hours"; Dt.Columns.Add("Pay", typeof(Decimal), cExpr);
型指定されたデータセットを使用している場合は、データセットデザイナでExpressionプロパティを設定することで集計列を定義できます。
DataTableのすべての行に対する計算を実行する場合は、DataTable.Compute()メソッドを使用します。Computeが返すオブジェクトは、適切な型にキャストできます。
string cExpr = " SUM(Hours)"; string cCond = " OTFlag = true"; decimal nTotHrs = (decimal)Dt.Compute(cExpr,cCond);
また、親子関係に基づく集計値を自動的に計算することもできます。例えば、顧客データのDataTableと注文データのDataTableがあり、顧客IDに基づく親子関係があるとします。この場合、顧客データのテーブル「dtClients」に、各顧客に対応する注文の合計を反映する集計列を持たせることができます。そのためには、親テーブルでSUM(Child.OrderAmount)という式を使用します。型指定されたデータセットを使用している場合には、Visual Studio 2005のデータセットデザイナでその式を直接指定できます。
