はじめに
開発者は、さまざまなレベルで、さまざまなツールを使って、さまざまな形態でデータを扱わなければならないことがよくあります。この記事では、データを扱う際に直面しがちな問題について、前編と後編の2回に分けて解説します。前編となる今回は、ADO.NET 2.0、ASP.NET 2.0、T-SQL 2005の機能について取り上げます。後編では、T-SQL 2005に関するその他の話題や、.NET 2.0のジェネリックの使用方法について取り上げます。
目的:データ処理の包括的なヒント集
先日私は、CodeCampという集まりの中で、データ処理に関するさまざまなトピックについて、いくつかのセッションで話す機会がありました。あるセッションでは、開発者の目を引くT-SQLの新機能について話し、別のセッションでは、ADO.NET 2.0による新手法について取り上げ、さらに別のセッションでは、Webアプリケーションにおけるデータ処理について解説する、といった具合です。そのとき、参加者の1人が、セッション終了後に私のところにやって来て、こんなことを言ってくれました。「C#のMVPとして紹介されている人が、データやその処理方法についてこんなに詳しく解説してくれるとは嬉しい驚きだった」、というのです。私は笑顔で答えました。私は今度、データ処理テクニックについての記事を『CoDe Magazine』誌に書くことになっており、あなたからいただいた質問も含め、今日の経験は、そのアイデアを練るうえで役に立ちましたと。
使用している言語がC#にせよVisual Basic .NETにせよ、あるいはそれ以外にせよ、開発者たちが日がな1日処理しているのは「データ」です。データの検索、集計、表示といった処理です。アプリケーション開発者の主たる仕事は、データの処理なのです。誰が何と言おうと、その点は確かです。
1つのツールだけを使ってデータ処理を行う開発者はあまりいません。データベース、レポート作成プログラム、サードパーティ製のユーティリティなどを使って、プレゼンテーション層を処理したり、他の部分もまとめて処理したりします。使用できるツールや手法は数限りなくあります。.NETにおけるデータ処理のさまざまな手法をテーマにして、1冊の本が書けるくらいです。
そこでこの記事は、前編と後編に分けることにします。「The Baker's Dozen」シリーズとしては初の「ダブルヘッダー」です。「第1戦」となる前編では、以下の内容を取り上げます。
- ADO.NETに関する5つの話題(型指定されたデータセット、日付の計算、データのリレーション、データのフィルタリング、データの集計)
- ASP.NET 2.0アプリケーションにおけるデータ処理の4つのヒント
- T-SQL 2005の新機能でデータを取得するための4つのヒント
ダブルヘッダーの「第2戦」では、.NETのジェネリックを活用したデータ処理や、新しいObjectDataSource
クラスの解説、T-SQL 2005でのデータ取得に関するその他の手法について取り上げます。では、プレイボールといきましょう。
ヒント1:型指定されたデータセットの基礎
型指定されたデータセットとは、ビジネスエンティティデータのコレクションを取りまとめる、厳密に型指定されたコンテナです。System.Data.DataSet
クラスを継承していますが、遅延バインディングと弱い型指定を特徴とする標準のデータセットとは違い、厳密な型指定に基づくデータアクセスが可能です。型指定されたデータセットでは、テーブル、列、およびリレーションオブジェクトを、汎用的なコレクション要素ではなく、名前の付いたプロパティとして利用できます。
型指定されたデータセットでは、IntelliSenseによる入力支援や、コンパイル時の型チェックが可能です。また、レポートという観点では、型指定されたデータセットを使用するとデータ駆動型のレポート作成を簡便化でき、ストアドプロシージャの結果セットのセルフドキュメンテーションを的確に実現できます。型指定されたデータセットを利用したコーディングでは、効率性や構造のレベルが上がります(この点については、後でいくつか例を示します)。
型指定されたデータセットの作成方法は2つあります。スキーマが既にある場合(または簡単に作成できる場合)には、次のような方法が使えます。
DataSet DsReturn = this.RunStoredProc("GetAgingReceivables"); // Define a DataSet name and table names // (we could use Table Mappings as well) DsReturn.DataSetName = "dsAgingReport"; DsReturn.Tables[0].TableName = "dtAgingDetails"; DsReturn.Tables[1].TableName = "dtAgingSummary"; DsReturn.Tables[2].TableName = "dtAgingBrackets"; DsReturn.Tables[3].TableName = "dtClients"; // Write out the schema DsReturn.WriteXml(@"c:\MyXml\DsAgingReport.xsd", XmlWriteMode.WriteSchema);
このテストコード(これを基にしたスキーマ生成ユーティリティも可)を実行し、生成されたスキーマを、型指定されたデータセットとしてVisual Studio 2005に追加します。ソリューションエクスプローラで右クリックし、[Add(追加)]の[Existing Item(既存の項目)]をクリックして、XSDファイルが保存されているフォルダに移動します。型指定されたデータセットがVisual Studioによって作成されたら、それを参照および編集する方法はいくつかあります。標準のXMLスキーマエディタを使用する場合は、ソリューションエクスプローラでデータセットを右クリックし、[Open With(ファイルを開くアプリケーションの選択)]をクリックします。
スキーマがまだなく、型指定されたデータセットの定義を一から作成する場合は、データセットデザイナを利用して手動で作成できます。しかし、その方法ばかり多用し、型指定されたデータセットを既存のスキーマから作成する方法をあまり使用していない人は、方法を考え直した方が良いかもしれません。
ともあれ、型指定されたデータセットの作り方は分かりました。ではこれで、何が実現されるのでしょうか。型指定されたデータセットの機能や特徴について見ていくことにしましょう。
1つ目は、型指定されたデータセットでは、厳密な型指定が可能で、IntelliSenseによる入力支援機能も利用できるという点です(図1を参照)。型指定されていないデータセットの場合は、列を番号で参照したり、式として指定しなければなりません。また、図1の例では、列を適切なデータ型にキャストする必要がありません。私見では、列オブジェクトをしかるべき型にキャストしなくてよいというのは、型指定されたデータセットの最も嬉しい機能の1つと言えます。
2つ目は、列の既定値をデータセットデザイナで簡単に定義できるという点です。defaultvalue
プロパティを設定すれば済みます。
3つ目は、データセットデザイナで厳密に型指定されたDataTableに主キーを定義した場合、その列名に対応するFind
メソッドがクラスに用意されるという点です。例えば、OrderID
という主キーのテーブルの場合は、FindByOrderID
というメソッドを指定して検索を実行できます。2つの列を組み合わせた主キーの場合は、例えばCustomerID
とOrderID
なら、FindByCustomerIDOrderID
というメソッドが生成され、これが使用されます。この方法は、効率の点では一番だと思います。
4つ目は、型指定されたデータセットにテーブルのリレーションが含まれている場合、XMLスキーマ定義ツールによって、階層型のデータをたどるためのメソッドが、型指定されたデータセットクラスに用意されるという点です。例えば、「DtOrderHeader」と「DtOrderDetail」というテーブルに対して、親テーブル/子テーブルのコードを作成する場合は、.NETのスキーマツールによってGetDtOrderDetailRows
というメソッドが自動的に生成されます(これについてはヒント3で取り上げます)。
5つ目は、型指定されたデータセットの方がnull値を扱いやすいという点です。厳密に型指定されたDataRowには、列値がnullかどうかをチェックするメソッドと、列値をnullに設定するメソッドが用意されます。
// Set value to NULL oRow.SetHoursWorkedNull(); // Check for null value if(oRow.IsHoursWorkedNull()==true)
6つ目は、Visual Studio .NET 2003の型指定されたデータセットに対する批判の声にも応える機能です。Visual Studio 2005の新しいパーシャルクラス機能によって、型指定されたデータセットを継承しやすくなりました。ただ、パーシャルクラスは確かに素晴らしい新機能ですが、VS2003でも、型指定されたデータセットのサブクラスを作成して検証コードやその他の機能を追加することは不可能ではありません。『CoDe Magazine』誌の2006年1月/2月号で私が執筆した記事では、型指定されたデータセットのサブクラスを作成して、XMLのインポート機能を追加するという例を紹介しています。
型指定されたデータセットは完全無欠ではありません。その使用にはオーバーヘッドが伴います。型指定されたデータセットは、型指定されていないデータセットより高速な面もあるものの、データセットが複雑な場合には、インスタンス化が負担となる可能性があるのです。型指定された同じデータセットのインスタンスを頻繁に作成するアプリケーションでは、その作成処理でかなりの時間を食われてしまうかもしれません。Uri N.氏が開発した、型指定されたデータセット用のプロキシクラスを使用すると、実際の作成は一度だけで済むようになります。このクラスの詳細については、CodeProjectのページを参照してください。
開発者の中には、型指定されたデータセットクラスで、DataTable/DataRowオブジェクトなどの項目やメソッド/イベントに適用される既定の名前付け規則について、不満を抱く人もいます。自動的に付けられる名前が、自分の開発プロジェクトで使用したい名前付け規則と合致しない場合もあります。幸い、名前付けに関してよくある問題のいくつかは、型指定されたデータセットの注釈を使用することで解決できます。注釈では、型指定されたデータセットで使用される要素の名前を開発者が変更できます。実際のスキーマは変更する必要がありません。
また、注釈では、列がDbNullの場合の対応方法を指定することも可能です。それには、nullValueという注釈を使用します。空文字列や既定の代替値を返すよう、オプションで指定できます。型指定されたデータセットの注釈の使用方法については、Shawn Wildermuth氏の素晴らしい記事がネット上で読めます(Shawnは、ADO.NETに関する文章を多数執筆している敏腕ライターです)。
ヒント2:日付の演算
.NET Frameworkには、2つの日付の差を計算したり、未来の日付を求めたりなど、日付に関する各種の演算機能を持つクラスがあります。いくつかの例を見てみましょう。
// Create two dates, Nov 1 2005 and Oct 1, 2005 DateTime dAgingDate = new DateTime(2005,11,1); DateTime dInvDate = new DateTime(2005, 10, 1); TimeSpan ts = dAgingDate.Subtract(dInvoiceDate); int nDiffDays = ts.Days; // Determine future dates DateTime dNextDay = dtInvoiceDate.AddDays(1); DateTime dNextMonth = dtInvoiceDate.AddMonths(1); DateTime dNextYear = dtInvoiceDate.AddYears(1); // Determine # of days in October 2002 int nDaysInMonth = DateTime.DaysInMonth(2002, 10); // Is the year a leap year? if(DateTime.IsLeapYear(2006)==true) // Determine if a string represents a valid date public bool IsValidDate(string cText) { bool lIsGoodDate = true; // Convert in a try/catch try { DateTime dt = Convert.ToDateTime(cText); } catch (Exception) { lIsGoodDate = false; } return lIsGoodDate; } // Gets today (time will be midnight) DateTime dToday = DateTime.Today; // Get the current date/time DateTime dNow = DateTime.Now;
Webで調べると、日付の計算機能は他にもいろいろと見つかります。私が最近見つけた中では、このような素晴らしいものがありました。
ヒント3:データのリレーション
ヒント1でも述べたように、型指定されたデータセットを定義すると、リレーションがあるDataTableを扱いやすくなります。データセットデザイナでリレーションシップを定義することにより、Visual Studioによってリレーションが自動的に生成され、親子関係の子の行を取得するための既定のメソッドが用意されます。次の例では、厳密に型指定された注文ヘッダーの行オブジェクトがGetdtOrderDtlRows
というメソッドを持っています。
dsOrders odsOrders = new dsOrders(); // Run some process to fill the DataSet foreach(dsOrders.dtOrderHdrRow oHdrRow in odsOrders.dtOrderHdr.Rows) foreach(dsOrders.dtOrderDtlRow oDtlRow in oHdrRow.GetdtOrderDtlRows()) nProductPK = oDetailRow.ProductPK;